User:Monster Domosed/common.js: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(211 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* mw.loader.load('https://deadlock.wiki/index.php?title=User:Monster_Domosed/Wiki_Redesign.js&action=raw&ctype=text/javascript'); */
(function () {
(function () {
     'use strict';
     'use strict';


     var availableIcons = [
     const urlCache = new Map();
        { id: 'abrams', name: 'Abrams' },
        { id: 'apollo', name: 'Apollo' },
        { id: 'bebop', name: 'Bebop' },
        { id: 'billy', name: 'Billy' },
        { id: 'calico', name: 'Calico' },
        { id: 'celeste', name: 'Celeste' },
        { id: 'doorman', name: 'The Doorman' },
        { id: 'drifter', name: 'Drifter' },
        { id: 'dynamo', name: 'Dynamo' },
        { id: 'graves', name: 'Graves' },
        { id: 'grey-talon', name: 'Grey Talon' },
        { id: 'haze', name: 'Haze' },
        { id: 'holliday', name: 'Holliday' },
        { id: 'infernus', name: 'Infernus' },
        { id: 'ivy', name: 'Ivy' },
        { id: 'kelvin', name: 'Kelvin' },
        { id: 'lady-geist', name: 'Lady Geist' },
        { id: 'lash', name: 'Lash' },
        { id: 'mcginnis', name: 'McGinnis' },
        { id: 'mina', name: 'Mina' },
        { id: 'mirage', name: 'Mirage' },
        { id: 'mo-krill', name: 'Mo & Krill' },
        { id: 'paige', name: 'Paige' },
        { id: 'paradox', name: 'Paradox' },
        { id: 'pocket', name: 'Pocket' },
        { id: 'rem', name: 'Rem' },
        { id: 'seven', name: 'Seven' },
        { id: 'shiv', name: 'Shiv' },
        { id: 'silver', name: 'Silver' },
        { id: 'sinclair', name: 'Sinclair' },
        { id: 'venator', name: 'Venator' },
        { id: 'victor', name: 'Victor' },
        { id: 'vindicta', name: 'Vindicta' },
        { id: 'viscous', name: 'Viscous' },
        { id: 'vyper', name: 'Vyper' },
        { id: 'warden', name: 'Warden' },
        { id: 'wraith', name: 'Wraith' },
        { id: 'yamato', name: 'Yamato' }
    ];
 
    var PREFERENCE_KEY = 'userjs-selectedicon';
 
    var remClickCount = 0;
    var remClickTimer = null;


     var isInfected = false;
     function getSize(element) {
    var infectionInterval = null;
         const match = element.className.match(/model_size_(\d+)/);
 
         return match ? parseInt(match[1], 10) : 500;
    var buddyMessages = [
        "I got little buddies.",
         "They cute.",
        "They love.",
        "They help.",
        "They great at shuffleboard.",
        "They do lots of things!",
        "Hi friends!",
        "Come help!",
        "I know you'll help me."
    ];
 
    var helperSounds = [
        'Familiar helper emote 01.mp3',
        'Familiar helper emote 02.mp3',
        'Familiar helper emote 03.mp3',
        'Familiar helper emote 04.mp3',
        'Familiar helper emote 05.mp3',
        'Familiar helper emote 06.mp3',
        'Familiar helper emote 07.mp3',
        'Familiar helper emote 08.mp3',
        'Familiar helper emote 09.mp3',
        'Familiar helper emote 10.mp3',
        'Familiar helper emote 11.mp3',
        'Familiar helper emote 12.mp3',
        'Familiar helper emote 13.mp3',
        'Familiar helper emote 14.mp3',
        'Familiar helper emote 15.mp3',
        'Familiar helper emote 16.mp3'
    ];
 
    function getFilePath(filename) {
         return mw.util.getUrl('Special:FilePath/' + filename);
     }
     }


     function playHelperSound() {
     function getFileUrl(fileName) {
         var file = helperSounds[Math.floor(Math.random() * helperSounds.length)];
         if (urlCache.has(fileName)) {
        var audio = new Audio(getFilePath(file));
            return urlCache.get(fileName);
        audio.volume = 0.25;
         }
         audio.play().catch(function () {});
    }


    function getCachedIcon() {
        const apiUrl =
        return localStorage.getItem(PREFERENCE_KEY);
            mw.config.get('wgScriptPath') +
    }
            '/api.php?action=query' +
            '&titles=File:' + encodeURIComponent(fileName) +
            '&prop=imageinfo' +
            '&iiprop=url' +
            '&format=json';


    function setCachedIcon(iconId) {
        const promise = fetch(apiUrl)
        localStorage.setItem(PREFERENCE_KEY, iconId);
            .then(function (response) {
    }
                if (!response.ok) {
 
                    throw new Error('API request failed');
    function applyIcon(iconId) {
                }
        document.body.className =
                return response.json();
            document.body.className.replace(/usericon-\w+/g, '');
            })
        document.body.classList.add('usericon-' + iconId);
            .then(function (data) {
    }
                const page = Object.values(data.query.pages)[0];
 
                return page && page.imageinfo ? page.imageinfo[0].url : null;
    function selectIcon(iconId) {
            })
        setCachedIcon(iconId);
            .catch(function () {
        applyIcon(iconId);
                 return null;
 
            });
        mw.loader.using(['mediawiki.api'], function () {
            var api = new mw.Api();
 
            api.saveOption(PREFERENCE_KEY, iconId)
                .done(function () {
                    if (iconId !== 'rem-helper') {
                        mw.notify('Icon updated successfully!', {
                            type: 'success'
                        });
                    }
                })
                .fail(function () {
                    mw.notify('Failed to save icon.', {
                        type: 'error'
                    });
                 });
        });
    }


    function infectIcon($el) {
         urlCache.set(fileName, promise);
        $el.attr('data-icon', 'rem-helper');
         return promise;
         $el.find('img').attr('src', getFilePath('Rem_helper.png'));
        $el.find('span').text('Rem Helper');
         $el.addClass('infected');
     }
     }
    function buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay) {
        const bar = document.createElement('div');
        bar.className = 'model-viewer-controls';


    function startInfection() {
        const playButton = document.createElement('button');
         if (isInfected) return;
         playButton.className = 'model-anim-button';
        playButton.type = 'button';


         isInfected = true;
         const select = document.createElement('select');
        select.className = 'model-anim-select';


         var msg = buddyMessages[Math.floor(Math.random() * buddyMessages.length)];
         const slider = document.createElement('input');
         mw.notify(msg, { type: 'success' });
        slider.className = 'model-anim-slider';
         slider.type = 'range';
        slider.min = '0';
        slider.max = '1000';
        slider.value = '0';
        slider.step = '1';


         playHelperSound();
         let paused = !autoplay;
        let rafId = null;
        let duration = 0;


         var $icons = $('.usericon-option');
         function updateSlider() {
        var remaining = $icons.toArray();
             if (duration > 0) {
 
                 slider.value = String(Math.round((viewer.currentTime / duration) * 1000));
        infectionInterval = setInterval(function () {
             if (remaining.length === 0) {
                 clearInterval(infectionInterval);
                return;
             }
             }
            rafId = requestAnimationFrame(updateSlider);
        }


             var count = Math.ceil(Math.random() * 3);
        function startLoop() {
            cancelAnimationFrame(rafId);
             rafId = requestAnimationFrame(updateSlider);
        }


            for (var i = 0; i < count && remaining.length > 0; i++) {
         function setPlaying(playing) {
                var index = Math.floor(Math.random() * remaining.length);
             paused = !playing;
                var el = remaining.splice(index, 1)[0];
             playButton.textContent = playing ? '❚❚' : '';
                infectIcon($(el));
             if (playing) {
            }
                 viewer.play();
         }, 400);
    }
 
    function createIconSelector() {
        var currentIcon = getCachedIcon() || 'rem';
 
        var html = '<div id="usericon-selector">' +
            '<h3>Choose Your Profile Icon</h3>' +
             '<div class="usericon-grid">';
 
        availableIcons.forEach(function (icon) {
             var selected = icon.id === currentIcon ? ' selected' : '';
            var imgSrc = getFilePath(icon.name + '.png');
 
            html += '<div class="usericon-option' + selected + '" data-icon="' + icon.id + '">' +
                '<img src="' + imgSrc + '">' +
                '<span>' + icon.name + '</span>' +
                '</div>';
        });
 
        html += '</div></div>';
 
        var $selector = $(html);
        $('#mw-content-text').prepend($selector);
 
        $('.usericon-option').on('click', function () {
            var iconId = $(this).data('icon');
 
             if (isInfected) {
                 iconId = 'rem-helper';
 
                var msg = buddyMessages[Math.floor(Math.random() * buddyMessages.length)];
                mw.notify(msg, { type: 'success' });
            }
 
            if (iconId === 'rem' && !isInfected) {
                remClickCount++;
 
                clearTimeout(remClickTimer);
                remClickTimer = setTimeout(function () {
                    remClickCount = 0;
                }, 1500);
 
                if (remClickCount >= 5) {
                    remClickCount = 0;
                    startInfection();
                    return;
                }
             } else {
             } else {
                 remClickCount = 0;
                 viewer.pause();
             }
             }
            $('.usericon-option').removeClass('selected');
            $(this).addClass('selected');
            selectIcon(iconId);
        });
    }
    mw.loader.using(['mediawiki.util'], function () {
        applyIcon(getCachedIcon() || 'rem');
        var config = mw.config.get([
            'wgNamespaceNumber',
            'wgTitle',
            'wgUserName'
        ]);
        if (
            config.wgNamespaceNumber === 2 &&
            config.wgTitle === config.wgUserName &&
            config.wgUserName
        ) {
            createIconSelector();
         }
         }
    });


})();
         playButton.addEventListener('click', function () {
 
             setPlaying(paused);
 
/**
* Rotatable Model Viewer — with 2D / 3D toggle
* All <img> elements created in JS to bypass MediaWiki sanitizer.
*/
(function () {
    'use strict';
 
    function makeImg(src, alt, className) {
        var i = new Image();
        i.src = src;
        i.alt = alt || '';
        i.draggable = false;
        if (className) i.className = className;
        return i;
    }
 
    function boot(el) {
        if (el.dataset.rmReady) return;
        el.dataset.rmReady = '1';
 
        /* ---- config ---- */
        var imageUrl    = el.dataset.image        || '',
            image2dUrl  = el.dataset.imageFlat    || '',
            icon2dUrl  = el.dataset.iconFlat    || '',
            icon3dUrl  = el.dataset.iconStereo  || '',
            iconRotUrl  = el.dataset.iconRotate  || '',
            fw          = +el.dataset.frameWidth  || 300,
            fh          = +el.dataset.frameHeight || 400,
            count      = +el.dataset.frameCount  || 24,
            startFrame  = +el.dataset.startFrame  || 0,
            sensitivity = +el.dataset.sensitivity || 5;
 
        /* ---- 1:1 square for 3D strip ---- */
        var sq = Math.min(fw, fh);
 
        var hint = el.querySelector('.rotatable-model-hint'),
            load = el.querySelector('.rotatable-model-loading'),
            wrap = el.closest('.rotatable-model-wrapper');
 
        if (!imageUrl) {
            if (load) load.textContent = 'Error: no image URL';
            return;
        }
 
        /* ---- state ---- */
        var cur    = startFrame,
            is2D  = false,
            dragOK = true,
            ready  = false;
 
        /* ============================================================
          SQUARE VIEWPORT
          ============================================================ */
        var viewport = document.createElement('div');
         viewport.className = 'rm-square-viewport';
        viewport.style.position = 'absolute';
        viewport.style.width    = sq + 'px';
        viewport.style.height  = sq + 'px';
        viewport.style.left    = ((fw - sq) / 2) + 'px';
        viewport.style.top      = ((fh - sq) / 2) + 'px';
        viewport.style.overflow = 'hidden';
 
        el.insertBefore(viewport, el.firstChild);
 
        /* ============================================================
          3D SPRITE STRIP — each frame is sq × sq
          ============================================================ */
        var img = makeImg(imageUrl, '', 'rm-strip');
 
        img.style.position = 'absolute';
        img.style.height  = sq + 'px';
        img.style.width    = (sq * count) + 'px';
        img.style.top      = '0';
        img.style.left    = '0';
 
        img.addEventListener('load', function () {
             ready = true;
            if (load) load.style.display = 'none';
            show(startFrame);
         });
         });
        img.addEventListener('error', function () {
            if (load) { load.textContent = 'Failed to load image'; load.style.color = '#d33'; }
        });
        viewport.appendChild(img);
        if (img.complete && img.naturalWidth > 0) {
            ready = true;
            if (load) load.style.display = 'none';
            show(startFrame);
        }
        /* ============================================================
          2D STATIC IMAGE — renders at full fw × fh
          ============================================================ */
        var staticImg = null;
        if (image2dUrl) {
            staticImg = makeImg(image2dUrl, '', 'rm-static');
            el.insertBefore(staticImg, el.firstChild);
        }
        /* ============================================================
          MODE TOGGLE — single button, swaps icon on click
          ============================================================ */
        var toggleBtn = null,
            iconImg2d = null,
            iconImg3d = null;
        if (image2dUrl) {
            toggleBtn = document.createElement('span');
            toggleBtn.className = 'rm-mode-toggle';
            toggleBtn.setAttribute('role', 'button');
            toggleBtn.setAttribute('tabindex', '0');
            toggleBtn.title = 'Switch to 2D';


            if (icon2dUrl) {
        slider.addEventListener('input', function () {
                iconImg2d = makeImg(icon2dUrl, '2D', 'rm-mode-icon rm-icon-2d');
             if (duration > 0) {
            }
                 viewer.currentTime = (parseFloat(slider.value) / 1000) * duration;
             if (icon3dUrl) {
                 iconImg3d = makeImg(icon3dUrl, '3D', 'rm-mode-icon rm-icon-3d');
                iconImg3d.style.display = 'none';
             }
             }
        });


            if (iconImg2d) toggleBtn.appendChild(iconImg2d);
        viewer.addEventListener('load', function () {
             if (iconImg3d) toggleBtn.appendChild(iconImg3d);
             const animations = viewer.availableAnimations || [];


             if (!icon2dUrl && !icon3dUrl) {
             if (animations.length) {
                 toggleBtn.textContent = '2D';
                 animations.forEach(function (animName) {
            }
                    const option = document.createElement('option');
                    option.value = animName;
                    option.textContent = animName;
                    select.appendChild(option);
                });


            el.appendChild(toggleBtn);
                if (defaultAnimation && animations.indexOf(defaultAnimation) !== -1) {
        }
                     select.value = defaultAnimation;
 
        /* ============================================================
          ROTATION ICON  (bottom-right)
          ============================================================ */
        if (iconRotUrl) {
            var rotDiv = document.createElement('div');
            rotDiv.className = 'rm-rotate-icon';
            rotDiv.appendChild(makeImg(iconRotUrl, 'Drag to rotate', ''));
            el.appendChild(rotDiv);
        }
 
        /* ============================================================
          FRAME DISPLAY
          ============================================================ */
        function show(idx) {
            cur = ((idx % count) + count) % count;
            img.style.left = -(cur * sq) + 'px';
        }
 
        function nudge(d) {
            if (!is2D && ready) {
                show(cur + d);
                hideHint();
            }
        }
 
        function reset() { show(startFrame); }
 
        function hideHint() {
            if (hint) hint.classList.add('rm-hidden');
        }
 
        /* ============================================================
          2D ↔ 3D SWITCHING
          ============================================================ */
        function setMode(mode) {
            is2D = (mode === '2d');
 
            if (is2D) {
                el.classList.add('rm-is-2d');
                if (wrap) wrap.classList.add('rm-is-2d');
                dragOK = false;
                viewport.style.display = 'none';
            } else {
                el.classList.remove('rm-is-2d');
                if (wrap) wrap.classList.remove('rm-is-2d');
                dragOK = true;
                viewport.style.display = '';
            }
 
            if (iconImg2d && iconImg3d) {
                iconImg2d.style.display = is2D ? 'none' : '';
                iconImg3d.style.display = is2D ? '' : 'none';
            }
 
            if (toggleBtn) {
                toggleBtn.title = is2D ? 'Switch to 3D' : 'Switch to 2D';
                if (!icon2dUrl && !icon3dUrl) {
                     toggleBtn.textContent = is2D ? '3D' : '2D';
                 }
                 }
            }
        }


        if (toggleBtn) {
                viewer.animationName = select.value;
            toggleBtn.addEventListener('click', function () {
                 duration = viewer.duration || 0;
                 setMode(is2D ? '3d' : '2d');
            });
        }


        /* ============================================================
                select.addEventListener('change', function () {
          DRAG  (mouse + touch)
                    viewer.animationName = select.value;
          ============================================================ */
                    duration = viewer.duration || 0;
        function startDrag(x0) {
                    viewer.currentTime = 0;
            if (!dragOK || !ready) return;
                    if (paused) {
            var acc = 0;
                        setPlaying(true);
            hideHint();
                    }
                });


            function move(cx) {
                 if (paused) {
                var dx = cx - x0;
                     setPlaying(false);
                x0 = cx;
                acc += dx;
 
                while (acc >= sensitivity) {
                    acc -= sensitivity;
                    show(cur + 1);
                }
                 while (acc <= -sensitivity) {
                     acc += sensitivity;
                    show(cur - 1);
                 }
                 }
            }
            function onMM(e) { move(e.clientX); e.preventDefault(); }
            function onTM(e) {
                if (e.touches.length === 1) { move(e.touches[0].clientX); e.preventDefault(); }
            }
            function stop() {
                el.classList.remove('rm-dragging');
                document.removeEventListener('mousemove',  onMM);
                document.removeEventListener('mouseup',    stop);
                document.removeEventListener('touchmove',  onTM);
                document.removeEventListener('touchend',    stop);
                document.removeEventListener('touchcancel', stop);
            }
            el.classList.add('rm-dragging');
            document.addEventListener('mousemove',  onMM);
            document.addEventListener('mouseup',    stop);
            document.addEventListener('touchmove',  onTM, { passive: false });
            document.addEventListener('touchend',    stop);
            document.addEventListener('touchcancel', stop);
        }


        el.addEventListener('mousedown', function (e) {
                startLoop();
            if (e.target.closest('.rm-mode-toggle')) return;
                bar.classList.add('model-viewer-controls--visible');
            if (e.button === 0 && dragOK) { startDrag(e.clientX); e.preventDefault(); }
        });
        el.addEventListener('touchstart', function (e) {
            if (e.target.closest('.rm-mode-toggle')) return;
            if (e.touches.length === 1 && dragOK) {
                startDrag(e.touches[0].clientX); e.preventDefault();
             }
             }
        }, { passive: false });
        /* ---- wheel ---- */
        el.addEventListener('wheel', function (e) {
            if (!dragOK || !ready) return;
            var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
            if (d) { nudge(d); e.preventDefault(); }
        }, { passive: false });
        /* ---- keyboard ---- */
        el.setAttribute('tabindex', '0');
        el.addEventListener('keydown', function (e) {
            if (is2D || !ready) return;
            if (e.key === 'ArrowLeft')  { nudge(-1); e.preventDefault(); }
            if (e.key === 'ArrowRight') { nudge( 1); e.preventDefault(); }
            if (e.key === 'Home')      { reset();  e.preventDefault(); }
         });
         });


         /* ---- nav buttons ---- */
         bar.appendChild(playButton);
        if (wrap) {
        bar.appendChild(select);
            var bL = wrap.querySelector('.rm-btn-left'),
        bar.appendChild(slider);
                bR = wrap.querySelector('.rm-btn-right'),
        container.appendChild(bar);
                bO = wrap.querySelector('.rm-btn-reset');
            if (bL) bL.addEventListener('click', function () { nudge(-1); });
            if (bR) bR.addEventListener('click', function () { nudge( 1); });
            if (bO) bO.addEventListener('click', reset);
        }
 
        /* ---- initial frame ---- */
        show(startFrame);
     }
     }


    /* ---- scan & init ---- */
    function initAll(root) {
        var els = (root || document).querySelectorAll('.rotatable-model');
        for (var i = 0; i < els.length; i++) boot(els[i]);
    }


    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function () { initAll(); });
    } else {
        initAll();
    }


     if (typeof mw !== 'undefined' && mw.hook) {
     function loadModel(preview) {
         mw.hook('wikipage.content').add(function ($c) { initAll($c[0]); });
         if (preview.dataset.loaded) {
    }
            return;
}());
        }


/**
        preview.dataset.loaded = '1';
* Custom Tooltips — replaces Extension:Popups for .item-link-wrapper and .ability-link-wrapper links
*/
( function () {
    'use strict';


    var tooltipCache = {};
        const fileName = preview.dataset.model;
    var $tooltip = null;
        const displayName = preview.dataset.name || fileName;
    var currentTarget = null;
        const defaultAnimation = preview.dataset.animation || '';
    var showTimer = null;
        const autoplay = preview.dataset.autoplay === 'true';
    var hideTimer = null;
        const size = getSize(preview);


    var SHOW_DELAY = 250;
        getFileUrl(fileName + '.glb')
    var HIDE_DELAY = 300;
            .then(function (url) {
    var EDGE_MARGIN = 10; // margin from viewport edges
                if (!url) {
                    return;
                }


    /* ─── Create tooltip DOM element ─── */
                const wrap = document.createElement('div');
    function getTooltip() {
                 wrap.className = 'model-viewer-wrap';
        if ( !$tooltip ) {
            $tooltip = $( '<div>' )
                 .addClass( 'custom-item-tooltip' )
                .hide()
                .appendTo( document.body )
                .on( 'mouseenter', function () {
                    clearTimeout( hideTimer );
                } )
                .on( 'mouseleave', function () {
                    scheduleHide();
                } );
        }
        return $tooltip;
    }


    /* ─── Positioning ─── */
                const container = document.createElement('div');
    function positionTooltip( anchor ) {
                container.className = 'model-viewer-container';
        var tt = getTooltip();
                container.style.width = size + 'px';
        var rect = anchor.getBoundingClientRect();
                container.style.height = size + 'px';
        var scrollTop = window.scrollY || document.documentElement.scrollTop;
        var scrollLeft = window.scrollX || document.documentElement.scrollLeft;
        var vpW = window.innerWidth;
        var vpH = window.innerHeight;


        // Render offscreen to measure actual dimensions
                const viewer = document.createElement('model-viewer');
        tt.css( { top: -9999, left: -9999, visibility: 'hidden' } ).show();
                viewer.src = url;
        var ttW = tt.outerWidth();
                viewer.setAttribute('camera-controls', '');
        var ttH = tt.outerHeight();
                viewer.setAttribute('touch-action', 'pan-y');
        tt.css( 'visibility', '' );
                if (preview.dataset.autoplay === 'true') {
                    viewer.setAttribute('autoplay', '');
                }


        // ─── Vertical positioning ───
                container.appendChild(viewer);
        var top;
                wrap.appendChild(container);
        var spaceBelow = vpH - rect.bottom - 8;
        var spaceAbove = rect.top - 8;


        if ( spaceBelow >= ttH + EDGE_MARGIN ) {
                buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay);
            // Fits below the anchor
            top = rect.bottom + scrollTop + 8;
        } else if ( spaceAbove >= ttH + EDGE_MARGIN ) {
            // Fits above the anchor
            top = rect.top + scrollTop - ttH - 8;
        } else {
            // Doesn't fit above or below — pin to the edge with more space
            if ( spaceBelow >= spaceAbove ) {
                top = scrollTop + vpH - ttH - EDGE_MARGIN;
            } else {
                top = scrollTop + EDGE_MARGIN;
            }
        }


        // ─── Horizontal positioning ───
                const name = document.createElement('div');
        var left = rect.left + scrollLeft;
                name.className = 'model-name';
                name.textContent = displayName;


        // Overflows right edge
                wrap.appendChild(name);
        if ( left + ttW > scrollLeft + vpW - EDGE_MARGIN ) {
            left = scrollLeft + vpW - ttW - EDGE_MARGIN;
        }


        // Overflows left edge
                preview.replaceWith(wrap);
        if ( left < scrollLeft + EDGE_MARGIN ) {
             });
             left = scrollLeft + EDGE_MARGIN;
        }
 
        tt.css( { top: top, left: left } );
     }
     }


/* ability upgrade gadget CURRENTLY DOESN'T WORK WITH TOOLTIP*/
     function initPreview(preview) {
    /* ─── Initialize ability upgrade gadget for tooltip content ─── */
         if (preview.dataset.initialized) {
     function initAbilityUpgrades( $container ) {
        // Check if the gadget exists
         if ( !window.abilityUpgradeGadget ) {
             return;
             return;
         }
         }


         var abilityCards = $container.find( '.ability-card' );
         preview.dataset.initialized = '1';
         if ( !abilityCards.length ) {
 
             return;
        const button = document.createElement('button');
         }
        button.className = 'model-load-button';
        button.type = 'button';
         button.addEventListener('click', function () {
             loadModel(preview);
         });


         // Re-run the gadget's initialization for new ability cards
         preview.appendChild(button);
        abilityCards.each( function () {
            var container = this;
           
            // Add hover listeners
            $( container ).find( '.ability-upgrade' ).each( function () {
                var el = this;
                $( el ).off( 'mouseenter.abilityUpgrade mouseleave.abilityUpgrade' );
                $( el ).on( 'mouseenter.abilityUpgrade', function () {
                    window.abilityUpgradeGadget.onHover( container, el );
                } );
                $( el ).on( 'mouseleave.abilityUpgrade', function () {
                    window.abilityUpgradeGadget.onUnhover( container );
                } );
            } );
        } );
     }
     }


    /* ─── Make ability name clickable ─── */
     function scan() {
     function makeAbilityNameClickable( $container, abilityName ) {
         document
         // Find the ability name text within the ability card
            .querySelectorAll('.model-placeholder')
        $container.find( '.ability-card' ).each( function () {
             .forEach(initPreview);
            var $card = $( this );
           
            // Target the ability name span (based on your HTML structure)
            var $nameSpan = $card.find( 'span[style*="font-family"][style*="Valve Occult"]' );
           
             if ( $nameSpan.length ) {
                var nameText = $nameSpan.text().trim();
               
                // Create a link wrapping the ability name
                var $link = $( '<a>' )
                    .attr( 'href', mw.util.getUrl( abilityName ) )
                    .attr( 'title', 'Go to ' + abilityName + ' page' )
                    .css( {
                        'color': 'inherit',
                        'text-decoration': 'none'
                    } )
                    .on( 'mouseenter', function () {
                        $( this ).css( 'text-decoration', 'underline' );
                    } )
                    .on( 'mouseleave', function () {
                        $( this ).css( 'text-decoration', 'none' );
                    } )
                    .text( nameText );
               
                // Replace the text with the link
                $nameSpan.empty().append( $link );
            }
        } );
     }
     }


     /* ─── Show tooltip ─── */
     let scanTimer = null;
    function showTooltip( anchor, name, type ) {
        clearTimeout( hideTimer );
        clearTimeout( showTimer );


        showTimer = setTimeout( function () {
    function debouncedScan() {
            var tt = getTooltip();
        clearTimeout(scanTimer);
            currentTarget = anchor;
        scanTimer = setTimeout(scan, 100);
            var cacheKey = type + ':' + name;
 
            // Update tooltip classes based on type
            tt.removeClass( 'ability-tooltip item-tooltip' );
            if ( type === 'ability' ) {
                tt.addClass( 'ability-tooltip' );
            } else if ( type === 'item' ) {
                tt.addClass( 'item-tooltip' );
            }
 
            // Already cached — show immediately
            if ( tooltipCache[ cacheKey ] ) {
                tt.html( tooltipCache[ cacheKey ] ).show();
                positionTooltip( anchor );
               
                // Re-initialize ability upgrades for cached content
                if ( type === 'ability' ) {
                    initAbilityUpgrades( tt );
                    makeAbilityNameClickable( tt, name );
                }
                return;
            }
 
            // Loading indicator
            tt.html( '<div class="custom-item-tooltip__loading">Loading…</div>' ).show();
            positionTooltip( anchor );
 
            // Build template text based on type
            var templateText;
            if ( type === 'item' ) {
                templateText = '{{Infobox item/auto|default=true|' + name + '}}';
            } else if ( type === 'ability' ) {
                templateText = '{{Ability card v2' +
                    '|hero_name={{#invoke:Abilities/utils|find_hero_by_ability|' + name + '}}' +
                    '|ability_num={{#invoke:Abilities|get_ability_number|{{#invoke:Abilities/utils|find_hero_by_ability|' + name + '}}|' + name + '}}' +
                    '}}';
            }
 
            // Fetch rendered template
            new mw.Api().post( {
                action: 'parse',
                text: templateText,
                title: mw.config.get( 'wgPageName' ),
                prop: 'text',
                disablelimitreport: true,
                disableeditsection: true,
                wrapoutputclass: '',
                format: 'json'
            } ).then( function ( data ) {
                var html = data.parse.text[ '*' ];
                tooltipCache[ cacheKey ] = html;
 
                // Only show if the mouse is still on this element
                if ( currentTarget === anchor ) {
                    tt.html( html ).show();
                    positionTooltip( anchor );
                   
                    // Initialize ability upgrades for new content
                    if ( type === 'ability' ) {
                        // Trigger MediaWiki's content hook so other gadgets can initialize
                        mw.hook( 'wikipage.content' ).fire( tt );
                        initAbilityUpgrades( tt );
                        makeAbilityNameClickable( tt, name );
                    }
                }
            } ).catch( function () {
                if ( currentTarget === anchor ) {
                    tt.hide();
                }
            } );
 
        }, SHOW_DELAY );
     }
     }


     /* ─── Hide tooltip ─── */
     scan();
    function scheduleHide() {
        clearTimeout( showTimer );
        hideTimer = setTimeout( function () {
            if ( $tooltip ) {
                $tooltip.hide();
            }
            currentTarget = null;
        }, HIDE_DELAY );
    }


     /* ─── Attach handlers to wrapper ─── */
     const observer = new MutationObserver(debouncedScan);
     function attachTooltipHandlers( $wrapper, name, type ) {
     observer.observe(document.body, {
         // All links inside the wrapper, not just the first one
         childList: true,
         var $links = $wrapper.find( 'a[href]' );
         subtree: true
       
    });
        // If no links found, attach to the wrapper itself (for icon-only mode)
        if ( !$links.length ) {
            $links = $wrapper;
        }


        $links.each( function () {
})();
            var $link = $( this );
 
            // Remove native title from each link
            $link.removeAttr( 'title' );
 
            $link.on( 'mouseenter.customTooltip', function () {
                showTooltip( this, name, type );
            } ).on( 'mouseleave.customTooltip', function () {
                scheduleHide();
            } );
        } );
    }


    /* ─── Initialization ─── */
    mw.hook( 'wikipage.content' ).add( function ( $content ) {
        // Item tooltips
        $content.find( '.item-link-wrapper' ).each( function () {
            var $wrapper = $( this );
            var itemName = $wrapper.attr( 'data-item-name' );
            if ( !itemName ) return;


            attachTooltipHandlers( $wrapper, itemName, 'item' );
        } );


        // Ability tooltips
        $content.find( '.ability-link-wrapper' ).each( function () {
            var $wrapper = $( this );
            var abilityName = $wrapper.attr( 'data-ability-name' );
            if ( !abilityName ) return;


            attachTooltipHandlers( $wrapper, abilityName, 'ability' );
        } );
    } );


} )();
const personal = document.querySelector(".vector-menu-content-list");
if (personal){
const test2 = document.createElement("li");
const test3 = document.createElement("li");
const test4 = document.createElement("li");
test2.innerHTML = `<a href="/User:Monster_Domosed/common.js">JS</a>`;
test3.innerHTML = `<a href="/User:Monster_Domosed/common.css">CSS</a>`;
test4.innerHTML = `<a href="/User:Monster_Domosed/Sandbox">SB</a>`;
personal.prepend(test2, test3, test4);
}

Latest revision as of 00:56, 15 September 2026

(function () {
    'use strict';

    const urlCache = new Map();

    function getSize(element) {
        const match = element.className.match(/model_size_(\d+)/);
        return match ? parseInt(match[1], 10) : 500;
    }

    function getFileUrl(fileName) {
        if (urlCache.has(fileName)) {
            return urlCache.get(fileName);
        }

        const apiUrl =
            mw.config.get('wgScriptPath') +
            '/api.php?action=query' +
            '&titles=File:' + encodeURIComponent(fileName) +
            '&prop=imageinfo' +
            '&iiprop=url' +
            '&format=json';

        const promise = fetch(apiUrl)
            .then(function (response) {
                if (!response.ok) {
                    throw new Error('API request failed');
                }
                return response.json();
            })
            .then(function (data) {
                const page = Object.values(data.query.pages)[0];
                return page && page.imageinfo ? page.imageinfo[0].url : null;
            })
            .catch(function () {
                return null;
            });

        urlCache.set(fileName, promise);
        return promise;
    }
    function buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay) {
        const bar = document.createElement('div');
        bar.className = 'model-viewer-controls';

        const playButton = document.createElement('button');
        playButton.className = 'model-anim-button';
        playButton.type = 'button';

        const select = document.createElement('select');
        select.className = 'model-anim-select';

        const slider = document.createElement('input');
        slider.className = 'model-anim-slider';
        slider.type = 'range';
        slider.min = '0';
        slider.max = '1000';
        slider.value = '0';
        slider.step = '1';

        let paused = !autoplay;
        let rafId = null;
        let duration = 0;

        function updateSlider() {
            if (duration > 0) {
                slider.value = String(Math.round((viewer.currentTime / duration) * 1000));
            }
            rafId = requestAnimationFrame(updateSlider);
        }

        function startLoop() {
            cancelAnimationFrame(rafId);
            rafId = requestAnimationFrame(updateSlider);
        }

        function setPlaying(playing) {
            paused = !playing;
            playButton.textContent = playing ? '❚❚' : '▶';
            if (playing) {
                viewer.play();
            } else {
                viewer.pause();
            }
        }

        playButton.addEventListener('click', function () {
            setPlaying(paused);
        });

        slider.addEventListener('input', function () {
            if (duration > 0) {
                viewer.currentTime = (parseFloat(slider.value) / 1000) * duration;
            }
        });

        viewer.addEventListener('load', function () {
            const animations = viewer.availableAnimations || [];

            if (animations.length) {
                animations.forEach(function (animName) {
                    const option = document.createElement('option');
                    option.value = animName;
                    option.textContent = animName;
                    select.appendChild(option);
                });

                if (defaultAnimation && animations.indexOf(defaultAnimation) !== -1) {
                    select.value = defaultAnimation;
                }

                viewer.animationName = select.value;
                duration = viewer.duration || 0;

                select.addEventListener('change', function () {
                    viewer.animationName = select.value;
                    duration = viewer.duration || 0;
                    viewer.currentTime = 0;
                    if (paused) {
                        setPlaying(true);
                    }
                });

                if (paused) {
                    setPlaying(false);
                }

                startLoop();
                bar.classList.add('model-viewer-controls--visible');
            }
        });

        bar.appendChild(playButton);
        bar.appendChild(select);
        bar.appendChild(slider);
        container.appendChild(bar);
    }



    function loadModel(preview) {
        if (preview.dataset.loaded) {
            return;
        }

        preview.dataset.loaded = '1';

        const fileName = preview.dataset.model;
        const displayName = preview.dataset.name || fileName;
        const defaultAnimation = preview.dataset.animation || '';
        const autoplay = preview.dataset.autoplay === 'true';
        const size = getSize(preview);

        getFileUrl(fileName + '.glb')
            .then(function (url) {
                if (!url) {
                    return;
                }

                const wrap = document.createElement('div');
                wrap.className = 'model-viewer-wrap';

                const container = document.createElement('div');
                container.className = 'model-viewer-container';
                container.style.width = size + 'px';
                container.style.height = size + 'px';

                const viewer = document.createElement('model-viewer');
                viewer.src = url;
                viewer.setAttribute('camera-controls', '');
                viewer.setAttribute('touch-action', 'pan-y');
                if (preview.dataset.autoplay === 'true') {
                    viewer.setAttribute('autoplay', '');
                }

                container.appendChild(viewer);
                wrap.appendChild(container);

                buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay);

                const name = document.createElement('div');
                name.className = 'model-name';
                name.textContent = displayName;

                wrap.appendChild(name);

                preview.replaceWith(wrap);
            });
    }

    function initPreview(preview) {
        if (preview.dataset.initialized) {
            return;
        }

        preview.dataset.initialized = '1';

        const button = document.createElement('button');
        button.className = 'model-load-button';
        button.type = 'button';
        button.addEventListener('click', function () {
            loadModel(preview);
        });

        preview.appendChild(button);
    }

    function scan() {
        document
            .querySelectorAll('.model-placeholder')
            .forEach(initPreview);
    }

    let scanTimer = null;

    function debouncedScan() {
        clearTimeout(scanTimer);
        scanTimer = setTimeout(scan, 100);
    }

    scan();

    const observer = new MutationObserver(debouncedScan);
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();





const personal = document.querySelector(".vector-menu-content-list");
if (personal){
	const test2 = document.createElement("li");
	const test3 = document.createElement("li");
	const test4 = document.createElement("li");
	test2.innerHTML = `<a href="/User:Monster_Domosed/common.js">JS</a>`;
	test3.innerHTML = `<a href="/User:Monster_Domosed/common.css">CSS</a>`;
	test4.innerHTML = `<a href="/User:Monster_Domosed/Sandbox">SB</a>`;
	personal.prepend(test2, test3, test4);
}