User:Gibcus/vector-2022.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| (2 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
mw. | /** | ||
* 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 · †† = Through collaboration with nearby academic shared resources · 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(); | |||
} | |||
}() ); | |||
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 · †† = Through collaboration with nearby academic shared resources · 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();
}
}() );