Jump to content

MediaWiki:Common.js: Difference between revisions

From Transight Wiki
No edit summary
No edit summary
 
(45 intermediate revisions by the same user not shown)
Line 19: Line 19:
});
});


 
/* === Transight site-wide footer with HOLI effects === */
 
/* === Transight site-wide footer (robust body-append) === */
mw.loader.using(['mediawiki.util']).then(function () {
mw.loader.using(['mediawiki.util']).then(function () {


   function injectTsFooter() {
   function injectTsFooter() {
     if (document.getElementById('ts-footer')) return; // avoid duplicates
     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">\
         <li class="ts-footer__title">PRODUCTS</li>\
         <li class="ts-footer__title">PRODUCTS</li>\
         <li><a href="/wiki/Trackers">Trackers</a></li>\
         <li><a href="https://telematics.transight.com/Trackers">Trackers</a></li>\
         <li><a href="/wiki/Accessories">Accessories</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>\
         <li><a href="https://transight.com/products/cloud-iot-platform/">Cloud IoT Solutions</a></li>\
       </ul>\
       </ul>\
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="/wiki/WIKI_KNOWLEDGE_BASE">Wiki Knowledge Base</a></li>\
         <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 class="ts-footer__legal"2024, Transight | <a href="/wiki/Privacy">Privacy</a> | <a href="/wiki/Cookies">Cookies</a> | <a href="/wiki/Policies">All Policies</a></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>';


     // Always append at the very end of <body> so no page element can constrain width
     document.body.insertAdjacentHTML("beforeend", html);
     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;


     // Hide any built-in wiki footers
     // Remove previous appended anchor if it exists to avoid duplication
     document.querySelectorAll('footer.mw-footer, .mw-footer, #mw-footer, .mw-footer-container')
     // (Assume it had no special id)
      .forEach(function (el) { el.style.display = 'none'; });
    var old = span.querySelector('a');
    if (old) span.removeChild(old);
 
    span.appendChild(textnodeHref);
   }
   }


   if (document.readyState === 'loading') {
   /**
     document.addEventListener('DOMContentLoaded', injectTsFooter);
  * Show p-cactions only when #p-views has anchors (mirrors original logic)
  } else {
  */
     injectTsFooter();
  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();
  }


/* === Inject floating "Go to our web" button (robust) === */
  // Run when DOM is ready
mw.loader.using(['mediawiki.util']).then(function () {
  if (document.readyState === 'loading') {
   if (document.getElementById('ts-webfab')) return;
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
   }
})();


   var url = 'https://transight.com/';
mw.hook('wikipage.content').add(function () {
   const track = document.querySelector('.ts-device-track');
  if (!track) return;


   var html = ''
   track.addEventListener('mouseenter', () => {
    + '<div id="ts-webfab" class="ts-webfab">'
     track.style.animationPlayState = 'paused';
    + '  <a class="ts-webfab__btn" href="' + url + '" target="_blank" rel="noopener" aria-label="Go to our website"></a>'
  });
     + '  <a class="ts-webfab__bubble" href="' + url + '" target="_blank" rel="noopener"><span>Go to our web</span></a>'
    + '</div>';


   document.body.insertAdjacentHTML('beforeend', html);
   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';
  });
});