Jump to content

User:Gibcus/vector-2022.js: Difference between revisions

From BioMicro Center
Gibcus (talk | contribs)
 
Gibcus (talk | contribs)
No edit summary
 
(11 intermediate revisions by the same user not shown)
Line 11: Line 11:
  */
  */


window.BMC_DEBUG = true;
( function () {
( function () {
   'use strict';
   'use strict';
  /* ============================================================
    0. WAIT FOR DOM
  ============================================================ */
  mw.hook( 'wikipage.content' ).add( function () {
    BMC.init();
  } );


   /* ============================================================
   /* ============================================================
Line 420: Line 414:
     /* ---- 4a. INIT ---- */
     /* ---- 4a. INIT ---- */
     init: function () {
     init: function () {
      if ( document.getElementById( 'bmc-topnav' ) ) return; // already ran
       this.injectCSS();
       this.injectCSS();
       this.injectNav();
       this.injectNav();
Line 962: Line 957:


   }; // end BMC
   }; // end BMC
  window.BMC = BMC; // expose globally for debugging


   /* ============================================================
   /* ============================================================
     5. KICK OFF
     5. KICK OFF — hook registered AFTER BMC is fully defined
   ============================================================ */
   ============================================================ */
   if ( document.readyState === 'loading' ) {
   mw.hook( 'wikipage.content' ).add( function () {
     document.addEventListener( 'DOMContentLoaded', function () { BMC.init(); } );
    BMC.init();
   } else {
  } );
 
  // Fire immediately — multiple strategies
  function tryInit() {
     if ( document.getElementById( 'bmc-topnav' ) ) return;
    if ( typeof mw !== 'undefined' && mw.config ) { BMC.init(); }
  }
  tryInit();
   setTimeout( tryInit, 500 );
  setTimeout( tryInit, 1500 );
  if ( document.readyState !== 'loading' ) {
     BMC.init();
     BMC.init();
   }
   }


}() );
}() );

Latest revision as of 18:22, 18 February 2026

/**
 * User:Gibcus/common.js
 * MIT BioMicro Center — Modern Wiki Theme Engine
 *
 * Paste this entire file into:
 * https://bmcwiki.mit.edu/index.php/User:Gibcus/common.js
 *
 * It applies to YOUR login session only.
 * To deploy for all users, an admin with editinterface rights
 * must paste it into MediaWiki:Common.js instead.
 */

window.BMC_DEBUG = true;
( function () {
  'use strict';

  /* ============================================================
     1. DESIGN TOKENS
  ============================================================ */
  var TOKENS = {
    black:    '#0d0d0d',
    darkGray: '#1a1a1a',
    midGray:  '#2e2e2e',
    border:   '#e2ddd6',
    bg:       '#f5f3ef',
    bgCard:   '#ffffff',
    gold:     '#b8913a',
    goldHov:  '#d4a84b',
    red:      '#8b1a1a',   // MIT red accent
    text:     '#1c1c1c',
    textMid:  '#555555',
    textLight:'#888888',
    fontBody: "'DM Sans', 'Helvetica Neue', Arial, sans-serif",
    fontMono: "'DM Mono', 'Courier New', monospace",
  };

  /* ============================================================
     2. GLOBAL CSS — injected once, applies to every BMC page
  ============================================================ */
  var GLOBAL_CSS = [
    "@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;1,400&family=DM+Mono:wght@400;500&display=swap');",

    /* --- Reset MediaWiki chrome --- */
    '#mw-navigation, #mw-head, #mw-panel, #mw-sidebar-button,',
    '#mw-sidebar-checkbox, .mw-footer, #footer, #p-logo,',
    '.vector-menu-tabs, .vector-header-start .vector-header-logo,',
    '.mw-portlet-lang, #p-cactions, .vector-page-toolbar { display: none !important; }',

    '#content, #mw-content-text, .mw-body, #mw-body-content,',
    '.mw-body-content { margin: 0 !important; padding: 0 !important;',
    'max-width: none !important; border: none !important; }',

    'body { background: ' + TOKENS.bg + ' !important;',
    'font-family: ' + TOKENS.fontBody + ' !important;',
    'color: ' + TOKENS.text + ' !important; margin: 0 !important; padding: 0 !important; }',

    /* hide edit pencil icons in headings */
    '.mw-editsection { display: none !important; }',

    /* --- BMC Top Nav Bar --- */
    '#bmc-topnav {',
    '  position: sticky; top: 0; z-index: 1000;',
    '  background: ' + TOKENS.black + ';',
    '  display: flex; align-items: center;',
    '  padding: 0 32px; height: 56px;',
    '  box-shadow: 0 1px 0 rgba(255,255,255,0.06);',
    '  gap: 0;',
    '}',
    '#bmc-topnav .bmc-nav-logo {',
    '  font-family: ' + TOKENS.fontBody + ';',
    '  font-size: 0.95em; font-weight: 600;',
    '  color: #fff; text-decoration: none;',
    '  letter-spacing: -0.01em; white-space: nowrap;',
    '  padding-right: 32px;',
    '  border-right: 1px solid #333;',
    '  margin-right: 8px;',
    '  display: flex; align-items: center; gap: 10px;',
    '}',
    '#bmc-topnav .bmc-nav-logo span.accent { color: ' + TOKENS.gold + '; }',
    '#bmc-topnav .bmc-nav-logo img { height: 28px; width: 28px; border-radius: 4px; }',
    '#bmc-nav-links {',
    '  display: flex; align-items: center;',
    '  gap: 2px; flex: 1; overflow-x: auto;',
    '}',
    '#bmc-nav-links a {',
    '  color: #aaa; text-decoration: none;',
    '  font-size: 0.8em; font-weight: 500;',
    '  letter-spacing: 0.03em;',
    '  padding: 6px 13px; border-radius: 6px;',
    '  white-space: nowrap;',
    '  transition: color 0.15s, background 0.15s;',
    '}',
    '#bmc-nav-links a:hover { color: #fff; background: #2a2a2a; }',
    '#bmc-nav-links a.bmc-nav-active { color: ' + TOKENS.gold + '; }',

    '#bmc-nav-right {',
    '  display: flex; align-items: center; gap: 12px; margin-left: auto;',
    '}',
    '#bmc-nav-right a.bmc-ilabs-btn {',
    '  background: ' + TOKENS.gold + ';',
    '  color: ' + TOKENS.black + '; font-weight: 600;',
    '  font-size: 0.75em; letter-spacing: 0.05em;',
    '  padding: 6px 14px; border-radius: 6px;',
    '  text-decoration: none; white-space: nowrap;',
    '  transition: background 0.15s;',
    '}',
    '#bmc-nav-right a.bmc-ilabs-btn:hover { background: ' + TOKENS.goldHov + '; }',
    '#bmc-nav-right a.bmc-nav-search {',
    '  color: #888; font-size: 1.1em;',
    '  text-decoration: none;',
    '  padding: 4px 8px; border-radius: 6px;',
    '  transition: color 0.15s;',
    '}',
    '#bmc-nav-right a.bmc-nav-search:hover { color: #fff; }',

    /* --- Page wrapper --- */
    '#bmc-page-wrap {',
    '  max-width: 1140px; margin: 0 auto;',
    '  padding: 40px 32px 80px;',
    '}',
    '@media (max-width: 768px) {',
    '  #bmc-page-wrap { padding: 24px 16px 60px; }',
    '  #bmc-topnav { padding: 0 16px; }',
    '}',

    /* --- Page title --- */
    '#bmc-page-title {',
    '  margin-bottom: 32px;',
    '}',
    '#bmc-page-title h1 {',
    '  font-size: 2em; font-weight: 600;',
    '  color: ' + TOKENS.black + '; letter-spacing: -0.025em;',
    '  line-height: 1.1; margin: 0 0 6px;',
    '}',
    '#bmc-page-title .bmc-subtitle {',
    '  font-size: 0.92em; color: ' + TOKENS.textMid + ';',
    '  font-weight: 400;',
    '}',

    /* --- Section headings --- */
    '#bmc-page-wrap h2 {',
    '  font-size: 1.1em; font-weight: 600;',
    '  color: ' + TOKENS.black + ';',
    '  letter-spacing: 0.06em; text-transform: uppercase;',
    '  margin: 40px 0 16px;',
    '  padding-bottom: 8px;',
    '  border-bottom: 1px solid ' + TOKENS.border + ';',
    '}',
    '#bmc-page-wrap h3 {',
    '  font-size: 0.95em; font-weight: 600;',
    '  color: ' + TOKENS.textMid + ';',
    '  letter-spacing: 0.04em; text-transform: uppercase;',
    '  margin: 28px 0 12px;',
    '}',

    /* --- Generic body content prose --- */
    '#bmc-page-wrap p {',
    '  font-size: 0.95em; line-height: 1.7;',
    '  color: ' + TOKENS.textMid + '; margin-bottom: 12px;',
    '}',
    '#bmc-page-wrap a {',
    '  color: ' + TOKENS.gold + '; text-decoration: none;',
    '  border-bottom: 1px solid transparent;',
    '  transition: border-color 0.15s;',
    '}',
    '#bmc-page-wrap a:hover { border-bottom-color: ' + TOKENS.gold + '; }',

    /* --- Generic list styling --- */
    '#bmc-page-wrap ul, #bmc-page-wrap ol {',
    '  padding-left: 1.4em; margin-bottom: 12px;',
    '}',
    '#bmc-page-wrap li {',
    '  font-size: 0.92em; line-height: 1.7;',
    '  color: ' + TOKENS.textMid + '; margin-bottom: 3px;',
    '}',

    /* --- Generic wiki tables → clean style --- */
    '#bmc-page-wrap table.wikitable, #bmc-page-wrap table {',
    '  border-collapse: collapse; width: 100%;',
    '  font-size: 0.87em; margin-bottom: 24px;',
    '  background: ' + TOKENS.bgCard + ';',
    '  border-radius: 10px; overflow: hidden;',
    '  border: 1px solid ' + TOKENS.border + ';',
    '  box-shadow: 0 1px 4px rgba(0,0,0,0.05);',
    '}',
    '#bmc-page-wrap table th {',
    '  background: ' + TOKENS.darkGray + ';',
    '  color: #ddd; font-weight: 500;',
    '  padding: 10px 14px; text-align: left;',
    '  font-size: 0.82em; letter-spacing: 0.04em;',
    '  text-transform: uppercase;',
    '}',
    '#bmc-page-wrap table td {',
    '  padding: 9px 14px;',
    '  border-bottom: 1px solid ' + TOKENS.border + ';',
    '  color: ' + TOKENS.text + '; vertical-align: top;',
    '}',
    '#bmc-page-wrap table tr:last-child td { border-bottom: none; }',
    '#bmc-page-wrap table tr:hover td { background: #faf8f5; }',

    /* --- Card component --- */
    '.bmc-card {',
    '  background: ' + TOKENS.bgCard + ';',
    '  border: 1px solid ' + TOKENS.border + ';',
    '  border-radius: 12px;',
    '  padding: 24px;',
    '  box-shadow: 0 1px 4px rgba(0,0,0,0.05);',
    '}',
    '.bmc-card-grid {',
    '  display: grid;',
    '  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));',
    '  gap: 16px; margin-bottom: 32px;',
    '}',

    /* --- Inline badge --- */
    '.bmc-badge {',
    '  display: inline-block;',
    '  font-size: 0.7em; font-weight: 600;',
    '  letter-spacing: 0.06em; text-transform: uppercase;',
    '  padding: 2px 7px; border-radius: 4px;',
    '  background: ' + TOKENS.gold + '22;',
    '  color: ' + TOKENS.gold + '; margin-left: 6px;',
    '  vertical-align: middle;',
    '}',

    /* --- TOC hide (we build nav instead) --- */
    '#toc, .toc { display: none !important; }',

    /* --- Breadcrumb strip under nav --- */
    '#bmc-breadcrumb {',
    '  background: #fff;',
    '  border-bottom: 1px solid ' + TOKENS.border + ';',
    '  padding: 10px 32px;',
    '  font-size: 0.78em;',
    '  color: ' + TOKENS.textLight + ';',
    '  display: flex; align-items: center; gap: 6px;',
    '}',
    '#bmc-breadcrumb a { color: ' + TOKENS.textMid + '; text-decoration: none; }',
    '#bmc-breadcrumb a:hover { color: ' + TOKENS.gold + '; }',
    '#bmc-breadcrumb .sep { color: #ccc; }',

    /* --- Pricing-specific styles (reused from earlier) --- */
    '#bmc-pricing-tier-toggle {',
    '  display: flex; gap: 4px;',
    '  background: ' + TOKENS.darkGray + ';',
    '  border-radius: 10px; padding: 4px;',
    '  margin-bottom: 24px; width: fit-content;',
    '}',
    '#bmc-pricing-tier-toggle button {',
    '  padding: 7px 18px; border: none;',
    '  border-radius: 7px; font-size: 0.8em;',
    '  font-weight: 600; cursor: pointer;',
    '  letter-spacing: 0.04em;',
    '  transition: all 0.15s;',
    '  font-family: ' + TOKENS.fontBody + ';',
    '  background: transparent; color: #777;',
    '}',
    '#bmc-pricing-tier-toggle button.active-core  { background: #1a6b3c; color: #fff; }',
    '#bmc-pricing-tier-toggle button.active-mit   { background: #8b1a1a; color: #fff; }',
    '#bmc-pricing-tier-toggle button.active-nonmit{ background: #1a3b8b; color: #fff; }',

    '.bmc-price-section {',
    '  background: ' + TOKENS.bgCard + ';',
    '  border: 1px solid ' + TOKENS.border + ';',
    '  border-radius: 12px; overflow: hidden;',
    '  margin-bottom: 12px;',
    '  box-shadow: 0 1px 3px rgba(0,0,0,0.04);',
    '}',
    '.bmc-price-section-header {',
    '  background: #faf9f7;',
    '  border-bottom: 1px solid ' + TOKENS.border + ';',
    '  padding: 13px 20px;',
    '  font-size: 0.88em; font-weight: 600;',
    '  color: #333; cursor: pointer;',
    '  display: flex; justify-content: space-between; align-items: center;',
    '  user-select: none;',
    '}',
    '.bmc-price-section-header:hover { background: #f2f0ec; }',
    '.bmc-price-section-header .chevron { color: #aaa; font-size: 0.8em; transition: transform 0.2s; }',
    '.bmc-price-section-header.collapsed .chevron { transform: rotate(-90deg); }',
    '.bmc-price-row {',
    '  display: flex; justify-content: space-between; align-items: flex-start;',
    '  padding: 11px 20px;',
    '  border-bottom: 1px solid #f5f2ee;',
    '  transition: background 0.1s; gap: 16px;',
    '}',
    '.bmc-price-row:last-child { border-bottom: none; }',
    '.bmc-price-row:hover { background: #faf8f5; }',
    '.bmc-price-row-name {',
    '  font-size: 0.87em; color: #222;',
    '}',
    '.bmc-price-row-unit {',
    '  font-size: 0.74em; color: #aaa;',
    '  font-family: ' + TOKENS.fontMono + '; margin-top: 2px;',
    '}',
    '.bmc-price-row-note {',
    '  font-size: 0.74em; color: #999;',
    '  font-style: italic; margin-top: 3px;',
    '}',
    '.bmc-price-amount {',
    '  font-size: 1em; font-weight: 600;',
    '  font-family: ' + TOKENS.fontMono + ';',
    '  white-space: nowrap; text-align: right;',
    '  min-width: 90px;',
    '}',
    '.bmc-price-amount.tier-core   { color: #1a6b3c; }',
    '.bmc-price-amount.tier-mit    { color: #8b1a1a; }',
    '.bmc-price-amount.tier-nonmit { color: #1a3b8b; }',
    '.bmc-price-amount.tier-na     { color: #ccc; font-style: italic; font-size: 0.82em; }',

    /* search bar */
    '#bmc-pricing-search-wrap {',
    '  position: relative; margin-bottom: 20px; width: 260px;',
    '}',
    '#bmc-pricing-search {',
    '  width: 100%; padding: 9px 14px 9px 36px;',
    '  border: 1.5px solid #ddd; border-radius: 8px;',
    '  font-size: 0.85em; font-family: ' + TOKENS.fontBody + ';',
    '  background: #fff; outline: none;',
    '}',
    '#bmc-pricing-search:focus { border-color: ' + TOKENS.gold + '; }',
    '#bmc-pricing-search-icon {',
    '  position: absolute; left: 12px; top: 50%;',
    '  transform: translateY(-50%); color: #aaa; pointer-events: none;',
    '}',

    /* tier indicator pill */
    '#bmc-tier-pill {',
    '  display: inline-flex; align-items: center; gap: 8px;',
    '  padding: 5px 12px; border-radius: 6px;',
    '  font-size: 0.78em; font-weight: 600;',
    '  letter-spacing: 0.04em; margin-bottom: 20px;',
    '}',

    /* homepage card grid */
    '.bmc-home-grid {',
    '  display: grid;',
    '  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));',
    '  gap: 16px; margin-top: 24px;',
    '}',
    '.bmc-home-card {',
    '  background: ' + TOKENS.bgCard + ';',
    '  border: 1px solid ' + TOKENS.border + ';',
    '  border-radius: 14px; padding: 22px 24px;',
    '  box-shadow: 0 1px 4px rgba(0,0,0,0.05);',
    '  transition: box-shadow 0.2s, transform 0.2s;',
    '}',
    '.bmc-home-card:hover {',
    '  box-shadow: 0 4px 16px rgba(0,0,0,0.1);',
    '  transform: translateY(-2px);',
    '}',
    '.bmc-home-card .card-icon {',
    '  font-size: 1.6em; margin-bottom: 12px;',
    '}',
    '.bmc-home-card .card-title {',
    '  font-size: 0.95em; font-weight: 600;',
    '  color: ' + TOKENS.black + '; margin-bottom: 8px;',
    '}',
    '.bmc-home-card .card-links {',
    '  list-style: none; padding: 0; margin: 0;',
    '}',
    '.bmc-home-card .card-links li {',
    '  font-size: 0.84em; line-height: 1.8;',
    '  color: ' + TOKENS.textMid + ';',
    '}',
    '.bmc-home-card .card-links li a {',
    '  color: ' + TOKENS.textMid + '; text-decoration: none;',
    '  border-bottom: none;',
    '  transition: color 0.15s;',
    '}',
    '.bmc-home-card .card-links li a:hover { color: ' + TOKENS.gold + '; }',
    '.bmc-home-card .card-links li::before {',
    '  content: "→ "; color: ' + TOKENS.gold + '; font-size: 0.85em;',
    '}',

    /* contact strip */
    '#bmc-contact-strip {',
    '  background: ' + TOKENS.darkGray + ';',
    '  color: #ccc; border-radius: 12px;',
    '  padding: 24px 28px; margin-top: 32px;',
    '  display: flex; gap: 32px; flex-wrap: wrap; align-items: center;',
    '}',
    '#bmc-contact-strip .contact-item {',
    '  font-size: 0.87em; display: flex; gap: 8px; align-items: center;',
    '}',
    '#bmc-contact-strip a { color: ' + TOKENS.gold + '; text-decoration: none; }',
    '#bmc-contact-strip .contact-label { color: #666; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.08em; }',

  ].join('\n');

  /* ============================================================
     3. NAV STRUCTURE
  ============================================================ */
  var NAV_LINKS = [
    { label: 'Home',         href: '/index.php/BioMicroCenter',                  match: 'BioMicroCenter$' },
    { label: 'Sequencing',   href: '/index.php/BioMicroCenter:Sequencing',        match: 'Sequencing' },
    { label: 'Library Prep', href: '/index.php/BioMicroCenter:Illumina_Library_Preparation', match: 'Library' },
    { label: 'Single Cell',  href: '/index.php/BioMicroCenter:SingleCell',        match: 'SingleCell|SpTx|Visium' },
    { label: 'Long Read',    href: '/index.php/BioMicroCenter:Oxford_Nanopore_Technologies', match: 'Nanopore|PacBio' },
    { label: 'Computing',    href: '/index.php/BioMicroCenter:Computing',         match: 'Computing' },
    { label: 'QC',           href: '/index.php/BioMicroCenter:QC',               match: ':QC' },
    { label: 'Pricing',      href: '/index.php/BioMicroCenter:Pricing',          match: 'Pricing' },
    { label: 'People',       href: '/index.php/BioMicroCenter:People',           match: 'People' },
    { label: 'FAQ',          href: '/index.php/BioMicroCenter:FAQ',              match: 'FAQ' },
  ];

  /* ============================================================
     4. MAIN BMC OBJECT
  ============================================================ */
  var BMC = {

    page: mw.config.get( 'wgPageName' ) || '',

    /* ---- 4a. INIT ---- */
    init: function () {
      if ( document.getElementById( 'bmc-topnav' ) ) return; // already ran
      this.injectCSS();
      this.injectNav();
      this.injectBreadcrumb();
      this.wrapContent();

      // Page-specific transforms
      if ( /^BioMicroCenter$/.test( this.page ) ) {
        this.transformHomepage();
      } else if ( /Pricing/.test( this.page ) ) {
        this.transformPricing();
      } else {
        this.transformGeneric();
      }
    },

    /* ---- 4b. INJECT GLOBAL CSS ---- */
    injectCSS: function () {
      var style = document.createElement( 'style' );
      style.id = 'bmc-theme-css';
      style.textContent = GLOBAL_CSS;
      document.head.appendChild( style );
    },

    /* ---- 4c. INJECT TOP NAV ---- */
    injectNav: function () {
      var page = this.page;
      var logoImg = document.querySelector( '#p-logo img, .mw-wiki-logo' );
      var logoSrc = logoImg ? logoImg.src : '';

      var linksHtml = NAV_LINKS.map( function ( l ) {
        var active = new RegExp( l.match ).test( page ) ? ' bmc-nav-active' : '';
        return '<a href="' + l.href + '" class="' + active + '">' + l.label + '</a>';
      } ).join( '' );

      var nav = document.createElement( 'div' );
      nav.id = 'bmc-topnav';
      nav.innerHTML =
        '<a href="/index.php/BioMicroCenter" class="bmc-nav-logo">' +
          ( logoSrc ? '<img src="' + logoSrc + '" alt="BMC">' : '' ) +
          'BioMicro <span class="accent">Center</span>' +
        '</a>' +
        '<div id="bmc-nav-links">' + linksHtml + '</div>' +
        '<div id="bmc-nav-right">' +
          '<a href="/index.php/Special:Search" class="bmc-nav-search" title="Search">⌕</a>' +
          '<a href="https://mit-ki.ilabsolutions.com/sc/3381/ki-genomics-core-mit-biomicro-center?tab=about"' +
          ' class="bmc-ilabs-btn" target="_blank">Submit via iLabs ↗</a>' +
        '</div>';

      // Insert before body content
      var body = document.body;
      body.insertBefore( nav, body.firstChild );
    },

    /* ---- 4d. BREADCRUMB ---- */
    injectBreadcrumb: function () {
      var page = this.page;
      if ( /^BioMicroCenter$/.test( page ) ) return; // no breadcrumb on homepage

      var parts = page.split( ':' );
      var crumb = document.createElement( 'div' );
      crumb.id = 'bmc-breadcrumb';
      crumb.innerHTML =
        '<a href="/index.php/BioMicroCenter">Home</a>' +
        '<span class="sep">›</span>' +
        '<span>' + ( parts[1] || parts[0] ).replace( /_/g, ' ' ) + '</span>';

      var nav = document.getElementById( 'bmc-topnav' );
      if ( nav ) nav.after( crumb );
    },

    /* ---- 4e. WRAP MAIN CONTENT ---- */
    wrapContent: function () {
      var content = document.getElementById( 'mw-content-text' )
        || document.getElementById( 'content' )
        || document.querySelector( '.mw-body-content' );
      if ( !content ) return;

      // Get the first heading
      var h1 = document.querySelector( '#firstHeading, h1.firstHeading' );
      var titleText = h1 ? h1.textContent.replace( /^BioMicroCenter:?/, '' ).trim() : '';

      // Build page title block
      var titleBlock = document.createElement( 'div' );
      titleBlock.id = 'bmc-page-title';
      if ( titleText && !/^BioMicroCenter$/.test( this.page ) ) {
        titleBlock.innerHTML =
          '<h1>' + titleText + '</h1>';
      }
      if ( h1 ) h1.style.display = 'none';

      // Wrap in page container
      var wrap = document.createElement( 'div' );
      wrap.id = 'bmc-page-wrap';
      content.parentNode.insertBefore( wrap, content );
      wrap.appendChild( titleBlock );
      wrap.appendChild( content );
    },

    /* ---- 4f. GENERIC PAGE TRANSFORM ---- */
    transformGeneric: function () {
      // Just let the global CSS handle it — tables, headings, prose all look better automatically.
      // Remove the old banner image if present (it's usually a 500px wide header)
      var banner = document.querySelector( '#mw-content-text img[src*="BMC_Header"]' );
      if ( banner ) {
        var bannerParent = banner.closest( 'p, div' );
        if ( bannerParent ) bannerParent.style.display = 'none';
      }
    },

    /* ---- 4g. HOMEPAGE TRANSFORM ---- */
    transformHomepage: function () {
      var wrap = document.getElementById( 'bmc-page-wrap' );
      if ( !wrap ) return;

      // Hide the old content and replace with hero + cards
      var oldContent = document.getElementById( 'mw-content-text' );

      // Extract links from existing wiki tables to preserve them
      var allLinks = {};
      if ( oldContent ) {
        oldContent.querySelectorAll( 'a' ).forEach( function ( a ) {
          allLinks[a.textContent.trim()] = a.href;
        } );
        oldContent.style.display = 'none';
      }

      // Hero
      var hero = document.createElement( 'div' );
      hero.style.cssText = [
        'background: ' + TOKENS.black + ';',
        'border-radius: 16px; padding: 48px 40px;',
        'margin-bottom: 32px; position: relative; overflow: hidden;',
        'color: #fff;',
      ].join( '' );
      hero.innerHTML = [
        '<div style="position:relative;z-index:1">',
          '<div style="font-size:0.72em;letter-spacing:0.16em;color:#888;text-transform:uppercase;',
              'margin-bottom:12px;font-family:' + TOKENS.fontMono + '">',
              'Koch Institute · MIT Department of Biology · Biological Engineering',
          '</div>',
          '<h1 style="font-size:2.4em;font-weight:600;letter-spacing:-0.03em;',
              'line-height:1;margin:0 0 16px;color:#fff">',
              'MIT BioMicro<br><span style="color:' + TOKENS.gold + '">Center</span>',
          '</h1>',
          '<p style="max-width:560px;font-size:0.95em;line-height:1.7;color:#aaa;margin:0 0 24px">',
              'An integrated genomics core facility providing expertise and equipment for ',
              'systems biology — from sample prep through sequencing, single-cell, and bioinformatics.',
          '</p>',
          '<div style="display:flex;gap:12px;flex-wrap:wrap">',
            '<a href="https://mit-ki.ilabsolutions.com/sc/3381/ki-genomics-core-mit-biomicro-center?tab=about"',
               ' target="_blank"',
               ' style="background:' + TOKENS.gold + ';color:' + TOKENS.black + ';',
               'font-weight:600;font-size:0.85em;padding:10px 20px;border-radius:8px;',
               'text-decoration:none;letter-spacing:0.02em">',
               'Submit Samples via iLabs ↗',
            '</a>',
            '<a href="/index.php/BioMicroCenter:Pricing"',
               ' style="background:#222;color:#ccc;',
               'font-weight:500;font-size:0.85em;padding:10px 20px;border-radius:8px;',
               'text-decoration:none">',
               'View Pricing',
            '</a>',
          '</div>',
        '</div>',
        // subtle grid pattern overlay
        '<div style="position:absolute;inset:0;opacity:0.04;pointer-events:none;',
            'background-image:repeating-linear-gradient(0deg,#fff 0,#fff 1px,transparent 1px,transparent 40px),',
            'repeating-linear-gradient(90deg,#fff 0,#fff 1px,transparent 1px,transparent 40px)">',
        '</div>',
      ].join( '' );
      wrap.appendChild( hero );

      // Service cards
      var CARDS = [
        {
          icon: '🧬',
          title: 'Sequencing',
          links: [
            { label: 'NovaSeq X / NovaSeq 6000', href: '/index.php/BioMicroCenter:Illumina_Sequencing' },
            { label: 'Element AVITI24', href: '/index.php/BioMicroCenter:Element_Sequencing' },
            { label: 'Singular G4 / MiSeq', href: '/index.php/BioMicroCenter:Illumina_Sequencing' },
            { label: 'ONT PromethION', href: '/index.php/BioMicroCenter:Oxford_Nanopore_Technologies' },
            { label: 'PacBio Revio †', href: '/index.php/BioMicroCenter:PacBio' },
          ],
        },
        {
          icon: '📚',
          title: 'Library Preparation',
          links: [
            { label: 'Short Read — DNA', href: '/index.php/BioMicroCenter:DNA_LIB' },
            { label: 'Short Read — RNA', href: '/index.php/BioMicroCenter:RNA_LIB' },
            { label: 'High Throughput DNA', href: '/index.php/BioMicroCenter:DNA_HTL' },
            { label: 'High Throughput RNA', href: '/index.php/BioMicroCenter:RNA_HTL' },
            { label: 'Nanopore Library Prep', href: '/index.php/BioMicroCenter:NanoPore_Library_Prep' },
            { label: 'PacBio Library Prep', href: '/index.php/BioMicroCenter:PacBio_Library_Preparation' },
          ],
        },
        {
          icon: '⬡',
          title: 'Single Cell & Spatial',
          links: [
            { label: '10X Chromium (5′/3′ RNA, ATAC)', href: '/index.php/BioMicroCenter:SingleCell' },
            { label: '10X Visium (Standard + HD)', href: '/index.php/BioMicroCenter:SpTx' },
            { label: 'AVITI24 in situ', href: '/index.php/BioMicroCenter:Element_Sequencing' },
          ],
        },
        {
          icon: '🔬',
          title: 'Sample Services & QC',
          links: [
            { label: 'Fragment Analyzer / FemtoPulse', href: '/index.php/BioMicroCenter:QC' },
            { label: 'BioAnalyzer', href: '/index.php/BioMicroCenter:QC' },
            { label: 'Chemagic360 DNA/RNA Extraction', href: '/index.php/BioMicroCenter:Tecan_Freedom_Evo' },
            { label: 'Covaris Sonicator', href: '/index.php/BioMicroCenter:Covaris' },
            { label: 'RT-PCR (Roche LC480)', href: '/index.php/BioMicroCenter:RTPCR' },
          ],
        },
        {
          icon: '🤖',
          title: 'Automation',
          links: [
            { label: 'Tecan EVO 150 Liquid Handler', href: '/index.php/BioMicroCenter:Tecan_Freedom_Evo' },
            { label: 'SPT Mosquito HV', href: '/index.php/BioMicroCenter:Tecan_Freedom_Evo' },
            { label: 'Pippin Prep Electrophoresis', href: '/index.php/BioMicroCenter:PippinPrep' },
            { label: 'Oligo Synthesis', href: '/index.php/BioMicroCenter:Oligo_Synthesis' },
          ],
        },
        {
          icon: '💻',
          title: 'Bioinformatics & Computing',
          links: [
            { label: 'Bioinformatics Consulting (IGB)', href: 'https://igb.mit.edu/' },
            { label: 'Luria Computing Cluster', href: 'https://igb.mit.edu/computing-resources/luria-cluster' },
            { label: 'Active Data Storage', href: 'https://igb.mit.edu/computing-resources/active-data-storage' },
            { label: 'Data Transfer (Globus)', href: 'https://igb.mit.edu/data-management/globus' },
            { label: 'Training Sessions', href: 'https://igb.mit.edu/mini-courses' },
          ],
        },
        {
          icon: '📋',
          title: 'Get Started',
          links: [
            { label: 'Pricing', href: '/index.php/BioMicroCenter:Pricing' },
            { label: 'New User Signup', href: '/index.php/BioMicroCenter:Forms#NEW_USERS' },
            { label: 'External Submission Forms', href: '/index.php/BioMicroCenter:Forms' },
            { label: 'FAQs', href: '/index.php/BioMicroCenter:FAQ' },
            { label: 'Staff Directory', href: '/index.php/BioMicroCenter:People' },
            { label: 'News & Updates', href: '/index.php/BioMicroCenter:News' },
          ],
        },
        {
          icon: '📅',
          title: 'News & Resources',
          links: [
            { label: 'News & General Information', href: '/index.php/BioMicroCenter:News' },
            { label: 'Technology Seminar Series', href: '/index.php/BioMicroCenter:Technology_Seminar_Series' },
            { label: 'Biostuff Mailing List', href: 'http://mailman.mit.edu:/mailman/listinfo/biostuff' },
            { label: 'FAIR Data Management', href: 'https://koch-institute-mit.gitbook.io/mit-data-management-analysis-core/' },
          ],
        },
      ];

      var grid = document.createElement( 'div' );
      grid.className = 'bmc-home-grid';
      CARDS.forEach( function ( card ) {
        var li = card.links.map( function ( lnk ) {
          return '<li><a href="' + lnk.href + '">' + lnk.label + '</a></li>';
        } ).join( '' );
        var el = document.createElement( 'div' );
        el.className = 'bmc-home-card';
        el.innerHTML =
          '<div class="card-icon">' + card.icon + '</div>' +
          '<div class="card-title">' + card.title + '</div>' +
          '<ul class="card-links">' + li + '</ul>';
        grid.appendChild( el );
      } );
      wrap.appendChild( grid );

      // Contact strip
      var contact = document.createElement( 'div' );
      contact.id = 'bmc-contact-strip';
      contact.innerHTML = [
        '<div><div class="contact-label">Location</div>',
        '<div class="contact-item">Room 68-322, MIT</div></div>',
        '<div><div class="contact-label">Email</div>',
        '<div class="contact-item"><a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a></div></div>',
        '<div><div class="contact-label">Phone</div>',
        '<div class="contact-item">617-715-4533</div></div>',
        '<div style="margin-left:auto">',
        '<a href="https://mit-ki.ilabsolutions.com/sc/3381/ki-genomics-core-mit-biomicro-center?tab=about"',
           ' target="_blank"',
           ' style="background:' + TOKENS.gold + ';color:#000;font-weight:600;',
           'font-size:0.82em;padding:9px 18px;border-radius:7px;text-decoration:none">',
           'iLabs — Internal Submission ↗',
        '</a></div>',
      ].join( '' );
      wrap.appendChild( contact );
    },

    /* ---- 4h. PRICING PAGE TRANSFORM ---- */
    transformPricing: function () {
      var self = this;
      var oldContent = document.getElementById( 'mw-content-text' );
      if ( !oldContent ) return;

      // Current tier state
      var tier = 'mit';
      var tierColors = { core: '#1a6b3c', mit: '#8b1a1a', nonmit: '#1a3b8b' };
      var tierBadges = { core: 'CORE', mit: 'MIT', nonmit: 'EXT' };
      var tierLabels = { core: 'Core Lab', mit: 'MIT', nonmit: 'Non-MIT' };

      // Parse all wiki tables from the existing rendered HTML
      // Each table = one pricing section
      var sections = self.parsePricingTables( oldContent );

      // Hide original content
      oldContent.style.display = 'none';

      var wrap = document.getElementById( 'bmc-page-wrap' );
      if ( !wrap ) return;

      // Controls row
      var controls = document.createElement( 'div' );
      controls.style.cssText = 'display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;margin-bottom:8px;';

      // Tier toggle
      var toggle = document.createElement( 'div' );
      toggle.id = 'bmc-pricing-tier-toggle';
      [ 'core', 'mit', 'nonmit' ].forEach( function ( t ) {
        var btn = document.createElement( 'button' );
        btn.textContent = tierLabels[t];
        btn.dataset.tier = t;
        btn.className = t === tier ? 'active-' + t : '';
        btn.addEventListener( 'click', function () {
          tier = t;
          toggle.querySelectorAll( 'button' ).forEach( function ( b ) { b.className = ''; } );
          btn.className = 'active-' + t;
          pill.style.background = tierColors[t] + '22';
          pill.style.color = tierColors[t];
          pill.style.borderColor = tierColors[t] + '44';
          pillBadge.style.background = tierColors[t];
          pillBadge.textContent = tierBadges[t];
          pillLabel.textContent = 'Showing ' + tierLabels[t] + ' pricing';
          self.updatePricingTier( t );
        } );
        toggle.appendChild( btn );
      } );

      // Search
      var searchWrap = document.createElement( 'div' );
      searchWrap.id = 'bmc-pricing-search-wrap';
      searchWrap.innerHTML =
        '<span id="bmc-pricing-search-icon">⌕</span>' +
        '<input id="bmc-pricing-search" type="text" placeholder="Search services…">';
      searchWrap.querySelector( 'input' ).addEventListener( 'input', function () {
        self.filterPricingRows( this.value.toLowerCase() );
      } );

      controls.appendChild( toggle );
      controls.appendChild( searchWrap );
      wrap.appendChild( controls );

      // Tier pill
      var pill = document.createElement( 'div' );
      pill.id = 'bmc-tier-pill';
      pill.style.cssText = 'background:' + tierColors[tier] + '22;color:' + tierColors[tier] + ';border:1.5px solid ' + tierColors[tier] + '44;border-radius:6px;';
      var pillBadge = document.createElement( 'span' );
      pillBadge.style.cssText = 'background:' + tierColors[tier] + ';color:#fff;border-radius:3px;padding:1px 6px;font-size:0.82em;letter-spacing:0.08em;';
      pillBadge.textContent = tierBadges[tier];
      var pillLabel = document.createElement( 'span' );
      pillLabel.textContent = 'Showing ' + tierLabels[tier] + ' pricing';
      pill.appendChild( pillBadge );
      pill.appendChild( pillLabel );
      wrap.appendChild( pill );

      // Render sections
      var container = document.createElement( 'div' );
      container.id = 'bmc-pricing-container';
      wrap.appendChild( container );

      sections.forEach( function ( section ) {
        var card = document.createElement( 'div' );
        card.className = 'bmc-price-section';

        var header = document.createElement( 'div' );
        header.className = 'bmc-price-section-header';
        header.innerHTML = section.title + '<span class="chevron">▼</span>';
        header.addEventListener( 'click', function () {
          var body = card.querySelector( '.bmc-price-body' );
          var collapsed = header.classList.toggle( 'collapsed' );
          body.style.display = collapsed ? 'none' : '';
        } );

        var body = document.createElement( 'div' );
        body.className = 'bmc-price-body';

        section.rows.forEach( function ( row ) {
          var rowEl = document.createElement( 'div' );
          rowEl.className = 'bmc-price-row';
          rowEl.dataset.rowName = row.name.toLowerCase();

          var amt = row.prices[tier];
          var amtClass = ( amt === null || amt === undefined || amt === '' )
            ? 'tier-na' : 'tier-' + tier;
          var amtText = ( amt === null || amt === undefined || amt === '' )
            ? '—' : amt;

          rowEl.innerHTML =
            '<div>' +
              '<div class="bmc-price-row-name">' + row.name + '</div>' +
              ( row.unit ? '<div class="bmc-price-row-unit">' + row.unit + '</div>' : '' ) +
              ( row.notes ? '<div class="bmc-price-row-note">' + row.notes + '</div>' : '' ) +
            '</div>' +
            '<div class="bmc-price-amount ' + amtClass + '" data-prices=\'' + JSON.stringify( row.prices ) + '\'>' +
              amtText +
            '</div>';

          body.appendChild( rowEl );
        } );

        card.appendChild( header );
        card.appendChild( body );
        container.appendChild( card );
      } );

      // Footer note
      var note = document.createElement( 'div' );
      note.style.cssText = 'margin-top:24px;padding:14px 18px;background:#fff8e8;border-radius:8px;border:1px solid #e8d8a0;font-size:0.78em;color:#7a6a3a;line-height:1.6;';
      note.innerHTML = '✦ = Minimal inventory maintained &nbsp;·&nbsp; †† = Through collaboration with nearby academic shared resources &nbsp;·&nbsp; Pricing as of January 2026. Questions? <a href="mailto:biomicro@mit.edu" style="color:#8b6914">biomicro@mit.edu</a>';
      wrap.appendChild( note );
    },

    /* ---- 4i. PARSE PRICING TABLES FROM WIKI HTML ---- */
    parsePricingTables: function ( container ) {
      var sections = [];
      // Walk h2/h3 + table pairs
      var nodes = container.querySelectorAll( 'h2, h3, table' );
      var currentTitle = 'Services';
      var currentRows = [];

      var flush = function () {
        if ( currentRows.length > 0 ) {
          sections.push( { title: currentTitle, rows: currentRows } );
          currentRows = [];
        }
      };

      nodes.forEach( function ( node ) {
        if ( node.tagName === 'H2' || node.tagName === 'H3' ) {
          flush();
          // Clean up heading text (remove [edit] spans)
          var clone = node.cloneNode( true );
          clone.querySelectorAll( '.mw-editsection' ).forEach( function ( e ) { e.remove(); } );
          currentTitle = clone.textContent.trim();
        } else if ( node.tagName === 'TABLE' ) {
          // Find header row to determine column order
          var headers = [];
          var headerRow = node.querySelector( 'tr' );
          if ( headerRow ) {
            headerRow.querySelectorAll( 'th, td' ).forEach( function ( cell ) {
              headers.push( cell.textContent.trim().toLowerCase() );
            } );
          }

          // Find price column indices
          var coreIdx   = -1, mitIdx = -1, nonmitIdx = -1;
          var nameIdx   = 0;
          var unitIdx   = -1, notesIdx = -1;
          headers.forEach( function ( h, i ) {
            if ( /core/.test( h ) )     coreIdx   = i;
            if ( /^mit$/.test( h ) )    mitIdx    = i;
            if ( /non.?mit/.test( h ) ) nonmitIdx = i;
            if ( /unit/.test( h ) )     unitIdx   = i;
            if ( /note/.test( h ) )     notesIdx  = i;
          } );

          // Parse data rows
          var rows = node.querySelectorAll( 'tr' );
          rows.forEach( function ( row, ri ) {
            if ( ri === 0 ) return; // skip header
            var cells = row.querySelectorAll( 'td, th' );
            if ( cells.length < 2 ) return;

            var getText = function ( idx ) {
              if ( idx < 0 || idx >= cells.length ) return '';
              return cells[idx].textContent.trim();
            };

            var name = getText( nameIdx );
            if ( !name ) return;

            var formatPrice = function ( idx ) {
              var raw = getText( idx );
              if ( !raw || raw === '' || /pricing in/i.test( raw ) ) return null;
              // Already has $ sign in wiki? Return as-is, else prefix
              return raw;
            };

            currentRows.push( {
              name: name,
              unit: unitIdx >= 0 ? getText( unitIdx ) : '',
              notes: notesIdx >= 0 ? getText( notesIdx ) : '',
              prices: {
                core:   formatPrice( coreIdx ),
                mit:    formatPrice( mitIdx ),
                nonmit: formatPrice( nonmitIdx ),
              }
            } );
          } );
        }
      } );
      flush();
      return sections;
    },

    /* ---- 4j. UPDATE PRICING TIER (live swap) ---- */
    updatePricingTier: function ( tier ) {
      document.querySelectorAll( '.bmc-price-amount' ).forEach( function ( el ) {
        try {
          var prices = JSON.parse( el.dataset.prices );
          var amt = prices[tier];
          var isNA = ( amt === null || amt === undefined || amt === '' );
          el.textContent = isNA ? '—' : amt;
          el.className = 'bmc-price-amount ' + ( isNA ? 'tier-na' : 'tier-' + tier );
        } catch ( e ) {}
      } );
    },

    /* ---- 4k. FILTER PRICING ROWS ---- */
    filterPricingRows: function ( query ) {
      document.querySelectorAll( '.bmc-price-row' ).forEach( function ( row ) {
        var name = row.dataset.rowName || '';
        row.style.display = name.includes( query ) ? '' : 'none';
      } );
      // Show/hide empty sections
      document.querySelectorAll( '.bmc-price-section' ).forEach( function ( section ) {
        var visible = section.querySelectorAll( '.bmc-price-row:not([style*="none"])' );
        section.style.display = visible.length ? '' : 'none';
      } );
    },

  }; // end BMC

  window.BMC = BMC; // expose globally for debugging

  /* ============================================================
     5. KICK OFF — hook registered AFTER BMC is fully defined
  ============================================================ */
  mw.hook( 'wikipage.content' ).add( function () {
    BMC.init();
  } );

  // Fire immediately — multiple strategies
  function tryInit() {
    if ( document.getElementById( 'bmc-topnav' ) ) return;
    if ( typeof mw !== 'undefined' && mw.config ) { BMC.init(); }
  }
  tryInit();
  setTimeout( tryInit, 500 );
  setTimeout( tryInit, 1500 );
  if ( document.readyState !== 'loading' ) {
    BMC.init();
  }

}() );