MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| (29 intermediate revisions by the same user not shown) | |||
| Line 19: | Line 19: | ||
}); | }); | ||
/* === Transight site-wide footer with HOLI effects === */ | |||
/* === Transight site-wide footer | |||
mw.loader.using(['mediawiki.util']).then(function () { | mw.loader.using(['mediawiki.util']).then(function () { | ||
function injectTsFooter() { | function injectTsFooter() { | ||
if (document.getElementById('ts-footer')) return; | if (document.getElementById('ts-footer')) return; | ||
var html = '\ | var html = '\ | ||
<style>\ | |||
/* ===== FOOTER BASE ===== */\ | |||
.ts-footer {\ | |||
position: relative;\ | |||
overflow: hidden;\ | |||
background: linear-gradient(270deg, #ff0080, #ff8c00, #40e0d0, #ff0080);\ | |||
background-size: 600% 600%;\ | |||
animation: tsGradientMove 12s ease infinite;\ | |||
color: #fff;\ | |||
padding: 40px 20px 20px;\ | |||
font-family: Arial, sans-serif;\ | |||
}\ | |||
@keyframes tsGradientMove {\ | |||
0%{background-position:0% 50%}\ | |||
50%{background-position:100% 50%}\ | |||
100%{background-position:0% 50%}\ | |||
}\ | |||
.ts-footer__container { max-width:1200px; margin:auto; position:relative; z-index:2;}\ | |||
.ts-footer__sitemap { display:flex; flex-wrap:wrap; gap:30px; justify-content:space-between;}\ | |||
.ts-footer__col { list-style:none; padding:0; min-width:150px;}\ | |||
.ts-footer__title { font-weight:bold; margin-bottom:10px; font-size:14px;}\ | |||
.ts-footer__col li a { color:#fff; text-decoration:none; font-size:13px;}\ | |||
.ts-footer__col li a:hover { text-decoration:underline;}\ | |||
.ts-footer__bottom { margin-top:30px; display:flex; justify-content:space-between; flex-wrap:wrap; font-size:13px;}\ | |||
.ts-footer__social a { margin-right:10px; color:#fff; text-decoration:none;}\ | |||
.ts-footer__social a:hover { text-decoration:underline;}\ | |||
.ts-footer__mapbox { overflow:hidden;}\ | |||
\ | |||
/* ===== HOLI COLOR PARTICLES ===== */\ | |||
.holi-particle {\ | |||
position:absolute;\ | |||
border-radius:50%;\ | |||
opacity:0.7;\ | |||
pointer-events:none;\ | |||
animation: floatUp linear forwards;\ | |||
}\ | |||
@keyframes floatUp {\ | |||
from { transform: translateY(0) scale(1); opacity:0.8;}\ | |||
to { transform: translateY(-600px) scale(1.5); opacity:0;}\ | |||
}\ | |||
\ | |||
/* ===== COLOR BOMB BLAST ===== */\ | |||
.holi-blast {\ | |||
position:absolute;\ | |||
width:20px;\ | |||
height:20px;\ | |||
border-radius:50%;\ | |||
pointer-events:none;\ | |||
animation: blastAnim 1s ease-out forwards;\ | |||
}\ | |||
@keyframes blastAnim {\ | |||
0% { transform: scale(1); opacity:1;}\ | |||
100% { transform: scale(15); opacity:0;}\ | |||
}\ | |||
</style>\ | |||
\ | |||
<div id="ts-footer" class="ts-footer">\ | <div id="ts-footer" class="ts-footer">\ | ||
<div class="ts-footer__container">\ | <div class="ts-footer__container">\ | ||
| Line 38: | Line 92: | ||
<li><a href="https://transight.com/solutions/asset-tracking/">Asset Tracking</a></li>\ | <li><a href="https://transight.com/solutions/asset-tracking/">Asset Tracking</a></li>\ | ||
<li><a href="https://transight.com/transforming-battery-management-for-ev/">Advanced EV Telematics</a></li>\ | <li><a href="https://transight.com/transforming-battery-management-for-ev/">Advanced EV Telematics</a></li>\ | ||
<li><a href="https://transight.com/remote-cold-chain-management/">Cold Chain Management</a></li>\ | |||
<li><a href="https://transight.com/redefining-telematics-in-global-farm-equipment-sector/">Global Farm Equipment Sector</a></li>\ | |||
</ul>\ | </ul>\ | ||
<ul class="ts-footer__col">\ | <ul class="ts-footer__col">\ | ||
| Line 48: | Line 104: | ||
<li class="ts-footer__title">SUPPORT</li>\ | <li class="ts-footer__title">SUPPORT</li>\ | ||
<li><a href="https://transight.com/client-support/">Product Support</a></li>\ | <li><a href="https://transight.com/client-support/">Product Support</a></li>\ | ||
<li><a href="/ | <li><a href="https://telematics.transight.com/">Wiki Knowledge Base</a></li>\ | ||
<li><a href="/wiki/RMA_Guidelines">Warranty & Repair</a></li>\ | <li><a href="/wiki/RMA_Guidelines">Warranty & Repair</a></li>\ | ||
</ul>\ | </ul>\ | ||
| Line 56: | Line 112: | ||
<li><a href="/wiki/Career">Career</a></li>\ | <li><a href="/wiki/Career">Career</a></li>\ | ||
<li><a href="https://transight.com/contact/">Contacts</a></li>\ | <li><a href="https://transight.com/contact/">Contacts</a></li>\ | ||
</ul>\ | |||
<ul class="ts-footer__col">\ | |||
<li class="ts-footer__title">LOCATION</li>\ | |||
<li>\ | |||
<div class="ts-footer__mapbox">\ | |||
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3928.5275868424515!2d76.35275553427442!3d10.055785794465395!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3b080c05015f9bf9%3A0x8e58791613152f5e!2sTransight!5e0!3m2!1sen!2sin!4v1765272619872!5m2!1sen!2sin" \ | |||
width="100%" height="140" style="border:0; border-radius:6px;" loading="lazy"></iframe>\ | |||
</div>\ | |||
</li>\ | |||
</ul>\ | </ul>\ | ||
</nav>\ | </nav>\ | ||
<div class="ts-footer__bottom">\ | <div class="ts-footer__bottom">\ | ||
<p | <p>© 2025, Transight</p>\ | ||
<p class="ts-footer__social"><a href="https://facebook.com">Facebook</a> <a href="https://www.linkedin.com/company/transight/">LinkedIn</a> <a href="https://x.com/transight_iot?s=21">X</a> <a href="https://youtube.com/@transightsystems">YouTube</a> <a href="https://www.instagram.com/transightsystems/">Instagram</a></p>\ | <p class="ts-footer__social">\ | ||
<a href="https://facebook.com">Facebook</a>\ | |||
<a href="https://www.linkedin.com/company/transight/">LinkedIn</a>\ | |||
<a href="https://x.com/transight_iot?s=21">X</a>\ | |||
<a href="https://youtube.com/@transightsystems">YouTube</a>\ | |||
<a href="https://www.instagram.com/transightsystems/">Instagram</a>\ | |||
</p>\ | |||
</div>\ | </div>\ | ||
</div>\ | </div>\ | ||
</div>'; | </div>'; | ||
document.body.insertAdjacentHTML("beforeend", html); | |||
document.body.insertAdjacentHTML( | |||
/* ===== HOLI PARTICLE EFFECT ===== */ | |||
var footer = document.getElementById("ts-footer"); | |||
var colors = ["#ff0080", "#ff8c00", "#00ffcc", "#ff0000", "#ffff00", "#00aaff"]; | |||
function createParticle() { | |||
var particle = document.createElement("div"); | |||
particle.className = "holi-particle"; | |||
var size = Math.random() * 15 + 10; | |||
particle.style.width = size + "px"; | |||
particle.style.height = size + "px"; | |||
particle.style.background = colors[Math.floor(Math.random() * colors.length)]; | |||
particle.style.left = Math.random() * footer.offsetWidth + "px"; | |||
particle.style.bottom = "0px"; | |||
particle.style.animationDuration = (Math.random() * 3 + 3) + "s"; | |||
footer.appendChild(particle); | |||
// | setTimeout(function () { | ||
document. | particle.remove(); | ||
}, 6000); | |||
} | |||
setInterval(createParticle, 300); | |||
/* ===== RANDOM COLOR BLAST ===== */ | |||
function createBlast() { | |||
var blast = document.createElement("div"); | |||
blast.className = "holi-blast"; | |||
blast.style.background = colors[Math.floor(Math.random() * colors.length)]; | |||
blast.style.left = Math.random() * footer.offsetWidth + "px"; | |||
blast.style.top = Math.random() * footer.offsetHeight + "px"; | |||
footer.appendChild(blast); | |||
setTimeout(function () { | |||
blast.remove(); | |||
}, 1000); | |||
} | |||
setInterval(createBlast, 2000); | |||
} | } | ||
injectTsFooter(); | |||
}); | }); | ||
| Line 207: | Line 307: | ||
}, 300); | }, 300); | ||
}() ); | }() ); | ||
$( | /* Mobile menu and navigation helper for MediaWiki (vanilla JS, no jQuery) */ | ||
/ | (function () { | ||
var $ | 'use strict'; | ||
/** | |||
* Utility: safely query a single element | |||
* @param {string} selector | |||
* @returns {Element|null} | |||
*/ | |||
function $$(selector) { | |||
return document.querySelector(selector); | |||
} | |||
/** | |||
* Utility: safely query multiple elements | |||
* @param {string} selector | |||
* @returns {NodeListOf<Element>} | |||
*/ | |||
function $$$ (selector) { | |||
return document.querySelectorAll(selector); | |||
} | |||
/** | |||
* Create the menu toggle element (hamburger) only if not already present. | |||
* Returns the toggle element. | |||
* @returns {HTMLElement|null} | |||
*/ | |||
function ensureMenuToggle () { | |||
var existing = $$('#menu-toggle'); | |||
if (existing) return existing; | |||
var toggle = document.createElement('div'); | |||
toggle.id = 'menu-toggle'; | |||
toggle.setAttribute('role', 'button'); | |||
toggle.setAttribute('tabindex', '0'); | |||
toggle.setAttribute('aria-label', 'Toggle menu'); | |||
toggle.className = 'menu-toggle'; | |||
// Create bars | |||
var bar1 = document.createElement('div'); bar1.className = 'bar1'; | |||
var bar2 = document.createElement('div'); bar2.className = 'bar2'; | |||
var bar3 = document.createElement('div'); bar3.className = 'bar3'; | |||
toggle.appendChild(bar1); | |||
toggle.appendChild(bar2); | |||
toggle.appendChild(bar3); | |||
// Click and keyboard handling | |||
function toggleHandler (e) { | |||
// Only handle left click or Enter/Space key | |||
if (e.type === 'click' || e.key === 'Enter' || e.key === ' ') { | |||
mobileMenuToggle(); | |||
e.preventDefault(); | |||
} | |||
} | |||
toggle.addEventListener('click', toggleHandler, false); | |||
toggle.addEventListener('keydown', toggleHandler, false); | |||
return toggle; | |||
} | |||
/** | |||
* Toggle the mobile menu state by toggling the 'open-toggle' class on #mw-panel | |||
*/ | |||
function mobileMenuToggle () { | |||
var panel = $$('#mw-panel'); | |||
if (!panel) return; | |||
panel.classList.toggle('open-toggle'); | |||
} | |||
/** | |||
* Remove the mobile menu open state when window is wide | |||
*/ | |||
function handleResize () { | |||
var panel = $$('#mw-panel'); | |||
if (!panel) return; | |||
if (window.innerWidth > 800) { | |||
panel.classList.remove('open-toggle'); | |||
} | |||
} | |||
/** | |||
* Copy anchors from a source selector into a target ul inside #p-cactions. | |||
* Keeps existing anchors' href and text. | |||
* @param {string} sourceSelector - e.g. "#p-views a" | |||
*/ | |||
function copyAnchorsToPCActions (sourceSelector) { | |||
var anchors = $$$ (sourceSelector); | |||
if (!anchors || anchors.length === 0) return; | |||
var pCActions = $$('#p-cactions'); | |||
if (!pCActions) return; | |||
// Find or create inner ul (the original code assumed p-cactions has a ul) | |||
var ul = pCActions.querySelector('ul'); | |||
if (!ul) { | |||
ul = document.createElement('ul'); | |||
pCActions.appendChild(ul); | |||
} | |||
// Append items (avoid duplicating items by checking href+text) | |||
Array.prototype.forEach.call(anchors, function (a) { | |||
if (!(a && a.href)) return; | |||
// Skip if an identical item already exists | |||
var exists = Array.prototype.some.call(ul.querySelectorAll('a'), function (x) { | |||
return x.href === a.href && x.textContent.trim() === a.textContent.trim(); | |||
}); | |||
if (exists) return; | |||
var li = document.createElement('li'); | |||
var newA = document.createElement('a'); | |||
newA.href = a.href; | |||
newA.textContent = a.textContent.trim(); | |||
// preserve title if present | |||
if (a.title) newA.title = a.title; | |||
li.appendChild(newA); | |||
ul.appendChild(li); | |||
}); | }); | ||
// | // Make p-cactions visible if there are items | ||
if (ul.children.length > 0) { | |||
pCActions.style.display = 'block'; | |||
} | |||
} | |||
// | /** | ||
* Copy namespace anchors into p-cactions (same as above but from #p-namespaces a) | |||
if (! | */ | ||
function copyNamespacesToPCActions () { | |||
copyAnchorsToPCActions('#p-namespaces a'); | |||
} | |||
} | |||
/** | |||
* Take the first anchor inside #p-namespaces, shorten its text at the last space, | |||
* create an anchor and append into the span inside #p-namespaces. | |||
*/ | |||
function updateNamespacesFirstElementText () { | |||
var namespaces = $$('#p-namespaces'); | |||
if (!namespaces) return; | |||
var anchors = namespaces.querySelectorAll('a'); | |||
if (!anchors || anchors.length === 0) return; | |||
var firstAnchor = anchors[0]; | |||
var rawText = firstAnchor.textContent.trim(); | |||
var newText = rawText; | |||
var lastSpaceIndex = rawText.lastIndexOf(' '); | |||
if (lastSpaceIndex > 0) { | |||
newText = rawText.substring(0, lastSpaceIndex); | |||
} | |||
var span = namespaces.querySelector('span'); | |||
if (!span) { | |||
// if span doesn't exist, create and append to #p-namespaces | |||
span = document.createElement('span'); | |||
namespaces.appendChild(span); | |||
} | |||
// Create text anchor | |||
var textnodeHref = document.createElement('a'); | |||
textnodeHref.href = firstAnchor.href || '#'; | |||
textnodeHref.title = firstAnchor.title || ''; | |||
textnodeHref.textContent = newText.length > 0 ? newText : rawText; | |||
// Remove previous appended anchor if it exists to avoid duplication | |||
// (Assume it had no special id) | |||
var old = span.querySelector('a'); | |||
if (old) span.removeChild(old); | |||
span.appendChild(textnodeHref); | |||
} | |||
/** | |||
* Show p-cactions only when #p-views has anchors (mirrors original logic) | |||
*/ | |||
function revealPCActionsIfPViewsNotEmpty () { | |||
var pViews = $$('#p-views'); | |||
if (!pViews) return; | |||
var anchors = pViews.querySelectorAll('a'); | |||
var pCActions = $$('#p-cactions'); | |||
if (!pCActions) return; | |||
if (anchors && anchors.length > 0) { | |||
pCActions.style.display = 'block'; | |||
} | |||
} | |||
/** | |||
* Initialization run on DOMContentLoaded | |||
*/ | |||
function init () { | |||
// Insert menu-toggle into #mw-head and #mw-panel (if they exist) | |||
var toggle = ensureMenuToggle(); | |||
var mwHead = $$('#mw-head'); | |||
var mwPanel = $$('#mw-panel'); | |||
// Insert a clone (or same node) into both places. Use clones to keep two elements. | |||
if (mwHead) { | |||
// Only append if not already present | |||
if (!mwHead.querySelector('#menu-toggle')) { | |||
mwHead.appendChild(toggle.cloneNode(true)); | |||
} | |||
} | |||
if (mwPanel) { | |||
if (!mwPanel.querySelector('#menu-toggle')) { | |||
mwPanel.appendChild(toggle.cloneNode(true)); | |||
} | } | ||
} | } | ||
// | // Setup copy operations (safe-guarded) | ||
copyAnchorsToPCActions('#p-views a'); | |||
copyNamespacesToPCActions(); | |||
updateNamespacesFirstElementText(); | |||
revealPCActionsIfPViewsNotEmpty(); | |||
// Remove open state on resize > 800 | |||
window.addEventListener('resize', handleResize, false); | |||
// A small safety: also run handleResize immediately to ensure correct state on load | |||
handleResize(); | |||
} | |||
// Run when DOM is ready | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', init); | |||
} else { | |||
init(); | |||
} | |||
})(); | |||
mw.hook('wikipage.content').add(function () { | |||
const track = document.querySelector('.ts-device-track'); | |||
if (!track) return; | |||
track.addEventListener('mouseenter', () => { | |||
track.style.animationPlayState = 'paused'; | |||
}); | |||
track.addEventListener('mouseleave', () => { | |||
track.style.animationPlayState = 'running'; | |||
}); | }); | ||
}); | }); | ||
Latest revision as of 06:44, 4 March 2026
$(document).ready(function() {
$('input#searchInput').attr('placeholder', 'Search for Transight Wiki');
});
// Open all external links in a new tab
$(document).ready(function () {
$("a.external").attr("target", "_blank");
});
mw.loader.using('mediawiki.util', function () {
// Force small text
document.documentElement.classList.remove('vector-feature-custom-font-size-standard', 'vector-feature-custom-font-size-large');
document.documentElement.classList.add('vector-feature-custom-font-size-small');
// Force wide layout
document.documentElement.classList.remove('vector-feature-limited-width');
document.documentElement.classList.add('vector-feature-wide-width');
});
/* === Transight site-wide footer with HOLI effects === */
mw.loader.using(['mediawiki.util']).then(function () {
function injectTsFooter() {
if (document.getElementById('ts-footer')) return;
var html = '\
<style>\
/* ===== FOOTER BASE ===== */\
.ts-footer {\
position: relative;\
overflow: hidden;\
background: linear-gradient(270deg, #ff0080, #ff8c00, #40e0d0, #ff0080);\
background-size: 600% 600%;\
animation: tsGradientMove 12s ease infinite;\
color: #fff;\
padding: 40px 20px 20px;\
font-family: Arial, sans-serif;\
}\
@keyframes tsGradientMove {\
0%{background-position:0% 50%}\
50%{background-position:100% 50%}\
100%{background-position:0% 50%}\
}\
.ts-footer__container { max-width:1200px; margin:auto; position:relative; z-index:2;}\
.ts-footer__sitemap { display:flex; flex-wrap:wrap; gap:30px; justify-content:space-between;}\
.ts-footer__col { list-style:none; padding:0; min-width:150px;}\
.ts-footer__title { font-weight:bold; margin-bottom:10px; font-size:14px;}\
.ts-footer__col li a { color:#fff; text-decoration:none; font-size:13px;}\
.ts-footer__col li a:hover { text-decoration:underline;}\
.ts-footer__bottom { margin-top:30px; display:flex; justify-content:space-between; flex-wrap:wrap; font-size:13px;}\
.ts-footer__social a { margin-right:10px; color:#fff; text-decoration:none;}\
.ts-footer__social a:hover { text-decoration:underline;}\
.ts-footer__mapbox { overflow:hidden;}\
\
/* ===== HOLI COLOR PARTICLES ===== */\
.holi-particle {\
position:absolute;\
border-radius:50%;\
opacity:0.7;\
pointer-events:none;\
animation: floatUp linear forwards;\
}\
@keyframes floatUp {\
from { transform: translateY(0) scale(1); opacity:0.8;}\
to { transform: translateY(-600px) scale(1.5); opacity:0;}\
}\
\
/* ===== COLOR BOMB BLAST ===== */\
.holi-blast {\
position:absolute;\
width:20px;\
height:20px;\
border-radius:50%;\
pointer-events:none;\
animation: blastAnim 1s ease-out forwards;\
}\
@keyframes blastAnim {\
0% { transform: scale(1); opacity:1;}\
100% { transform: scale(15); opacity:0;}\
}\
</style>\
\
<div id="ts-footer" class="ts-footer">\
<div class="ts-footer__container">\
<nav class="ts-footer__sitemap">\
<ul class="ts-footer__col">\
<li class="ts-footer__title">USE CASES</li>\
<li><a href="https://transight.com/solutions/fleet-management/">Fleet Telematics</a></li>\
<li><a href="https://transight.com/solutions/remote-management-system/">Remote Monitoring System</a></li>\
<li><a href="https://transight.com/solutions/automation/">Automation</a></li>\
<li><a href="https://transight.com/solutions/asset-tracking/">Asset Tracking</a></li>\
<li><a href="https://transight.com/transforming-battery-management-for-ev/">Advanced EV Telematics</a></li>\
<li><a href="https://transight.com/remote-cold-chain-management/">Cold Chain Management</a></li>\
<li><a href="https://transight.com/redefining-telematics-in-global-farm-equipment-sector/">Global Farm Equipment Sector</a></li>\
</ul>\
<ul class="ts-footer__col">\
<li class="ts-footer__title">PRODUCTS</li>\
<li><a href="https://telematics.transight.com/Trackers">Trackers</a></li>\
<li><a href="https://telematics.transight.com/Accessories">Accessories</a></li>\
<li><a href="https://transight.com/products/cloud-iot-platform/">Cloud IoT Solutions</a></li>\
</ul>\
<ul class="ts-footer__col">\
<li class="ts-footer__title">SUPPORT</li>\
<li><a href="https://transight.com/client-support/">Product Support</a></li>\
<li><a href="https://telematics.transight.com/">Wiki Knowledge Base</a></li>\
<li><a href="/wiki/RMA_Guidelines">Warranty & Repair</a></li>\
</ul>\
<ul class="ts-footer__col">\
<li class="ts-footer__title">ABOUT US</li>\
<li><a href="https://transight.com/about/">Mission, Vision</a></li>\
<li><a href="/wiki/Career">Career</a></li>\
<li><a href="https://transight.com/contact/">Contacts</a></li>\
</ul>\
<ul class="ts-footer__col">\
<li class="ts-footer__title">LOCATION</li>\
<li>\
<div class="ts-footer__mapbox">\
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3928.5275868424515!2d76.35275553427442!3d10.055785794465395!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3b080c05015f9bf9%3A0x8e58791613152f5e!2sTransight!5e0!3m2!1sen!2sin!4v1765272619872!5m2!1sen!2sin" \
width="100%" height="140" style="border:0; border-radius:6px;" loading="lazy"></iframe>\
</div>\
</li>\
</ul>\
</nav>\
<div class="ts-footer__bottom">\
<p>© 2025, Transight</p>\
<p class="ts-footer__social">\
<a href="https://facebook.com">Facebook</a>\
<a href="https://www.linkedin.com/company/transight/">LinkedIn</a>\
<a href="https://x.com/transight_iot?s=21">X</a>\
<a href="https://youtube.com/@transightsystems">YouTube</a>\
<a href="https://www.instagram.com/transightsystems/">Instagram</a>\
</p>\
</div>\
</div>\
</div>';
document.body.insertAdjacentHTML("beforeend", html);
/* ===== HOLI PARTICLE EFFECT ===== */
var footer = document.getElementById("ts-footer");
var colors = ["#ff0080", "#ff8c00", "#00ffcc", "#ff0000", "#ffff00", "#00aaff"];
function createParticle() {
var particle = document.createElement("div");
particle.className = "holi-particle";
var size = Math.random() * 15 + 10;
particle.style.width = size + "px";
particle.style.height = size + "px";
particle.style.background = colors[Math.floor(Math.random() * colors.length)];
particle.style.left = Math.random() * footer.offsetWidth + "px";
particle.style.bottom = "0px";
particle.style.animationDuration = (Math.random() * 3 + 3) + "s";
footer.appendChild(particle);
setTimeout(function () {
particle.remove();
}, 6000);
}
setInterval(createParticle, 300);
/* ===== RANDOM COLOR BLAST ===== */
function createBlast() {
var blast = document.createElement("div");
blast.className = "holi-blast";
blast.style.background = colors[Math.floor(Math.random() * colors.length)];
blast.style.left = Math.random() * footer.offsetWidth + "px";
blast.style.top = Math.random() * footer.offsetHeight + "px";
footer.appendChild(blast);
setTimeout(function () {
blast.remove();
}, 1000);
}
setInterval(createBlast, 2000);
}
injectTsFooter();
});
/* === Inject floating buttons: "Go to our web" & "Go to Top" === */
mw.loader.using(['mediawiki.util']).then(function () {
// Prevent duplicate injection
if (!document.getElementById('ts-webfab')) {
var webUrl = 'https://transight.com/';
var htmlWeb = ''
+ '<div id="ts-webfab" class="ts-fab">'
+ ' <a class="ts-fab__btn" href="' + webUrl + '" target="_blank" rel="noopener" aria-label="Go to our website"></a>'
+ ' <span class="ts-fab__bubble">Go to our web</span>'
+ '</div>';
document.body.insertAdjacentHTML('beforeend', htmlWeb);
}
if (!document.getElementById('ts-topfab')) {
var htmlTop = ''
+ '<div id="ts-topfab" class="ts-fab">'
+ ' <a class="ts-fab__btn" href="javascript:void(0);" aria-label="Go to top"></a>'
+ ' <span class="ts-fab__bubble">Go to Top</span>'
+ '</div>';
document.body.insertAdjacentHTML('beforeend', htmlTop);
// Smooth scroll to top
document.querySelector('#ts-topfab .ts-fab__btn').addEventListener('click', function() {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
});
/* === Transightee: Sticky header helper === */
( function () {
if ( window.transighteeStickyHeaderInit ) return;
window.transighteeStickyHeaderInit = true;
function getTopFixedElementsHeight(excludeEl) {
// find fixed-position elements at the very top (likely toolbars) and sum their heights
var all = document.getElementsByTagName('*');
var sum = 0;
for (var i = 0; i < all.length; i++) {
var el = all[i];
if (el === excludeEl) continue;
var cs = window.getComputedStyle(el);
if (cs.position === 'fixed' && cs.display !== 'none' && el.offsetHeight > 0) {
var rect = el.getBoundingClientRect();
// consider elements whose top is near the top of viewport
if (rect.top >= -2 && rect.top <= 10) {
sum += el.offsetHeight;
}
}
}
return sum;
}
function updateStickyHeader() {
var header = document.querySelector('.vector-header-container');
var pageContainer = document.querySelector('.mw-page-container');
if (!header || !pageContainer) {
// If header or container not present yet, keep trying later
return;
}
// Ensure header has a stable background so it doesn't appear transparent after becoming fixed
try {
var comp = getComputedStyle(header);
if (!header.style.background && comp.backgroundColor && comp.backgroundColor !== 'rgba(0, 0, 0, 0)') {
header.style.backgroundColor = comp.backgroundColor;
}
} catch (e) {
// ignore
}
// Detect any other fixed elements at the top (toolbars) and position header below them
var topOffset = getTopFixedElementsHeight(header);
header.style.top = topOffset + 'px';
// measure header height AFTER top is applied
// force reflow to get accurate size
var height = header.offsetHeight;
// total vertical space header occupies (top offset + header height) = required padding
var totalSpace = topOffset + height;
document.documentElement.style.setProperty('--trans-header-height', totalSpace + 'px');
// add body class to enable CSS that uses the variable
document.body.classList.add('transightee-has-sticky-header');
}
// debounce helper for resize events
var resizeTimer = null;
function scheduleUpdate() {
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
updateStickyHeader();
resizeTimer = null;
}, 120);
}
document.addEventListener('DOMContentLoaded', function () {
// initial attempt
updateStickyHeader();
// update when all resources loaded (images, fonts can change header height)
window.addEventListener('load', updateStickyHeader);
// keep updated on resize
window.addEventListener('resize', scheduleUpdate);
// Observe mutations inside header (e.g. buttons collapsing/expanding) and update padding
var header = document.querySelector('.vector-header-container');
if (header && window.MutationObserver) {
var mo = new MutationObserver(function () {
scheduleUpdate();
});
mo.observe(header, { attributes: true, childList: true, subtree: true, characterData: true });
// keep reference to avoid GC in some environments
window._transighteeStickyMO = mo;
}
});
// Also try a periodic update a couple of times in case skin scripts alter header after load
var tries = 0;
var intId = setInterval(function () {
updateStickyHeader();
tries++;
if (tries > 6) clearInterval(intId);
}, 300);
}() );
/* Mobile menu and navigation helper for MediaWiki (vanilla JS, no jQuery) */
(function () {
'use strict';
/**
* Utility: safely query a single element
* @param {string} selector
* @returns {Element|null}
*/
function $$(selector) {
return document.querySelector(selector);
}
/**
* Utility: safely query multiple elements
* @param {string} selector
* @returns {NodeListOf<Element>}
*/
function $$$ (selector) {
return document.querySelectorAll(selector);
}
/**
* Create the menu toggle element (hamburger) only if not already present.
* Returns the toggle element.
* @returns {HTMLElement|null}
*/
function ensureMenuToggle () {
var existing = $$('#menu-toggle');
if (existing) return existing;
var toggle = document.createElement('div');
toggle.id = 'menu-toggle';
toggle.setAttribute('role', 'button');
toggle.setAttribute('tabindex', '0');
toggle.setAttribute('aria-label', 'Toggle menu');
toggle.className = 'menu-toggle';
// Create bars
var bar1 = document.createElement('div'); bar1.className = 'bar1';
var bar2 = document.createElement('div'); bar2.className = 'bar2';
var bar3 = document.createElement('div'); bar3.className = 'bar3';
toggle.appendChild(bar1);
toggle.appendChild(bar2);
toggle.appendChild(bar3);
// Click and keyboard handling
function toggleHandler (e) {
// Only handle left click or Enter/Space key
if (e.type === 'click' || e.key === 'Enter' || e.key === ' ') {
mobileMenuToggle();
e.preventDefault();
}
}
toggle.addEventListener('click', toggleHandler, false);
toggle.addEventListener('keydown', toggleHandler, false);
return toggle;
}
/**
* Toggle the mobile menu state by toggling the 'open-toggle' class on #mw-panel
*/
function mobileMenuToggle () {
var panel = $$('#mw-panel');
if (!panel) return;
panel.classList.toggle('open-toggle');
}
/**
* Remove the mobile menu open state when window is wide
*/
function handleResize () {
var panel = $$('#mw-panel');
if (!panel) return;
if (window.innerWidth > 800) {
panel.classList.remove('open-toggle');
}
}
/**
* Copy anchors from a source selector into a target ul inside #p-cactions.
* Keeps existing anchors' href and text.
* @param {string} sourceSelector - e.g. "#p-views a"
*/
function copyAnchorsToPCActions (sourceSelector) {
var anchors = $$$ (sourceSelector);
if (!anchors || anchors.length === 0) return;
var pCActions = $$('#p-cactions');
if (!pCActions) return;
// Find or create inner ul (the original code assumed p-cactions has a ul)
var ul = pCActions.querySelector('ul');
if (!ul) {
ul = document.createElement('ul');
pCActions.appendChild(ul);
}
// Append items (avoid duplicating items by checking href+text)
Array.prototype.forEach.call(anchors, function (a) {
if (!(a && a.href)) return;
// Skip if an identical item already exists
var exists = Array.prototype.some.call(ul.querySelectorAll('a'), function (x) {
return x.href === a.href && x.textContent.trim() === a.textContent.trim();
});
if (exists) return;
var li = document.createElement('li');
var newA = document.createElement('a');
newA.href = a.href;
newA.textContent = a.textContent.trim();
// preserve title if present
if (a.title) newA.title = a.title;
li.appendChild(newA);
ul.appendChild(li);
});
// Make p-cactions visible if there are items
if (ul.children.length > 0) {
pCActions.style.display = 'block';
}
}
/**
* Copy namespace anchors into p-cactions (same as above but from #p-namespaces a)
*/
function copyNamespacesToPCActions () {
copyAnchorsToPCActions('#p-namespaces a');
}
/**
* Take the first anchor inside #p-namespaces, shorten its text at the last space,
* create an anchor and append into the span inside #p-namespaces.
*/
function updateNamespacesFirstElementText () {
var namespaces = $$('#p-namespaces');
if (!namespaces) return;
var anchors = namespaces.querySelectorAll('a');
if (!anchors || anchors.length === 0) return;
var firstAnchor = anchors[0];
var rawText = firstAnchor.textContent.trim();
var newText = rawText;
var lastSpaceIndex = rawText.lastIndexOf(' ');
if (lastSpaceIndex > 0) {
newText = rawText.substring(0, lastSpaceIndex);
}
var span = namespaces.querySelector('span');
if (!span) {
// if span doesn't exist, create and append to #p-namespaces
span = document.createElement('span');
namespaces.appendChild(span);
}
// Create text anchor
var textnodeHref = document.createElement('a');
textnodeHref.href = firstAnchor.href || '#';
textnodeHref.title = firstAnchor.title || '';
textnodeHref.textContent = newText.length > 0 ? newText : rawText;
// Remove previous appended anchor if it exists to avoid duplication
// (Assume it had no special id)
var old = span.querySelector('a');
if (old) span.removeChild(old);
span.appendChild(textnodeHref);
}
/**
* Show p-cactions only when #p-views has anchors (mirrors original logic)
*/
function revealPCActionsIfPViewsNotEmpty () {
var pViews = $$('#p-views');
if (!pViews) return;
var anchors = pViews.querySelectorAll('a');
var pCActions = $$('#p-cactions');
if (!pCActions) return;
if (anchors && anchors.length > 0) {
pCActions.style.display = 'block';
}
}
/**
* Initialization run on DOMContentLoaded
*/
function init () {
// Insert menu-toggle into #mw-head and #mw-panel (if they exist)
var toggle = ensureMenuToggle();
var mwHead = $$('#mw-head');
var mwPanel = $$('#mw-panel');
// Insert a clone (or same node) into both places. Use clones to keep two elements.
if (mwHead) {
// Only append if not already present
if (!mwHead.querySelector('#menu-toggle')) {
mwHead.appendChild(toggle.cloneNode(true));
}
}
if (mwPanel) {
if (!mwPanel.querySelector('#menu-toggle')) {
mwPanel.appendChild(toggle.cloneNode(true));
}
}
// Setup copy operations (safe-guarded)
copyAnchorsToPCActions('#p-views a');
copyNamespacesToPCActions();
updateNamespacesFirstElementText();
revealPCActionsIfPViewsNotEmpty();
// Remove open state on resize > 800
window.addEventListener('resize', handleResize, false);
// A small safety: also run handleResize immediately to ensure correct state on load
handleResize();
}
// Run when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
mw.hook('wikipage.content').add(function () {
const track = document.querySelector('.ts-device-track');
if (!track) return;
track.addEventListener('mouseenter', () => {
track.style.animationPlayState = 'paused';
});
track.addEventListener('mouseleave', () => {
track.style.animationPlayState = 'running';
});
});