Jump to content

MediaWiki:Common.js: Difference between revisions

From Detective Conan Wiki
No edit summary
Removed event listener since it was included previously via jQuery
 
(One intermediate revision by the same user not shown)
Line 441: Line 441:
   } // end of adjustTooltipPositions()
   } // end of adjustTooltipPositions()


  // code for Template:SidescrollSlides
  /**
  * Sidescroll Slides
  * Automatic element (a.k.a. slide) sidescrolling with interactive buttons
  * @version 1.0.0 (2025-08-22)
  * @author [[User:AvidConanContributor]] & [[User:R0o0di]]
  */
  function sidescrollScripts() {
    var sidescrolls = document.querySelectorAll(".sidescroll-container");
    for (var i = 0; i < sidescrolls.length; i++) {
      (function (i) {
        var sidescroll = sidescrolls[i].querySelector(".sidescroll"); if (!sidescroll) return;
        var slides = sidescroll.querySelectorAll(".sidescroll-slide");
        var totalSlides = slides.length;
       
        var leftButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-chevron-left)");
        var pausePlayButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-pause-play)");
        var rightButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-chevron-right)");
       
        var slideMinWidth = parseFloat(getComputedStyle(sidescroll).getPropertyValue("--slide-min-width"));
        var visibleSlideCount = null;
       
        var slidePos = 0;
       
        function updateSlideWidths() {
          var sidescrollWidth = sidescroll.getBoundingClientRect().width;
          visibleSlideCount = Math.max(1, Math.min(Math.floor(sidescrollWidth / slideMinWidth), totalSlides));
         
          var slideWidth = sidescrollWidth / visibleSlideCount; // sets the width of slides
         
          sidescroll.style.setProperty("--slide-width", slideWidth + "px");
        }
       
        function cloneSlides() {
          for (var j = 0; j < totalSlides; j++) {
            var slideClone = slides[j].cloneNode(true);
            sidescroll.appendChild(slideClone);
            slideClone.setAttribute("data-slide-clone", "true");
          }
        }
       
        function clampSlidePos() {
          void sidescroll.offsetWidth;
         
          sidescroll.style.setProperty("transition-duration", "0s");
         
          if (slidePos === 0) {
            sidescroll.style.setProperty("--slide-pos", slidePos = totalSlides);
          } else if (slidePos > totalSlides) {
            sidescroll.style.setProperty("--slide-pos", slidePos -= totalSlides);
          }
         
          void sidescroll.offsetWidth;
        }
       
        var canScroll = true;
        var pausePending = false;
        function sidescrollMove(dir) {
          if (!canScroll) return;
         
          canScroll = false;
         
          dir === "left" ? slidePos-- : slidePos++;
          sidescroll.style.removeProperty("transition-duration");
          sidescroll.style.setProperty("--slide-pos", slidePos);
         
          sidescroll.addEventListener("transitionend", function handler() {
            clampSlidePos();
            sidescroll.removeEventListener("transitionend", handler);
           
            setTimeout(function advanceCooldown() {
              canScroll = true;
              if (pausePending) {
                pausePending = false;
                pauseAutoAdvance();
              }
            }, 1);
          });
        }
       
        var progressBarFillDur = parseFloat(getComputedStyle(sidescroll).getPropertyValue("--progress-bar-fill-duration")) * 1000;
        var autoAdvanceTimer = null;
        var progressBarBg = sidescrolls[i].querySelector(".sidescroll-progress-bar-background");
        var progressBar = progressBarBg.querySelector(".sidescroll-progress-bar");
        var pausePlayIcon = pausePlayButton ? pausePlayButton.querySelector(".sidescroll-button-pause-play > div") : null;
       
        var isPaused = false;
        var timerStartTime = null;    // when current countdown began
        var remainingTime = null;      // ms left when paused
        var progressBarStartWidth = 0; // % width when paused
 
        var progressBarResetting = false;
        function resetProgressBar() {
          if (!progressBar) return;
 
          progressBarResetting = true;
 
          progressBar.style.removeProperty("transition");
          progressBar.style.setProperty("width", "0%");
 
          void progressBar.offsetWidth;
 
          progressBar.addEventListener("transitionend", function handler(event) {
            if (event.propertyName !== "width") return;
 
            progressBar.removeEventListener("transitionend", handler);
            progressBarResetting = false;
 
            if (isPaused) {
              progressBar.style.setProperty("transition", "none");
              progressBar.style.setProperty("width", "0%");
              return;
            }
 
            progressBar.style.setProperty(
              "transition",
              "width var(--auto-advance-delay) linear"
            );
            progressBar.style.setProperty("width", "100%");
          });
        }
 
        function freezeProgressBar() {
          if (!progressBar) return;
 
          if (progressBarResetting) {
            progressBarColors("onAndPaused");
            return;
          }
 
          var computed = parseFloat(getComputedStyle(progressBar).width);
          var bgWidth = parseFloat(getComputedStyle(progressBar.parentNode).width);
 
          progressBarStartWidth = bgWidth > 0
            ? (computed / bgWidth) * 100
            : 0;
 
          progressBar.style.setProperty("transition", "none");
          progressBar.style.setProperty("width", progressBarStartWidth + "%");
 
          progressBarColors("onAndPaused");
        }
 
        function resumeProgressBar() {
          if (!progressBar) return;
         
          var pct = progressBarStartWidth;
          var durSec = remainingTime / 1000;
          progressBar.style.setProperty("transition", "none");
          progressBar.style.setProperty("width", pct + "%");
          void progressBar.offsetWidth;
         
          progressBar.style.setProperty("transition", "width " + durSec + "s linear");
          progressBar.style.setProperty("width", "100%");
         
          progressBarColors("on");
        }
       
        function resetAutoAdvance() {
          if (isPaused) return;
         
          if (autoAdvanceTimer) {
            clearTimeout(autoAdvanceTimer);
          }
         
          remainingTime = progressBarFillDur;
          timerStartTime = Date.now();
 
          autoAdvanceTimer = setTimeout(function () {
            sidescrollMove("right");
            resetAutoAdvance();
          }, remainingTime);
         
          resetProgressBar();
        }
 
        function pauseAutoAdvance() {
          if (!canScroll) {
            pausePending = true;
            return;
          }
         
          if (isPaused) return;
          isPaused = true;
          var elapsed = Date.now() - timerStartTime;
          remainingTime = Math.max(0, remainingTime - elapsed);
          clearTimeout(autoAdvanceTimer);
          autoAdvanceTimer = null;
          freezeProgressBar();
          setPlayIcon();
        }
 
        function resumeAutoAdvance() {
          if (!isPaused) return;
          isPaused = false;
          timerStartTime = Date.now();
          autoAdvanceTimer = setTimeout(function () {
            sidescrollMove("right");
            resetAutoAdvance();
          }, remainingTime);
          resumeProgressBar();
          setPauseIcon();
        }
 
        function setPauseIcon() {
          if (!pausePlayIcon) return;
          pausePlayIcon.className = "sidescroll-button-pause-icon";
        }
 
        function setPlayIcon() {
          if (!pausePlayIcon) return;
          pausePlayIcon.className = "sidescroll-button-play-icon";
        }
       
        function whenAllSlidesFit() {
          if (visibleSlideCount === totalSlides) {
            sidescroll.style.setProperty("--slide-pos", slidePos = 0);
            sidescrolls[i].classList.remove("sidescroll-container-can-scroll");
           
            pauseAutoAdvance();
 
            progressBar.style.setProperty("width", 0);
            progressBarColors("off");         
          } else {
            sidescrolls[i].classList.add("sidescroll-container-can-scroll");
            progressBarColors(isPaused ? "onAndPaused" : "on");
          }
        }
       
        function progressBarColors(state) {
          if (state === "onAndPaused") {
            progressBar.style.removeProperty("visibility");
            progressBar.style.setProperty("background", "var(--btn-and-progress-bar-color)");
            progressBarBg.style.setProperty("background", "var(--btn-and-progress-bar-color)");
          } else if (state === "off") {
            progressBar.style.setProperty("visibility", "hidden");
            progressBarBg.style.setProperty("background", "var(--progress-bar-background)");
          } else {
            progressBar.style.removeProperty("visibility");
            progressBarBg.style.setProperty("background", "var(--progress-bar-background)");
          }
        }
 
        // Function calls
        updateSlideWidths();
        cloneSlides();
        clampSlidePos();
        resetAutoAdvance();
        whenAllSlidesFit();
       
        if (leftButton) {
          leftButton.addEventListener("click", function () {
            sidescrollMove("left");
            if (!isPaused) {
              resetAutoAdvance();
            }
          });
        }
       
        if (rightButton) {
          rightButton.addEventListener("click", function () {
            sidescrollMove("right");
            if (!isPaused) {
              resetAutoAdvance();
            }
          });
        }
 
        if (pausePlayButton) {
          pausePlayButton.addEventListener("click", function () {
            if (isPaused) {
              resumeAutoAdvance();
            } else {
              pauseAutoAdvance();
            }
          });
        }
       
        window.addEventListener("resize", function () {
          updateSlideWidths();
          clampSlidePos();
          whenAllSlidesFit();
        });
      })(i);
    }
  }
  sidescrollScripts();
}); // end of $(function () )
}); // end of $(function () )

Latest revision as of 06:02, 23 August 2026

/**
 * Keep code in MediaWiki:Common.js to a minimum as it is unconditionally
 * loaded for all users on every wiki page. If possible create a gadget that is
 * enabled by default instead of adding it here (since gadgets are fully
 * optimized ResourceLoader modules with possibility to add dependencies etc.)
 *
 * Since Common.js isn't a gadget, there is no place to declare its
 * dependencies, so we have to lazy load them with mw.loader.using on demand and
 * then execute the rest in the callback. In most cases these dependencies will
 * be loaded (or loading) already and the callback will not be delayed. In case a
 * dependency hasn't arrived yet it'll make sure those are loaded before this.
 */

/* global mw, $ */
/* jshint strict:false, browser:true */

mw.loader.using( ['mediawiki.user', 'mediawiki.util', 'mediawiki.notify'] ).done( function () {
/* Begin of mw.loader.using callback */

/**
 * Redirect User:Name/skin.js and skin.css to the current skin's pages
 * (unless the 'skin' page really exists)
 * @source: http://www.mediawiki.org/wiki/Snippets/Redirect_skin.js
 * @rev: 2
 */
if ( mw.config.get( 'wgArticleId' ) === 0 && mw.config.get( 'wgNamespaceNumber' ) === 2 ) {
	var titleParts = mw.config.get( 'wgPageName' ).split( '/' );
	/* Make sure there was a part before and after the slash
	   and that the latter is 'skin.js' or 'skin.css' */
	if ( titleParts.length == 2 ) {
		var userSkinPage = titleParts.shift() + '/' + mw.config.get( 'skin' );
		if ( titleParts.slice( -1 ) == 'skin.js' ) {
			window.location.href = mw.util.getUrl( userSkinPage + '.js' );
		} else if ( titleParts.slice( -1 ) == 'skin.css' ) {
			window.location.href = mw.util.getUrl( userSkinPage + '.css' );
		}
	}
}

/**
 * Map addPortletLink to mw.util
 * @deprecated: Use mw.util.addPortletLink instead.
 */
mw.log.deprecate( window, 'addPortletLink', mw.util.addPortletLink, 'Use mw.util.addPortletLink instead' );

/**
 * Extract a URL parameter from the current URL
 * @deprecated: Use mw.util.getParamValue with proper escaping
 */
mw.log.deprecate( window, 'getURLParamValue', mw.util.getParamValue, 'Use mw.util.getParamValue instead' );

/**
 * Test if an element has a certain class
 * @deprecated:  Use $(element).hasClass() instead.
 */
mw.log.deprecate( window, 'hasClass', function ( element, className ) {
	return $( element ).hasClass( className );
}, 'Use jQuery.hasClass() instead' );

/**
 * @source www.mediawiki.org/wiki/Snippets/Load_JS_and_CSS_by_URL
 * @rev 6
 */
var extraCSS = mw.util.getParamValue( 'withCSS' ),
	extraJS = mw.util.getParamValue( 'withJS' );

if ( extraCSS ) {
	if ( extraCSS.match( /^MediaWiki:[^&<>=%#]*\.css$/ ) ) {
		mw.loader.load( '/w/index.php?title=' + extraCSS + '&action=raw&ctype=text/css', 'text/css' );
	} else {
		mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withCSS value' } );
	}
}

if ( extraJS ) {
	if ( extraJS.match( /^MediaWiki:[^&<>=%#]*\.js$/ ) ) {
		mw.loader.load( '/w/index.php?title=' + extraJS + '&action=raw&ctype=text/javascript' );
	} else {
		mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withJS value' } );
	}
}

/**
 * WikiMiniAtlas
 *
 * Description: WikiMiniAtlas is a popup click and drag world map.
 *              This script causes all of our coordinate links to display the WikiMiniAtlas popup button.
 *              The script itself is located on meta because it is used by many projects.
 *              See [[Meta:WikiMiniAtlas]] for more information.
 * Note - use of this service is recommended to be repalced with mw:Help:Extension:Kartographer
 */
( function () {
	var require_wikiminiatlas = false;
	var coord_filter = /geohack/;
	$( function () {
		$( 'a.external.text' ).each( function( key, link ) {
			if ( link.href && coord_filter.exec( link.href ) ) {
				require_wikiminiatlas = true;
				// break from loop
				return false;
			}
		} );
		if ( $( 'div.kmldata' ).length ) {
			require_wikiminiatlas = true;
		}
		if ( require_wikiminiatlas ) {
			mw.loader.load( '//meta.wikimedia.org/w/index.php?title=MediaWiki:Wikiminiatlas.js&action=raw&ctype=text/javascript' );
		}
	} );
} )();

/**
 * Collapsible tables; reimplemented with mw-collapsibe
 * Styling is also in place to avoid FOUC
 * 
 * Allows tables to be collapsed, showing only the header. See [[Help:Collapsing]].
 * @version 3.0.0 (2018-05-20)
 * @source https://www.mediawiki.org/wiki/MediaWiki:Gadget-collapsibleTables.js
 * @author [[User:R. Koot]]
 * @author [[User:Krinkle]]
 * @author [[User:TheDJ]]
 * @deprecated Since MediaWiki 1.20: Use class="mw-collapsible" instead which
 * is supported in MediaWiki core. Shimmable since MediaWiki 1.32
 */
function makeCollapsibleMwCollapsible( $content ) {
	var $tables = $content
		.find( 'table.collapsible:not(.mw-collapsible)' )
		.addClass( 'mw-collapsible' );

	$.each( $tables, function( index, table ) {
		// mw.log.warn( 'This page is using the deprecated class collapsible. Please replace it with mw-collapsible.');
		if( $( table ).hasClass( 'collapsed') ) {
			$( table ).addClass( 'mw-collapsed' );
			// mw.log.warn( 'This page is using the deprecated class collapsed. Please replace it with mw-collapsed.');
		}
	} );
	if( $tables.length > 0 ) {
		mw.loader.using( 'jquery.makeCollapsible' ).then( function() {
			$tables.makeCollapsible();
		} );
	}
}
mw.hook( 'wikipage.content' ).add( makeCollapsibleMwCollapsible );

/**
 * Add support to mw-collapsible for autocollapse, innercollapse and outercollapse
 *
 * Maintainers: TheDJ
 */
function mwCollapsibleSetup( $collapsibleContent ) {
	var $element,
		$toggle,
		autoCollapseThreshold = 2;
	$.each( $collapsibleContent, function (index, element) {
		$element = $( element );
		if ( $element.hasClass( 'collapsible' ) ) {
			$element.find('tr:first > th:first').prepend( $element.find('tr:first > * > .mw-collapsible-toggle'));
		}
		if ( $collapsibleContent.length >= autoCollapseThreshold && $element.hasClass( 'autocollapse' ) ) {
			$element.data( 'mw-collapsible' ).collapse();
		} else if ( $element.hasClass( 'innercollapse' ) ) {
			if ( $element.parents( '.outercollapse' ).length > 0 ) {
				$element.data( 'mw-collapsible' ).collapse();
			}
		}
		// because of colored backgrounds, style the link in the text color
		// to ensure accessible contrast
		$toggle = $element.find( '.mw-collapsible-toggle' );
		if ( $toggle.length ) {
			// Make the toggle inherit text color
			if( $toggle.parent()[0].style.color ) {
				$toggle.find( 'a' ).css( 'color', 'inherit' );
			}
		}
	} );
}

mw.hook( 'wikipage.collapsibleContent' ).add( mwCollapsibleSetup );

/**
 * Dynamic Navigation Bars (experimental)
 *
 * Description: See [[Wikipedia:NavFrame]].
 * Maintainers: UNMAINTAINED
 */
 
var collapseCaption = 'hide';
var expandCaption = 'show';

// Set up the words in your language
var navigationBarHide = '[' + collapseCaption + ']';
var navigationBarShow = '[' + expandCaption + ']';

/**
 * Shows and hides content and picture (if available) of navigation bars.
 *
 * @param {number} indexNavigationBar The index of navigation bar to be toggled
 * @param {jQuery.Event} event Event object
 */
function toggleNavigationBar( indexNavigationBar, event ) {
	var navToggle = document.getElementById( 'NavToggle' + indexNavigationBar );
	var navFrame = document.getElementById( 'NavFrame' + indexNavigationBar );
	var navChild;

	if ( !navFrame || !navToggle ) {
		return false;
	}

	// If shown now
	if ( navToggle.firstChild.data === navigationBarHide ) {
		for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
			if ( $( navChild ).hasClass( 'NavContent' ) || $( navChild ).hasClass( 'NavPic' ) ) {
				navChild.style.display = 'none';
			}
		}
		navToggle.firstChild.data = navigationBarShow;

	// If hidden now
	} else if ( navToggle.firstChild.data === navigationBarShow ) {
		for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
			if ( $( navChild ).hasClass( 'NavContent' ) || $( navChild ).hasClass( 'NavPic' ) ) {
				navChild.style.display = 'block';
			}
		}
		navToggle.firstChild.data = navigationBarHide;
	}

	event.preventDefault();
}

/**
 * Adds show/hide-button to navigation bars.
 *
 * @param {jQuery} $content
 */
function createNavigationBarToggleButton( $content ) {
	var i, j, navChild, navToggle, navToggleText, isCollapsed,
		indexNavigationBar = 0;
	// Iterate over all < div >-elements
	var $divs = $content.find( 'div.NavFrame:not(.mw-collapsible)' );
	$divs.each( function ( i, navFrame ) {
		indexNavigationBar++;
		navToggle = document.createElement( 'a' );
		navToggle.className = 'NavToggle';
		navToggle.setAttribute( 'id', 'NavToggle' + indexNavigationBar );
		navToggle.setAttribute( 'href', '#' );
		$( navToggle ).on( 'click', $.proxy( toggleNavigationBar, null, indexNavigationBar ) );

		isCollapsed = $( navFrame ).hasClass( 'collapsed' );
		/**
		 * Check if any children are already hidden.  This loop is here for backwards compatibility:
		 * the old way of making NavFrames start out collapsed was to manually add style="display:none"
		 * to all the NavPic/NavContent elements.  Since this was bad for accessibility (no way to make
		 * the content visible without JavaScript support), the new recommended way is to add the class
		 * "collapsed" to the NavFrame itself, just like with collapsible tables.
		 */
		for ( navChild = navFrame.firstChild; navChild !== null && !isCollapsed; navChild = navChild.nextSibling ) {
			if ( $( navChild ).hasClass( 'NavPic' ) || $( navChild ).hasClass( 'NavContent' ) ) {
				if ( navChild.style.display === 'none' ) {
					isCollapsed = true;
				}
			}
		}
		if ( isCollapsed ) {
			for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
				if ( $( navChild ).hasClass( 'NavPic' ) || $( navChild ).hasClass( 'NavContent' ) ) {
					navChild.style.display = 'none';
				}
			}
		}
		navToggleText = document.createTextNode( isCollapsed ? navigationBarShow : navigationBarHide );
		navToggle.appendChild( navToggleText );

		// Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked)
		for ( j = 0; j < navFrame.childNodes.length; j++ ) {
			if ( $( navFrame.childNodes[j] ).hasClass( 'NavHead' ) ) {
				navToggle.style.color = navFrame.childNodes[j].style.color;
				navFrame.childNodes[j].appendChild( navToggle );
			}
		}
		navFrame.setAttribute( 'id', 'NavFrame' + indexNavigationBar );
	} );
}

mw.hook( 'wikipage.content' ).add( createNavigationBarToggleButton );

/**
 * Magic editintros ****************************************************
 *
 * Description: Adds editintros on disambiguation pages and BLP pages.
 * Maintainers: [[User:RockMFR]]
 */
function addEditIntro( name ) {
	$( '.mw-editsection, #ca-edit, #ca-ve-edit' ).find( 'a' ).each( function ( i, el ) {
		el.href = $( this ).attr( 'href' ) + '&editintro=' + name;
	} );
}

if ( mw.config.get( 'wgNamespaceNumber' ) === 0 ) {
	$( function () {
		if ( document.getElementById( 'disambigbox' ) ) {
			addEditIntro( 'Template:Disambig_editintro' );
		}
	} );

	$( function () {
		var cats = mw.config.get('wgCategories');
		if ( !cats ) {
			return;
		}
		if ( $.inArray( 'Living people', cats ) !== -1 || $.inArray( 'Possibly living people', cats ) !== -1 ) {
			addEditIntro( 'Template:BLP_editintro' );
		}
	} );
}

/* Actions specific to the edit page */
if ( mw.config.get( 'wgAction' ) === 'edit' || mw.config.get( 'wgAction' ) === 'submit' ) {
	/**
	 * Fix edit summary prompt for undo
	 *
	 *  Fixes the fact that the undo function combined with the "no edit summary prompter"
	 *  complains about missing editsummary, if leaving the edit summary unchanged.
	 *  Added by [[User:Deskana]], code by [[User:Tra]].
	 *  See also [[phab:T10912]].
	 */
	$(function () {
		if (document.location.search.indexOf('undo=') !== -1 && document.getElementsByName('wpAutoSummary')[0]) {
			document.getElementsByName('wpAutoSummary')[0].value = '1';
		}
	});
}

/* End of mw.loader.using callback */
} );



// CODE ADDED BY User:R0o0di

$(function () {
  // allows advanced HTML and CSS features within wikitext with the help of Template:Code
  // without the code below, Template:Code will not work
  var codeElements = document.querySelectorAll(".Template-code");

  if (codeElements.length > 0) {
	Array.prototype.forEach.call(codeElements, function (el) {
		var styles = el.dataset.styles || "";
		var html = decodeURIComponent(el.dataset.html || "");
		var param = el.dataset.param || "";
		var param2 = el.dataset.param2 || "";
		var uid = (Math.random().toString(36).slice(2)); // unique ID, useful for Template:cover

		// replace placeholders inside HTML
                html = html.replace("{{{param}}}", param).replace("{{{param2}}}", param2).replace(/{{{UID}}}/g, uid);

		// Insert HTML code right where the <span> was
		if (html) {
			var container = document.createElement("div");
			container.innerHTML = html;
			// Insert each child of the container before the span
			while (container.firstChild) {
				el.parentNode.insertBefore(container.firstChild, el);
			}
		}
		el.remove();

		if (styles) {
			var styleTag = document.createElement("style");
			styleTag.textContent = styles;
			document.head.appendChild(styleTag);
		}
	});
  }



  // code for Template:tt
  /**
   * Tooltip
   * Interactive tooltip with smart repositioning
   * @version 1.0 (2025-06-07)
   * @author [[User:AvidConanContributor]]
   */

  // Run on page load, resize, and scroll
  adjustTooltipPositions();
  window.addEventListener("resize", adjustTooltipPositions);
  window.addEventListener("scroll", adjustTooltipPositions, true);
    
  function adjustTooltipPositions() {
    var tooltips = document.querySelectorAll(".tt-container");

    if (tooltips.length > 0) {

        Array.prototype.forEach.call(tooltips, function (tooltipContainer) {
            var tooltip = tooltipContainer.querySelector(".tt-tooltip");
            var chevron = tooltip.querySelector(".tt-chevron");
            
            // 1. Temporarily show tooltip to measure size
            tooltipContainer.style.opacity = "0";
            tooltipContainer.style.display = "flex";
            tooltip.style.transform = "";
            
            var tooltipRect = tooltip.getBoundingClientRect();
            var targetRect = tooltipContainer.parentElement.getBoundingClientRect();
            
            // 2. Measure how much the tooltip is outside the viewport
            var overflowTop = tooltipRect.top < 1;
            var spaceTop = targetRect.top > tooltipRect.height + 10;
            var overflowLeft = Math.min(Math.max(0, -tooltipRect.left), 0.5 * tooltipRect.width - 15);
            var overflowRight = Math.min(Math.max(0, tooltipRect.right - document.documentElement.clientWidth), 0.5 * tooltipRect.width - 15);
            
            // 3. Shift tooltip into view if needed and move chevron accordingly
            if (overflowTop) {
                tooltipContainer.classList.remove("tt-container-above");
                tooltipContainer.classList.add("tt-container-below");
            } else if (spaceTop) {
                tooltipContainer.classList.add("tt-container-above");
                tooltipContainer.classList.remove("tt-container-below");
            }
            
            if (overflowLeft > 0) {
                tooltip.style.transform = "translateX(calc(" + overflowLeft + "px + 4px))";
                chevron.style.transform = "translateX(calc(-50% - " + overflowLeft + "px - 4px))";
            } else if (overflowRight > 0) {
                tooltip.style.transform = "translateX(calc(-" + overflowRight + "px - 4px))";
                chevron.style.transform = "translateX(calc(-50% + " + overflowRight + "px + 4px))";
            } else {
                tooltip.style.transform = "";
                chevron.style.transform = "";
            }
            
            // 4. Hide it again until hover
            tooltipContainer.style.opacity = "";
            tooltipContainer.style.display = "";
        });

    } // end of if (tooltips.length > 0) {
    
  } // end of adjustTooltipPositions()


  // code for Template:SidescrollSlides
  /**
   * Sidescroll Slides
   * Automatic element (a.k.a. slide) sidescrolling with interactive buttons
   * @version 1.0.0 (2025-08-22)
   * @author [[User:AvidConanContributor]] & [[User:R0o0di]]
   */

  function sidescrollScripts() {
    var sidescrolls = document.querySelectorAll(".sidescroll-container");
    for (var i = 0; i < sidescrolls.length; i++) {
      (function (i) {
        var sidescroll = sidescrolls[i].querySelector(".sidescroll"); if (!sidescroll) return;
        var slides = sidescroll.querySelectorAll(".sidescroll-slide");
        var totalSlides = slides.length;
        
        var leftButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-chevron-left)");
        var pausePlayButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-pause-play)");
        var rightButton = sidescrolls[i].querySelector(".sidescroll-button:has(> .sidescroll-button-chevron-right)");
        
        var slideMinWidth = parseFloat(getComputedStyle(sidescroll).getPropertyValue("--slide-min-width"));
        var visibleSlideCount = null;
        
        var slidePos = 0;
        
        function updateSlideWidths() {
          var sidescrollWidth = sidescroll.getBoundingClientRect().width;
          visibleSlideCount = Math.max(1, Math.min(Math.floor(sidescrollWidth / slideMinWidth), totalSlides));
          
          var slideWidth = sidescrollWidth / visibleSlideCount; // sets the width of slides
          
          sidescroll.style.setProperty("--slide-width", slideWidth + "px");
        }
        
        function cloneSlides() {
          for (var j = 0; j < totalSlides; j++) {
            var slideClone = slides[j].cloneNode(true);
            sidescroll.appendChild(slideClone);
            slideClone.setAttribute("data-slide-clone", "true");
          }
        }
        
        function clampSlidePos() {
          void sidescroll.offsetWidth;
          
          sidescroll.style.setProperty("transition-duration", "0s");
          
          if (slidePos === 0) {
            sidescroll.style.setProperty("--slide-pos", slidePos = totalSlides);
          } else if (slidePos > totalSlides) {
            sidescroll.style.setProperty("--slide-pos", slidePos -= totalSlides);
          }
          
          void sidescroll.offsetWidth;
        }
        
        var canScroll = true;
        var pausePending = false;
        function sidescrollMove(dir) {
          if (!canScroll) return;
          
          canScroll = false;
          
          dir === "left" ? slidePos-- : slidePos++;
          sidescroll.style.removeProperty("transition-duration");
          sidescroll.style.setProperty("--slide-pos", slidePos);
          
          sidescroll.addEventListener("transitionend", function handler() {
            clampSlidePos();
            sidescroll.removeEventListener("transitionend", handler);
            
            setTimeout(function advanceCooldown() {
              canScroll = true;
              if (pausePending) {
                pausePending = false;
                pauseAutoAdvance();
              }
            }, 1);
          });
        }
        
        var progressBarFillDur = parseFloat(getComputedStyle(sidescroll).getPropertyValue("--progress-bar-fill-duration")) * 1000;
        var autoAdvanceTimer = null;
        var progressBarBg = sidescrolls[i].querySelector(".sidescroll-progress-bar-background");
        var progressBar = progressBarBg.querySelector(".sidescroll-progress-bar");
        var pausePlayIcon = pausePlayButton ? pausePlayButton.querySelector(".sidescroll-button-pause-play > div") : null;
        
        var isPaused = false;
        var timerStartTime = null;     // when current countdown began
        var remainingTime = null;      // ms left when paused
        var progressBarStartWidth = 0; // % width when paused
  
        var progressBarResetting = false;
        function resetProgressBar() {
          if (!progressBar) return;
  
          progressBarResetting = true;
  
          progressBar.style.removeProperty("transition");
          progressBar.style.setProperty("width", "0%");
  
          void progressBar.offsetWidth;
  
          progressBar.addEventListener("transitionend", function handler(event) {
            if (event.propertyName !== "width") return;
  
            progressBar.removeEventListener("transitionend", handler);
            progressBarResetting = false;
  
            if (isPaused) {
              progressBar.style.setProperty("transition", "none");
              progressBar.style.setProperty("width", "0%");
              return;
            }
  
            progressBar.style.setProperty(
              "transition",
              "width var(--auto-advance-delay) linear"
            );
            progressBar.style.setProperty("width", "100%");
          });
        }
  
        function freezeProgressBar() {
          if (!progressBar) return;
  
          if (progressBarResetting) {
            progressBarColors("onAndPaused");
            return;
          }
  
          var computed = parseFloat(getComputedStyle(progressBar).width);
          var bgWidth = parseFloat(getComputedStyle(progressBar.parentNode).width);
  
          progressBarStartWidth = bgWidth > 0
            ? (computed / bgWidth) * 100
            : 0;
  
          progressBar.style.setProperty("transition", "none");
          progressBar.style.setProperty("width", progressBarStartWidth + "%");
  
          progressBarColors("onAndPaused");
        }
  
        function resumeProgressBar() {
          if (!progressBar) return;
          
          var pct = progressBarStartWidth;
          var durSec = remainingTime / 1000;
          progressBar.style.setProperty("transition", "none");
          progressBar.style.setProperty("width", pct + "%");
          void progressBar.offsetWidth;
          
          progressBar.style.setProperty("transition", "width " + durSec + "s linear");
          progressBar.style.setProperty("width", "100%");
          
          progressBarColors("on");
        }
        
        function resetAutoAdvance() {
          if (isPaused) return;
          
          if (autoAdvanceTimer) {
            clearTimeout(autoAdvanceTimer);
          }
          
          remainingTime = progressBarFillDur;
          timerStartTime = Date.now();
  
          autoAdvanceTimer = setTimeout(function () {
            sidescrollMove("right");
            resetAutoAdvance();
          }, remainingTime);
          
          resetProgressBar();
        }
  
        function pauseAutoAdvance() {
          if (!canScroll) {
            pausePending = true;
            return;
          }
          
          if (isPaused) return;
          isPaused = true;
          var elapsed = Date.now() - timerStartTime;
          remainingTime = Math.max(0, remainingTime - elapsed);
          clearTimeout(autoAdvanceTimer);
          autoAdvanceTimer = null;
          freezeProgressBar();
          setPlayIcon();
        }
  
        function resumeAutoAdvance() {
          if (!isPaused) return;
          isPaused = false;
          timerStartTime = Date.now();
          autoAdvanceTimer = setTimeout(function () {
            sidescrollMove("right");
            resetAutoAdvance();
          }, remainingTime);
          resumeProgressBar();
          setPauseIcon();
        }
  
        function setPauseIcon() {
          if (!pausePlayIcon) return;
          pausePlayIcon.className = "sidescroll-button-pause-icon";
        }
  
        function setPlayIcon() {
          if (!pausePlayIcon) return;
          pausePlayIcon.className = "sidescroll-button-play-icon";
        }
        
        function whenAllSlidesFit() {
          if (visibleSlideCount === totalSlides) {
            sidescroll.style.setProperty("--slide-pos", slidePos = 0);
            sidescrolls[i].classList.remove("sidescroll-container-can-scroll");
            
            pauseAutoAdvance();
  
            progressBar.style.setProperty("width", 0);
            progressBarColors("off");          
          } else {
            sidescrolls[i].classList.add("sidescroll-container-can-scroll");
            progressBarColors(isPaused ? "onAndPaused" : "on");
          }
        }
        
        function progressBarColors(state) {
          if (state === "onAndPaused") {
            progressBar.style.removeProperty("visibility");
            progressBar.style.setProperty("background", "var(--btn-and-progress-bar-color)");
            progressBarBg.style.setProperty("background", "var(--btn-and-progress-bar-color)");
          } else if (state === "off") {
            progressBar.style.setProperty("visibility", "hidden");
            progressBarBg.style.setProperty("background", "var(--progress-bar-background)");
          } else {
            progressBar.style.removeProperty("visibility");
            progressBarBg.style.setProperty("background", "var(--progress-bar-background)");
          }
        }
  
        // Function calls
        updateSlideWidths();
        cloneSlides();
        clampSlidePos();
        resetAutoAdvance();
        whenAllSlidesFit();
        
        if (leftButton) {
          leftButton.addEventListener("click", function () {
            sidescrollMove("left");
            if (!isPaused) {
              resetAutoAdvance();
            }
          });
        }
        
        if (rightButton) {
          rightButton.addEventListener("click", function () {
            sidescrollMove("right");
            if (!isPaused) {
              resetAutoAdvance();
            }
          });
        }
  
        if (pausePlayButton) {
          pausePlayButton.addEventListener("click", function () {
            if (isPaused) {
              resumeAutoAdvance();
            } else {
              pauseAutoAdvance();
            }
          });
        }
        
        window.addEventListener("resize", function () {
          updateSlideWidths();
          clampSlidePos();
          whenAllSlidesFit();
        });
      })(i); 
    }
  }
  sidescrollScripts();
}); // end of $(function () )