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

From The Deadlock Wiki
Jump to navigation Jump to search
Scrapped HeroIcon hovers for now, added support for AbilityIcon for tooltips?
No edit summary
 
(234 intermediate revisions by the same user not shown)
Line 1: Line 1:
/**
* Rotatable Model Viewer — with 2D / 3D toggle
* All <img> elements created in JS to bypass MediaWiki sanitizer.
*/
(function () {
(function () {
     'use strict';
     'use strict';


     function makeImg(src, alt, className) {
     const urlCache = new Map();
        var i = new Image();
 
         i.src = src;
    function getSize(element) {
        i.alt = alt || '';
         const match = element.className.match(/model_size_(\d+)/);
        i.draggable = false;
         return match ? parseInt(match[1], 10) : 500;
        if (className) i.className = className;
         return i;
     }
     }


     function boot(el) {
     function getFileUrl(fileName) {
         if (el.dataset.rmReady) return;
         if (urlCache.has(fileName)) {
        el.dataset.rmReady = '1';
             return urlCache.get(fileName);
 
        /* ---- 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 ---- */
         const apiUrl =
        var cur    = startFrame,
            mw.config.get('wgScriptPath') +
             is2D  = false,
            '/api.php?action=query' +
             dragOK = true,
            '&titles=File:' + encodeURIComponent(fileName) +
             ready  = false;
             '&prop=imageinfo' +
             '&iiprop=url' +
             '&format=json';


         /* ============================================================
         const promise = fetch(apiUrl)
          SQUARE VIEWPORT
            .then(function (response) {
          ============================================================ */
                if (!response.ok) {
        var viewport = document.createElement('div');
                    throw new Error('API request failed');
        viewport.className = 'rm-square-viewport';
                }
        viewport.style.position = 'absolute';
                return response.json();
        viewport.style.width    = sq + 'px';
            })
        viewport.style.height  = sq + 'px';
            .then(function (data) {
        viewport.style.left    = ((fw - sq) / 2) + 'px';
                const page = Object.values(data.query.pages)[0];
        viewport.style.top      = ((fh - sq) / 2) + 'px';
                return page && page.imageinfo ? page.imageinfo[0].url : null;
        viewport.style.overflow = 'hidden';
            })
            .catch(function () {
                return null;
            });


         el.insertBefore(viewport, el.firstChild);
         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');
          3D SPRITE STRIP — each frame is sq × sq
        playButton.className = 'model-anim-button';
          ============================================================ */
         playButton.type = 'button';
         var img = makeImg(imageUrl, '', 'rm-strip');


         img.style.position = 'absolute';
         const select = document.createElement('select');
        img.style.height  = sq + 'px';
         select.className = 'model-anim-select';
        img.style.width    = (sq * count) + 'px';
        img.style.top      = '0';
         img.style.left    = '0';


         img.addEventListener('load', function () {
         const slider = document.createElement('input');
            ready = true;
        slider.className = 'model-anim-slider';
            if (load) load.style.display = 'none';
         slider.type = 'range';
            show(startFrame);
         slider.min = '0';
         });
        slider.max = '1000';
         img.addEventListener('error', function () {
        slider.value = '0';
            if (load) { load.textContent = 'Failed to load image'; load.style.color = '#d33'; }
         slider.step = '1';
         });


         viewport.appendChild(img);
         let paused = !autoplay;
        let rafId = null;
        let duration = 0;


         if (img.complete && img.naturalWidth > 0) {
         function updateSlider() {
            ready = true;
            if (duration > 0) {
            if (load) load.style.display = 'none';
                slider.value = String(Math.round((viewer.currentTime / duration) * 1000));
             show(startFrame);
             }
            rafId = requestAnimationFrame(updateSlider);
         }
         }


         /* ============================================================
         function startLoop() {
          2D STATIC IMAGE — renders at full fw × fh
             cancelAnimationFrame(rafId);
          ============================================================ */
             rafId = requestAnimationFrame(updateSlider);
        var staticImg = null;
        if (image2dUrl) {
             staticImg = makeImg(image2dUrl, '', 'rm-static');
             el.insertBefore(staticImg, el.firstChild);
         }
         }


         /* ============================================================
         function setPlaying(playing) {
          MODE TOGGLE — single button, swaps icon on click
             paused = !playing;
          ============================================================ */
             playButton.textContent = playing ? '❚❚' : '';
        var toggleBtn = null,
             if (playing) {
            iconImg2d = null,
                 viewer.play();
            iconImg3d = null;
             } else {
 
                 viewer.pause();
        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) {
                 iconImg2d = makeImg(icon2dUrl, '2D', 'rm-mode-icon rm-icon-2d');
             }
            if (icon3dUrl) {
                 iconImg3d = makeImg(icon3dUrl, '3D', 'rm-mode-icon rm-icon-3d');
                iconImg3d.style.display = 'none';
             }
             }
            if (iconImg2d) toggleBtn.appendChild(iconImg2d);
            if (iconImg3d) toggleBtn.appendChild(iconImg3d);
            if (!icon2dUrl && !icon3dUrl) {
                toggleBtn.textContent = '2D';
            }
            el.appendChild(toggleBtn);
         }
         }


         /* ============================================================
         playButton.addEventListener('click', function () {
          ROTATION ICON  (bottom-right)
             setPlaying(paused);
          ============================================================ */
        });
        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) {
         slider.addEventListener('input', function () {
             if (!is2D && ready) {
             if (duration > 0) {
                 show(cur + d);
                 viewer.currentTime = (parseFloat(slider.value) / 1000) * duration;
                hideHint();
             }
             }
         }
         });


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


        function hideHint() {
            if (animations.length) {
            if (hint) hint.classList.add('rm-hidden');
                animations.forEach(function (animName) {
        }
                    const option = document.createElement('option');
                    option.value = animName;
                    option.textContent = animName;
                    select.appendChild(option);
                });


        /* ============================================================
                 if (defaultAnimation && animations.indexOf(defaultAnimation) !== -1) {
          2D ↔ 3D SWITCHING
                     select.value = defaultAnimation;
          ============================================================ */
        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) {
            toggleBtn.addEventListener('click', function () {
                setMode(is2D ? '3d' : '2d');
            });
        }


        /* ============================================================
                viewer.animationName = select.value;
          DRAG  (mouse + touch)
                duration = viewer.duration || 0;
          ============================================================ */
        function startDrag(x0) {
            if (!dragOK || !ready) return;
            var acc = 0;
            hideHint();


            function move(cx) {
                select.addEventListener('change', function () {
                var dx = cx - x0;
                    viewer.animationName = select.value;
                x0 = cx;
                    duration = viewer.duration || 0;
                 acc += dx;
                    viewer.currentTime = 0;
                    if (paused) {
                        setPlaying(true);
                    }
                 });


                 while (acc >= sensitivity) {
                 if (paused) {
                     acc -= sensitivity;
                     setPlaying(false);
                    show(cur + 1);
                 }
                 }
                while (acc <= -sensitivity) {
                    acc += sensitivity;
                    show(cur - 1);
                }
            }


            function onMM(e) { move(e.clientX); e.preventDefault(); }
                 startLoop();
            function onTM(e) {
                 bar.classList.add('model-viewer-controls--visible');
                 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');
        bar.appendChild(playButton);
            document.addEventListener('mousemove',  onMM);
        bar.appendChild(select);
            document.addEventListener('mouseup',    stop);
        bar.appendChild(slider);
            document.addEventListener('touchmove',  onTM, { passive: false });
        container.appendChild(bar);
            document.addEventListener('touchend',    stop);
    }
            document.addEventListener('touchcancel', stop);
        }


        el.addEventListener('mousedown', function (e) {
            if (e.target.closest('.rm-mode-toggle')) return;
            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 ---- */
    function loadModel(preview) {
        el.setAttribute('tabindex', '0');
         if (preview.dataset.loaded) {
        el.addEventListener('keydown', function (e) {
             return;
            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 ---- */
         if (wrap) {
            var bL = wrap.querySelector('.rm-btn-left'),
                bR = wrap.querySelector('.rm-btn-right'),
                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 ---- */
         preview.dataset.loaded = '1';
        show(startFrame);
    }


    /* ---- scan & init ---- */
        const fileName = preview.dataset.model;
    function initAll(root) {
        const displayName = preview.dataset.name || fileName;
         var els = (root || document).querySelectorAll('.rotatable-model');
         const defaultAnimation = preview.dataset.animation || '';
         for (var i = 0; i < els.length; i++) boot(els[i]);
         const autoplay = preview.dataset.autoplay === 'true';
    }
        const size = getSize(preview);


    if (document.readyState === 'loading') {
        getFileUrl(fileName + '.glb')
        document.addEventListener('DOMContentLoaded', function () { initAll(); });
            .then(function (url) {
    } else {
                if (!url) {
        initAll();
                    return;
    }
                }


    if (typeof mw !== 'undefined' && mw.hook) {
                const wrap = document.createElement('div');
        mw.hook('wikipage.content').add(function ($c) { initAll($c[0]); });
                wrap.className = 'model-viewer-wrap';
    }
}());


/**
                const container = document.createElement('div');
* Custom Tooltips — replaces Extension:Popups for .item-link-wrapper and .ability-link-wrapper links
                container.className = 'model-viewer-container';
*/
                container.style.width = size + 'px';
( function () {
                container.style.height = size + 'px';
    'use strict';


    var tooltipCache = {};
                const viewer = document.createElement('model-viewer');
    var $tooltip = null;
                viewer.src = url;
    var currentTarget = null;
                viewer.setAttribute('camera-controls', '');
    var showTimer = null;
                viewer.setAttribute('touch-action', 'pan-y');
    var hideTimer = null;
                if (preview.dataset.autoplay === 'true') {
                    viewer.setAttribute('autoplay', '');
                }


    var SHOW_DELAY = 250;
                container.appendChild(viewer);
    var HIDE_DELAY = 300;
                wrap.appendChild(container);
    var EDGE_MARGIN = 10; // margin from viewport edges


    /* ─── Create tooltip DOM element ─── */
                 buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay);
    function getTooltip() {
        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 name = document.createElement('div');
    function positionTooltip( anchor ) {
                name.className = 'model-name';
        var tt = getTooltip();
                name.textContent = displayName;
        var rect = anchor.getBoundingClientRect();
        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
                wrap.appendChild(name);
        tt.css( { top: -9999, left: -9999, visibility: 'hidden' } ).show();
        var ttW = tt.outerWidth();
        var ttH = tt.outerHeight();
        tt.css( 'visibility', '' );


        // ─── Vertical positioning ───
                preview.replaceWith(wrap);
        var top;
            });
        var spaceBelow = vpH - rect.bottom - 8;
    }
        var spaceAbove = rect.top - 8;


        if ( spaceBelow >= ttH + EDGE_MARGIN ) {
    function initPreview(preview) {
            // Fits below the anchor
         if (preview.dataset.initialized) {
            top = rect.bottom + scrollTop + 8;
             return;
         } 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 ───
         preview.dataset.initialized = '1';
        var left = rect.left + scrollLeft;


         // Overflows right edge
         const button = document.createElement('button');
         if ( left + ttW > scrollLeft + vpW - EDGE_MARGIN ) {
        button.className = 'model-load-button';
             left = scrollLeft + vpW - ttW - EDGE_MARGIN;
         button.type = 'button';
         }
        button.addEventListener('click', function () {
             loadModel(preview);
         });


         // Overflows left edge
         preview.appendChild(button);
        if ( left < scrollLeft + EDGE_MARGIN ) {
    }
            left = scrollLeft + EDGE_MARGIN;
        }


         tt.css( { top: top, left: left } );
    function scan() {
         document
            .querySelectorAll('.model-placeholder')
            .forEach(initPreview);
     }
     }


     /* ─── Show tooltip ─── */
     let scanTimer = null;
    function showTooltip( anchor, name, type ) {
        clearTimeout( hideTimer );
        clearTimeout( showTimer );
 
        showTimer = setTimeout( function () {
            var tt = getTooltip();
            currentTarget = anchor;
            var cacheKey = type + ':' + name;
 
            // Already cached — show immediately
            if ( tooltipCache[ cacheKey ] ) {
                tt.html( tooltipCache[ cacheKey ] ).show();
                positionTooltip( anchor );
                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 );
                }
            } ).catch( function () {
                if ( currentTarget === anchor ) {
                    tt.hide();
                }
            } );
 
        }, SHOW_DELAY );
    }


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


     /* ─── Attach handlers to wrapper ─── */
     scan();
    function attachTooltipHandlers( $wrapper, name, type ) {
        // All links inside the wrapper, not just the first one
        var $links = $wrapper.find( 'a[href]' );
       
        // If no links found, attach to the wrapper itself (for icon-only mode)
        if ( !$links.length ) {
            $links = $wrapper;
        }


        $links.each( function () {
    const observer = new MutationObserver(debouncedScan);
            var $link = $( this );
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });


            // 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);
}