Jump to content

User:Udays108/common.js: Difference between revisions

From BioMicro Center
Udays108 (talk | contribs)
 
Udays108 (talk | contribs)
No edit summary
Line 2: Line 2:
   MIT BioMicro Center — MediaWiki Custom Scripts
   MIT BioMicro Center — MediaWiki Custom Scripts
   Skin: Vector-2022
   Skin: Vector-2022
   Scope: User:USERNAME/common.js  (personal preview only)
   Scope: User:USERNAME/common.js  (personal preview)
  Promote to MediaWiki:Common.js for site-wide deployment
   ============================================================ */
   ============================================================ */


mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
   'use strict';
   'use strict';
  var WIKI = 'https://bmcwiki.mit.edu';
  /* Nav definition — mirrors the mockup structure exactly.
    Update href values if your wiki uses different page titles. */
  var NAV = [
    {
      label: 'About',
      href:  WIKI + '/index.php/BioMicroCenter:About'
    },
    {
      label: 'News',
      href:  WIKI + '/index.php/BioMicroCenter:News',
      children: [
        { label: 'Latest News',      href: WIKI + '/index.php/BioMicroCenter:News' },
        { label: 'Seminars',          href: WIKI + '/index.php/BioMicroCenter:Seminars' },
        { label: 'Classes & Training',href: 'https://igb.mit.edu/mini-courses', external: true }
      ]
    },
    {
      label: 'Services',
      href:  WIKI + '/index.php/BioMicroCenter:Assisted_Services',
      children: [
        { label: 'Walkup',        href: WIKI + '/index.php/BioMicroCenter:Walk-up_Services' },
        { label: 'Assisted',      href: WIKI + '/index.php/BioMicroCenter:Assisted_Services' },
        { label: 'Consumables',  href: WIKI + '/index.php/BioMicroCenter:Consumables' },
        { label: 'Training',      href: 'https://igb.mit.edu/mini-courses', external: true },
        { label: 'Informatics',  href: 'https://igb.mit.edu/', external: true }
      ]
    },
    {
      label: 'Submission',
      href:  WIKI + '/index.php/BioMicroCenter:Submission',
      children: [
        { label: 'MIT Users',    group: true },
        { label: 'Submit a Sample',  href: WIKI + '/index.php/BioMicroCenter:Submission' },
        { label: 'MIT Pricing',      href: WIKI + '/index.php/MIT:Pricing', external: true },
        { divider: true },
        { label: 'Non-MIT Users', group: true },
        { label: 'External Submission', href: WIKI + '/index.php/BioMicroCenter:Submission' },
        { label: 'External Pricing',    href: WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing', highlight: true }
      ]
    },
    {
      label: 'Staff',
      href:  WIKI + '/index.php/BioMicroCenter:Staff'
    },
    {
      label: 'Resources',
      href:  WIKI + '/index.php/BioMicroCenter:FAQ',
      children: [
        { label: 'FAQs',                  href: WIKI + '/index.php/BioMicroCenter:FAQ' },
        { label: 'Forms',                  href: WIKI + '/index.php/BioMicroCenter:Forms' },
        { label: 'Grant Support & Pricing',href: WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing' },
        { label: 'Acknowledgements',      href: WIKI + '/index.php/BioMicroCenter:Acknowledgement', external: true }
      ]
    }
  ];


   /* ── Helpers ─────────────────────────────────────────────── */
   /* ── Helpers ─────────────────────────────────────────────── */


   function el( tag, props, children ) {
   function make( tag, cls, html ) {
     var node = document.createElement( tag );
     var n = document.createElement( tag );
     Object.keys( props || {} ).forEach( function ( k ) {
     if ( cls ) n.className = cls;
      if ( k === 'style' && typeof props[ k ] === 'object' ) {
     if ( html ) n.innerHTML = html;
        Object.assign( node.style, props[ k ] );
     return n;
      } else if ( k === 'className' ) {
        node.className = props[ k ];
      } else {
        node.setAttribute( k, props[ k ] );
      }
    } );
     ( children || [] ).forEach( function ( c ) {
      if ( typeof c === 'string' ) {
        node.appendChild( document.createTextNode( c ) );
      } else {
        node.appendChild( c );
      }
    } );
     return node;
   }
   }


  /* Detect if a nav item matches the current page */
  function isActive( href ) {
    return window.location.href.indexOf( href ) !== -1;
  }


  /* ── 1. Top Contact Bar ──────────────────────────────────── */
  /* Injects a crimson bar above the header with email/phone/location */
  ( function injectTopbar() {
    var header = document.querySelector( '.vector-header' );
    if ( !header || document.querySelector( '.bmc-topbar' ) ) return;


     var topbar = document.createElement( 'div' );
  /* ── 1. Build and Inject Topbar ──────────────────────────── */
    topbar.className = 'bmc-topbar';
  function injectTopbar() {
     topbar.innerHTML =
     var bar = make( 'div', 'bmc-topbar' );
       '<div class="bmc-topbar-inner">' +
     bar.innerHTML =
       '<div class="bmc-inner">' +
         '<a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a>' +
         '<a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a>' +
         '<span class="bmc-sep">|</span>' +
         '<span class="bmc-sep">|</span>617-715-4533' +
        '617-715-4533' +
         '<span class="bmc-sep">|</span>Building 68-322' +
         '<span class="bmc-sep">|</span>' +
        'Building 68-322' +
       '</div>';
       '</div>';
     header.parentNode.insertBefore( topbar, header );
     document.body.insertBefore( bar, document.body.firstChild );
  }


    /* Inject inline styles — common.css won't reach injected elements
      before the DOM settles on first load */
    mw.util.addCSS(
      '.bmc-topbar{background:#a31f34;color:#fff;font-size:.75rem;' +
      'padding:6px 0;text-align:right;font-family:\'Helvetica Neue\',Arial,sans-serif}' +
      '.bmc-topbar-inner{max-width:1140px;margin:0 auto;padding:0 24px}' +
      '.bmc-topbar a{color:#fff;opacity:.85;text-decoration:none}' +
      '.bmc-topbar a:hover{opacity:1}' +
      '.bmc-sep{margin:0 6px;opacity:.5}'
    );
  }() );


  /* ── 2. Build Navigation HTML ────────────────────────────── */
  function buildNav() {
    var ul = make( 'ul' );


  /* ── 2. Active Navigation Highlight ─────────────────────── */
    NAV.forEach( function ( item ) {
  /* Marks the sidebar nav link that matches the current wiki page */
      var li = make( 'li' );
      var pageActive = isActive( item.href );


  ( function markActiveNav() {
      if ( item.children ) {
    var pageName = mw.config.get( 'wgPageName' );
        li.className = 'bmc-dropdown' + ( pageActive ? ' bmc-active' : '' );
    if ( !pageName ) return;
        li.innerHTML = '<a href="' + item.href + '">' + item.label + '</a>';


    /* Normalise: spaces → underscores, lower-case for comparison */
        var menu = make( 'div', 'bmc-dropdown-menu' );
    var norm = pageName.replace( / /g, '_' ).toLowerCase();
        item.children.forEach( function ( c ) {
          if ( c.divider ) {
            menu.appendChild( make( 'div', 'bmc-divider' ) );
          } else if ( c.group ) {
            var g = make( 'div', 'bmc-group-label' );
            g.textContent = c.label;
            menu.appendChild( g );
          } else {
            var a = make( 'a' );
            a.href = c.href;
            a.textContent = c.label;
            if ( c.external ) a.target = '_blank';
            if ( c.highlight ) { a.style.fontWeight = '600'; a.style.color = '#a31f34'; }
            menu.appendChild( a );
          }
        } );
        li.appendChild( menu );
      } else {
        li.className = pageActive ? 'bmc-active' : '';
        li.innerHTML = '<a href="' + item.href + '">' + item.label + '</a>';
      }


    var links = document.querySelectorAll(
      ul.appendChild( li );
      '#p-navigation .mw-list-item a, ' +
     } );
      '.vector-main-menu .mw-list-item a'
     );


     links.forEach( function ( link ) {
     return ul;
      var href = ( link.getAttribute( 'href' ) || '' ).toLowerCase();
  }
      /* Match on the last path segment (/wiki/PageName) */
 
      if ( href.indexOf( norm ) !== -1 ) {
 
        var li = link.closest( 'li' );
  /* ── 3. Build and Inject Header ──────────────────────────── */
        if ( li ) li.classList.add( 'bmc-active' );
  function injectHeader() {
      }
    var header = make( 'div', 'bmc-header' );
    } );
    var inner  = make( 'div', 'bmc-header-inner' );
  }() );


    /* Logo */
    var logo = make( 'a', 'bmc-logo' );
    logo.href = WIKI;
    logo.innerHTML =
      '<div class="bmc-logo-mark">BMC</div>' +
      '<div class="bmc-logo-text">' +
        '<div class="bmc-logo-name">MIT BioMicro Center</div>' +
        '<div class="bmc-logo-sub">Integrated Genomics Core Facility</div>' +
      '</div>';


  /* ── 3. Scroll Offset Fix (sticky header) ────────────────── */
    /* Nav */
  /* Vector-2022's sticky header is ~50–68px tall. Without this fix
    var nav = make( 'nav', 'bmc-nav' );
    clicking a TOC anchor scrolls the heading behind the header. */
    nav.appendChild( buildNav() );


  ( function fixScrollOffset() {
     /* Right side: move Vector's search + user links here */
     var OFFSET = 88; /* header + topbar combined */
    var right = make( 'div', 'bmc-header-right' );


     document.addEventListener( 'click', function ( e ) {
     var vectorSearch = document.querySelector( '.vector-search-box, #p-search' );
       var anchor = e.target.closest( 'a[href^="#"]' );
    if ( vectorSearch ) {
       if ( !anchor ) return;
       vectorSearch.style.cssText = 'margin:0;padding:0;';
       right.appendChild( vectorSearch );
    }


      var targetId = anchor.getAttribute( 'href' ).slice( 1 );
    var vectorUser = document.querySelector( '.vector-user-links, #p-personal' );
      if ( !targetId ) return;
    if ( vectorUser ) {
       var target = document.getElementById( targetId ) ||
       vectorUser.style.cssText = 'margin:0;padding:0;';
                  document.querySelector( '[name="' + CSS.escape( targetId ) + '"]' );
       right.appendChild( vectorUser );
       if ( !target ) return;
    }


      e.preventDefault();
    inner.appendChild( logo );
      var top = target.getBoundingClientRect().top + window.pageYOffset - OFFSET;
    inner.appendChild( nav );
      window.scrollTo( { top: top, behavior: 'smooth' } );
    inner.appendChild( right );
    header.appendChild( inner );


      /* Update URL hash without triggering browser jump */
    /* Insert after topbar */
      if ( history.pushState ) {
    var topbar = document.querySelector( '.bmc-topbar' );
        history.pushState( null, null, '#' + targetId );
    if ( topbar && topbar.nextSibling ) {
       }
      document.body.insertBefore( header, topbar.nextSibling );
     } );
    } else {
   }() );
       document.body.insertBefore( header, document.body.firstChild );
     }
   }




   /* ── 4. Tab Panel System ─────────────────────────────────── */
   /* ── 4. Build and Inject Hero Banner ─────────────────────── */
   /* Activates the .tab-btn / .tab-panel system used on the
   function injectHero() {
    pricing page. Does nothing if no .tab-btn elements exist. */
    /* Don't show hero on Special pages or edit forms */
    var ns = mw.config.get( 'wgNamespaceNumber' );
    if ( ns < 0 ) return; /* Special: pages */
    if ( document.querySelector( '#editform, .mw-editform' ) ) return;


  ( function initTabs() {
    /* Get the page title from the hidden h1 */
     var tabBtns = document.querySelectorAll( '.tab-btn' );
     var h1 = document.querySelector( 'h1.firstHeading, .mw-first-heading' );
     if ( !tabBtns.length ) return;
     var title = h1 ? h1.textContent.trim() : mw.config.get( 'wgTitle' );


     var tabPanels = document.querySelectorAll( '.tab-panel' );
    /* Build breadcrumb: Home > Title */
     var hero = make( 'div', 'bmc-hero' );
    hero.innerHTML =
      '<div class="bmc-inner">' +
        '<div class="bmc-breadcrumb">' +
          '<a href="' + WIKI + '">Home</a>' +
          '<span class="bmc-sep">›</span>' +
          mw.html.escape( title ) +
        '</div>' +
        '<h1>' + mw.html.escape( title ) + '</h1>' +
      '</div>';


     /* Activate first tab by default if none is active */
     /* Insert before the main page container */
     if ( !document.querySelector( '.tab-btn.active' ) ) {
     var container = document.querySelector( '.mw-page-container' );
      tabBtns[ 0 ].classList.add( 'active' );
     if ( container ) {
    }
       container.parentNode.insertBefore( hero, container );
     if ( !document.querySelector( '.tab-panel.active' ) ) {
       tabPanels[ 0 ] && tabPanels[ 0 ].classList.add( 'active' );
     }
     }
  }


    tabBtns.forEach( function ( btn ) {
      btn.addEventListener( 'click', function () {
        var target = btn.dataset.tab;


        tabBtns.forEach( function ( t ) { t.classList.remove( 'active' ); } );
  /* ── 5. Wrap Content in Our Layout Grid ──────────────────── */
        tabPanels.forEach( function ( p ) { p.classList.remove( 'active' ); } );
  function wrapLayout() {
    /* Vector-2022 puts TOC and content inside .mw-content-container.
      We pull them into our bmc-page-layout grid. */
    var contentContainer = document.querySelector( '.mw-content-container' );
    if ( !contentContainer ) return;


        btn.classList.add( 'active' );
    var layout = make( 'div', 'bmc-page-layout' );
        var panel = document.getElementById( 'tab-' + target );
        if ( panel ) panel.classList.add( 'active' );


        /* Scroll to tab bar, accounting for sticky header */
    /* Left: sidebar — use Vector's TOC panel if it exists */
        var tabBarWrap = document.querySelector( '.tab-bar-wrap' );
    var sidebar = make( 'div', 'bmc-sidebar' );
        if ( tabBarWrap ) {
    var toc = document.querySelector( '#mw-panel-toc, .vector-toc-landmark' );
          var top = tabBarWrap.getBoundingClientRect().top + window.pageYOffset - 68;
    if ( toc ) {
          window.scrollTo( { top: top, behavior: 'smooth' } );
      sidebar.appendChild( toc );
        }
    }
      } );
     layout.appendChild( sidebar );
     } );
  }() );


    /* Right: content column — move Vector's #mw-content into it */
    var contentCol = make( 'div', 'bmc-content-col' );
    var mwContent = document.querySelector( '#mw-content' );
    if ( mwContent ) {
      contentCol.appendChild( mwContent );
    }
    layout.appendChild( contentCol );


  /* ── 5. BMC Logo Mark ────────────────────────────────────── */
    /* Replace the content container's children with our layout */
  /* Injects the red square "BMC" mark next to the wiki wordmark
    contentContainer.innerHTML = '';
    if the logo element is a plain text/image logo.             */
    contentContainer.appendChild( layout );
  }


  ( function injectLogoMark() {
    var logoLink = document.querySelector( '.mw-logo' );
    if ( !logoLink || document.querySelector( '.bmc-logo-mark' ) ) return;


     var mark = document.createElement( 'div' );
  /* ── 6. Build and Inject Footer ──────────────────────────── */
     mark.className = 'bmc-logo-mark';
  function injectFooter() {
    mark.textContent = 'BMC';
     var footer = make( 'div', 'bmc-footer' );
     footer.innerHTML =
      '<div class="bmc-footer-inner">' +
        '<div>' +
          '<h4>MIT BioMicro Center</h4>' +
          '<ul>' +
            '<li>Building 68-322</li>' +
            '<li>Cambridge, MA 02139</li>' +
            '<li><a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a></li>' +
            '<li>617-715-4533</li>' +
          '</ul>' +
        '</div>' +
        '<div>' +
          '<h4>Services</h4>' +
          '<ul>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Walk-up_Services">Walk-up</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Assisted_Services">Assisted</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Consumables">Consumables</a></li>' +
            '<li><a href="https://igb.mit.edu/" target="_blank">Informatics</a></li>' +
          '</ul>' +
        '</div>' +
        '<div>' +
          '<h4>Resources</h4>' +
          '<ul>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:FAQ">FAQs</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Forms">Forms</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing">Grant Support &amp; Pricing</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Acknowledgement">Acknowledgements</a></li>' +
          '</ul>' +
        '</div>' +
      '</div>' +
      '<div class="bmc-footer-bottom">' +
        '<span>&copy; ' + new Date().getFullYear() + ' MIT BioMicro Center</span>' +
        '<span><a href="https://accessibility.mit.edu" target="_blank">Accessibility</a></span>' +
      '</div>';


     mw.util.addCSS(
     document.body.appendChild( footer );
      '.bmc-logo-mark{' +
  }
        'width:36px;height:36px;background:#a31f34;border-radius:4px;' +
        'display:flex;align-items:center;justify-content:center;' +
        'color:#fff;font-weight:700;font-size:.9rem;letter-spacing:-.5px;' +
        'flex-shrink:0;font-family:\'Helvetica Neue\',Arial,sans-serif' +
      '}' +
      '.mw-logo{display:flex!important;align-items:center!important;gap:10px!important}'
    );


    /* Prepend mark before the existing logo image/text */
    logoLink.insertBefore( mark, logoLink.firstChild );
  }() );


  /* ── 7. Scroll Offset Fix (anchor links) ─────────────────── */
  function fixScrollOffset() {
    var OFFSET = 88;
    document.addEventListener( 'click', function ( e ) {
      var anchor = e.target.closest( 'a[href^="#"]' );
      if ( !anchor ) return;
      var id = anchor.getAttribute( 'href' ).slice( 1 );
      if ( !id ) return;
      var target = document.getElementById( id ) ||
                  document.querySelector( '[name="' + CSS.escape( id ) + '"]' );
      if ( !target ) return;
      e.preventDefault();
      window.scrollTo( {
        top: target.getBoundingClientRect().top + window.pageYOffset - OFFSET,
        behavior: 'smooth'
      } );
      if ( history.pushState ) history.pushState( null, null, '#' + id );
    } );
  }


  /* ── 6. Wrap Long Tables for Mobile Scroll ───────────────── */
  /* Wraps .wikitable in a scrollable div so wide tables don't
    break the layout on small screens.                        */


   ( function wrapTables() {
   /* ── 8. Tab Panel System ─────────────────────────────────── */
     document.querySelectorAll( '.mw-parser-output .wikitable' ).forEach( function ( table ) {
  function initTabs() {
       if ( table.closest( '.wikitable-wrapper' ) ) return;
     var btns  = document.querySelectorAll( '.tab-btn' );
      var wrapper = document.createElement( 'div' );
    var panels = document.querySelectorAll( '.tab-panel' );
      wrapper.className = 'wikitable-wrapper';
    if ( !btns.length ) return;
      table.parentNode.insertBefore( wrapper, table );
 
       wrapper.appendChild( table );
    if ( !document.querySelector( '.tab-btn.active' ) ) btns[ 0 ].classList.add( 'active' );
    if ( !document.querySelector( '.tab-panel.active' ) && panels[ 0 ] ) panels[ 0 ].classList.add( 'active' );
 
    btns.forEach( function ( btn ) {
       btn.addEventListener( 'click', function () {
        btns.forEach( function ( t ) { t.classList.remove( 'active' ); } );
        panels.forEach( function ( p ) { p.classList.remove( 'active' ); } );
        btn.classList.add( 'active' );
        var panel = document.getElementById( 'tab-' + btn.dataset.tab );
        if ( panel ) panel.classList.add( 'active' );
        var wrap = document.querySelector( '.tab-bar-wrap' );
        if ( wrap ) window.scrollTo( { top: wrap.getBoundingClientRect().top + window.pageYOffset - 68, behavior: 'smooth' } );
       } );
     } );
     } );
  }


    mw.util.addCSS(
  /* ── Run Everything ──────────────────────────────────────── */
      '.wikitable-wrapper{overflow-x:auto;border-radius:8px;' +
  injectTopbar();
      'border:1px solid #e4e4e4;margin-bottom:26px}'
  injectHeader();
    );
  injectHero();
   }() );
  wrapLayout();
  injectFooter();
   fixScrollOffset();
  initTabs();


} );
} );

Revision as of 19:49, 8 May 2026

/* ============================================================
   MIT BioMicro Center — MediaWiki Custom Scripts
   Skin: Vector-2022
   Scope: User:USERNAME/common.js  (personal preview)
   ============================================================ */

mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
  'use strict';

  var WIKI = 'https://bmcwiki.mit.edu';

  /* Nav definition — mirrors the mockup structure exactly.
     Update href values if your wiki uses different page titles. */
  var NAV = [
    {
      label: 'About',
      href:  WIKI + '/index.php/BioMicroCenter:About'
    },
    {
      label: 'News',
      href:  WIKI + '/index.php/BioMicroCenter:News',
      children: [
        { label: 'Latest News',       href: WIKI + '/index.php/BioMicroCenter:News' },
        { label: 'Seminars',          href: WIKI + '/index.php/BioMicroCenter:Seminars' },
        { label: 'Classes & Training',href: 'https://igb.mit.edu/mini-courses', external: true }
      ]
    },
    {
      label: 'Services',
      href:  WIKI + '/index.php/BioMicroCenter:Assisted_Services',
      children: [
        { label: 'Walkup',        href: WIKI + '/index.php/BioMicroCenter:Walk-up_Services' },
        { label: 'Assisted',      href: WIKI + '/index.php/BioMicroCenter:Assisted_Services' },
        { label: 'Consumables',   href: WIKI + '/index.php/BioMicroCenter:Consumables' },
        { label: 'Training',      href: 'https://igb.mit.edu/mini-courses', external: true },
        { label: 'Informatics',   href: 'https://igb.mit.edu/', external: true }
      ]
    },
    {
      label: 'Submission',
      href:  WIKI + '/index.php/BioMicroCenter:Submission',
      children: [
        { label: 'MIT Users',     group: true },
        { label: 'Submit a Sample',   href: WIKI + '/index.php/BioMicroCenter:Submission' },
        { label: 'MIT Pricing',       href: WIKI + '/index.php/MIT:Pricing', external: true },
        { divider: true },
        { label: 'Non-MIT Users', group: true },
        { label: 'External Submission', href: WIKI + '/index.php/BioMicroCenter:Submission' },
        { label: 'External Pricing',    href: WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing', highlight: true }
      ]
    },
    {
      label: 'Staff',
      href:  WIKI + '/index.php/BioMicroCenter:Staff'
    },
    {
      label: 'Resources',
      href:  WIKI + '/index.php/BioMicroCenter:FAQ',
      children: [
        { label: 'FAQs',                   href: WIKI + '/index.php/BioMicroCenter:FAQ' },
        { label: 'Forms',                  href: WIKI + '/index.php/BioMicroCenter:Forms' },
        { label: 'Grant Support & Pricing',href: WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing' },
        { label: 'Acknowledgements',       href: WIKI + '/index.php/BioMicroCenter:Acknowledgement', external: true }
      ]
    }
  ];


  /* ── Helpers ─────────────────────────────────────────────── */

  function make( tag, cls, html ) {
    var n = document.createElement( tag );
    if ( cls ) n.className = cls;
    if ( html ) n.innerHTML = html;
    return n;
  }

  /* Detect if a nav item matches the current page */
  function isActive( href ) {
    return window.location.href.indexOf( href ) !== -1;
  }


  /* ── 1. Build and Inject Topbar ──────────────────────────── */
  function injectTopbar() {
    var bar = make( 'div', 'bmc-topbar' );
    bar.innerHTML =
      '<div class="bmc-inner">' +
        '<a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a>' +
        '<span class="bmc-sep">|</span>617-715-4533' +
        '<span class="bmc-sep">|</span>Building 68-322' +
      '</div>';
    document.body.insertBefore( bar, document.body.firstChild );
  }


  /* ── 2. Build Navigation HTML ────────────────────────────── */
  function buildNav() {
    var ul = make( 'ul' );

    NAV.forEach( function ( item ) {
      var li = make( 'li' );
      var pageActive = isActive( item.href );

      if ( item.children ) {
        li.className = 'bmc-dropdown' + ( pageActive ? ' bmc-active' : '' );
        li.innerHTML = '<a href="' + item.href + '">' + item.label + '</a>';

        var menu = make( 'div', 'bmc-dropdown-menu' );
        item.children.forEach( function ( c ) {
          if ( c.divider ) {
            menu.appendChild( make( 'div', 'bmc-divider' ) );
          } else if ( c.group ) {
            var g = make( 'div', 'bmc-group-label' );
            g.textContent = c.label;
            menu.appendChild( g );
          } else {
            var a = make( 'a' );
            a.href = c.href;
            a.textContent = c.label;
            if ( c.external ) a.target = '_blank';
            if ( c.highlight ) { a.style.fontWeight = '600'; a.style.color = '#a31f34'; }
            menu.appendChild( a );
          }
        } );
        li.appendChild( menu );
      } else {
        li.className = pageActive ? 'bmc-active' : '';
        li.innerHTML = '<a href="' + item.href + '">' + item.label + '</a>';
      }

      ul.appendChild( li );
    } );

    return ul;
  }


  /* ── 3. Build and Inject Header ──────────────────────────── */
  function injectHeader() {
    var header = make( 'div', 'bmc-header' );
    var inner  = make( 'div', 'bmc-header-inner' );

    /* Logo */
    var logo = make( 'a', 'bmc-logo' );
    logo.href = WIKI;
    logo.innerHTML =
      '<div class="bmc-logo-mark">BMC</div>' +
      '<div class="bmc-logo-text">' +
        '<div class="bmc-logo-name">MIT BioMicro Center</div>' +
        '<div class="bmc-logo-sub">Integrated Genomics Core Facility</div>' +
      '</div>';

    /* Nav */
    var nav = make( 'nav', 'bmc-nav' );
    nav.appendChild( buildNav() );

    /* Right side: move Vector's search + user links here */
    var right = make( 'div', 'bmc-header-right' );

    var vectorSearch = document.querySelector( '.vector-search-box, #p-search' );
    if ( vectorSearch ) {
      vectorSearch.style.cssText = 'margin:0;padding:0;';
      right.appendChild( vectorSearch );
    }

    var vectorUser = document.querySelector( '.vector-user-links, #p-personal' );
    if ( vectorUser ) {
      vectorUser.style.cssText = 'margin:0;padding:0;';
      right.appendChild( vectorUser );
    }

    inner.appendChild( logo );
    inner.appendChild( nav );
    inner.appendChild( right );
    header.appendChild( inner );

    /* Insert after topbar */
    var topbar = document.querySelector( '.bmc-topbar' );
    if ( topbar && topbar.nextSibling ) {
      document.body.insertBefore( header, topbar.nextSibling );
    } else {
      document.body.insertBefore( header, document.body.firstChild );
    }
  }


  /* ── 4. Build and Inject Hero Banner ─────────────────────── */
  function injectHero() {
    /* Don't show hero on Special pages or edit forms */
    var ns = mw.config.get( 'wgNamespaceNumber' );
    if ( ns < 0 ) return; /* Special: pages */
    if ( document.querySelector( '#editform, .mw-editform' ) ) return;

    /* Get the page title from the hidden h1 */
    var h1 = document.querySelector( 'h1.firstHeading, .mw-first-heading' );
    var title = h1 ? h1.textContent.trim() : mw.config.get( 'wgTitle' );

    /* Build breadcrumb: Home > Title */
    var hero = make( 'div', 'bmc-hero' );
    hero.innerHTML =
      '<div class="bmc-inner">' +
        '<div class="bmc-breadcrumb">' +
          '<a href="' + WIKI + '">Home</a>' +
          '<span class="bmc-sep">›</span>' +
          mw.html.escape( title ) +
        '</div>' +
        '<h1>' + mw.html.escape( title ) + '</h1>' +
      '</div>';

    /* Insert before the main page container */
    var container = document.querySelector( '.mw-page-container' );
    if ( container ) {
      container.parentNode.insertBefore( hero, container );
    }
  }


  /* ── 5. Wrap Content in Our Layout Grid ──────────────────── */
  function wrapLayout() {
    /* Vector-2022 puts TOC and content inside .mw-content-container.
       We pull them into our bmc-page-layout grid. */
    var contentContainer = document.querySelector( '.mw-content-container' );
    if ( !contentContainer ) return;

    var layout = make( 'div', 'bmc-page-layout' );

    /* Left: sidebar — use Vector's TOC panel if it exists */
    var sidebar = make( 'div', 'bmc-sidebar' );
    var toc = document.querySelector( '#mw-panel-toc, .vector-toc-landmark' );
    if ( toc ) {
      sidebar.appendChild( toc );
    }
    layout.appendChild( sidebar );

    /* Right: content column — move Vector's #mw-content into it */
    var contentCol = make( 'div', 'bmc-content-col' );
    var mwContent = document.querySelector( '#mw-content' );
    if ( mwContent ) {
      contentCol.appendChild( mwContent );
    }
    layout.appendChild( contentCol );

    /* Replace the content container's children with our layout */
    contentContainer.innerHTML = '';
    contentContainer.appendChild( layout );
  }


  /* ── 6. Build and Inject Footer ──────────────────────────── */
  function injectFooter() {
    var footer = make( 'div', 'bmc-footer' );
    footer.innerHTML =
      '<div class="bmc-footer-inner">' +
        '<div>' +
          '<h4>MIT BioMicro Center</h4>' +
          '<ul>' +
            '<li>Building 68-322</li>' +
            '<li>Cambridge, MA 02139</li>' +
            '<li><a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a></li>' +
            '<li>617-715-4533</li>' +
          '</ul>' +
        '</div>' +
        '<div>' +
          '<h4>Services</h4>' +
          '<ul>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Walk-up_Services">Walk-up</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Assisted_Services">Assisted</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Consumables">Consumables</a></li>' +
            '<li><a href="https://igb.mit.edu/" target="_blank">Informatics</a></li>' +
          '</ul>' +
        '</div>' +
        '<div>' +
          '<h4>Resources</h4>' +
          '<ul>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:FAQ">FAQs</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Forms">Forms</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Grant_Support_%26_Pricing">Grant Support &amp; Pricing</a></li>' +
            '<li><a href="' + WIKI + '/index.php/BioMicroCenter:Acknowledgement">Acknowledgements</a></li>' +
          '</ul>' +
        '</div>' +
      '</div>' +
      '<div class="bmc-footer-bottom">' +
        '<span>&copy; ' + new Date().getFullYear() + ' MIT BioMicro Center</span>' +
        '<span><a href="https://accessibility.mit.edu" target="_blank">Accessibility</a></span>' +
      '</div>';

    document.body.appendChild( footer );
  }


  /* ── 7. Scroll Offset Fix (anchor links) ─────────────────── */
  function fixScrollOffset() {
    var OFFSET = 88;
    document.addEventListener( 'click', function ( e ) {
      var anchor = e.target.closest( 'a[href^="#"]' );
      if ( !anchor ) return;
      var id = anchor.getAttribute( 'href' ).slice( 1 );
      if ( !id ) return;
      var target = document.getElementById( id ) ||
                   document.querySelector( '[name="' + CSS.escape( id ) + '"]' );
      if ( !target ) return;
      e.preventDefault();
      window.scrollTo( {
        top: target.getBoundingClientRect().top + window.pageYOffset - OFFSET,
        behavior: 'smooth'
      } );
      if ( history.pushState ) history.pushState( null, null, '#' + id );
    } );
  }


  /* ── 8. Tab Panel System ─────────────────────────────────── */
  function initTabs() {
    var btns   = document.querySelectorAll( '.tab-btn' );
    var panels = document.querySelectorAll( '.tab-panel' );
    if ( !btns.length ) return;

    if ( !document.querySelector( '.tab-btn.active' ) ) btns[ 0 ].classList.add( 'active' );
    if ( !document.querySelector( '.tab-panel.active' ) && panels[ 0 ] ) panels[ 0 ].classList.add( 'active' );

    btns.forEach( function ( btn ) {
      btn.addEventListener( 'click', function () {
        btns.forEach( function ( t ) { t.classList.remove( 'active' ); } );
        panels.forEach( function ( p ) { p.classList.remove( 'active' ); } );
        btn.classList.add( 'active' );
        var panel = document.getElementById( 'tab-' + btn.dataset.tab );
        if ( panel ) panel.classList.add( 'active' );
        var wrap = document.querySelector( '.tab-bar-wrap' );
        if ( wrap ) window.scrollTo( { top: wrap.getBoundingClientRect().top + window.pageYOffset - 68, behavior: 'smooth' } );
      } );
    } );
  }


  /* ── Run Everything ──────────────────────────────────────── */
  injectTopbar();
  injectHeader();
  injectHero();
  wrapLayout();
  injectFooter();
  fixScrollOffset();
  initTabs();

} );