{"id":703,"date":"2024-11-26T20:26:36","date_gmt":"2024-11-26T20:26:36","guid":{"rendered":"https:\/\/geiselmed.dartmouth.edu\/gsr\/?page_id=703"},"modified":"2026-04-03T15:26:14","modified_gmt":"2026-04-03T15:26:14","slug":"cost-estimator","status":"publish","type":"page","link":"https:\/\/geiselmed.dartmouth.edu\/gsr\/cost-estimator\/","title":{"rendered":"Experiment Cost Estimation Tool"},"content":{"rendered":"<p><title>GSR Cost Estimator<\/title><\/p>\n<p>:root {<br \/>\n  --green: #006845;<br \/>\n  --green-dark: #004d33;<br \/>\n  --green-light: #e8f2ee;<br \/>\n  --green-mid: #b5d4c8;<br \/>\n  --text: #222;<br \/>\n  --muted: #555;<br \/>\n  --border: #ccc;<br \/>\n  --radius: 6px;<br \/>\n}<br \/>\n* { box-sizing: border-box; margin: 0; padding: 0; }<br \/>\nbody { font-family: Arial, sans-serif; color: var(--text); font-size: 16px; background: #fff; }<\/p>\n<p>.gsr-estimator { max-width: 800px; margin: 0 auto; padding: 28px 20px 60px; }<br \/>\n.gsr-tool-title { color: var(--green); font-size: 26px; font-weight: 700; margin-bottom: 4px; }<br \/>\n.gsr-subtitle { color: var(--muted); font-size: 14px; margin-bottom: 24px; }<\/p>\n<p>\/* Progress *\/<br \/>\n.gsr-progress-wrap { margin-bottom: 28px; }<br \/>\n.gsr-progress-bar { display: flex; gap: 5px; }<br \/>\n.gsr-progress-step { flex: 1; height: 7px; background: #ddd; border-radius: 4px; transition: background .25s; }<br \/>\n.gsr-progress-step.active { background: var(--green); }<br \/>\n.gsr-progress-step.done { background: var(--green-dark); }<br \/>\n.gsr-progress-label { font-size: 12px; color: var(--muted); margin-top: 5px; }<\/p>\n<p>\/* Step headings *\/<br \/>\n.gsr-step-title { font-size: 21px; font-weight: 700; color: var(--green); margin-bottom: 6px; }<br \/>\n.gsr-step-desc  { color: var(--muted); font-size: 14px; margin-bottom: 20px; }<\/p>\n<p>\/* Cards *\/<br \/>\n.gsr-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 14px; margin-bottom: 26px; }<br \/>\n.gsr-card { border: 2px solid var(--border); border-radius: var(--radius); padding: 18px 14px; cursor: pointer; transition: border-color .2s, background .2s; text-align: center; }<br \/>\n.gsr-card:hover { border-color: var(--green); background: var(--green-light); }<br \/>\n.gsr-card.selected { border-color: var(--green); background: var(--green-light); }<br \/>\n.gsr-card .card-icon  { font-size: 26px; margin-bottom: 8px; }<br \/>\n.gsr-card .card-label { font-weight: 700; font-size: 15px; color: var(--green); margin-bottom: 4px; }<br \/>\n.gsr-card .card-desc  { font-size: 12px; color: var(--muted); line-height: 1.4; }<\/p>\n<p>\/* Form fields *\/<br \/>\n.gsr-field { margin-bottom: 20px; }<br \/>\n.gsr-field label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 6px; }<br \/>\n.gsr-input-num { width: 130px; padding: 9px 10px; font-size: 15px; border: 2px solid var(--border); border-radius: var(--radius); }<br \/>\n.gsr-input-num:focus { outline: none; border-color: var(--green); }<br \/>\n.field-note { font-size: 12px; color: var(--muted); margin-top: 5px; }<\/p>\n<p>\/* Checkboxes *\/<br \/>\n.gsr-check-group { display: flex; flex-direction: column; gap: 12px; }<br \/>\n.gsr-check-item { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }<br \/>\n.gsr-check-item input[type=\"checkbox\"] { width: 17px; height: 17px; margin-top: 2px; accent-color: var(--green); cursor: pointer; flex-shrink: 0; }<br \/>\n.gsr-check-item .cb-text { flex: 1; }<br \/>\n.gsr-check-item .cb-label { font-size: 14px; font-weight: 600; }<br \/>\n.gsr-check-item .cb-price { font-size: 12px; color: var(--muted); }<br \/>\n.gsr-sub-check { padding-left: 27px; }<\/p>\n<p>\/* Reads box *\/<br \/>\n.gsr-reads-box { background: var(--green-light); border: 1px solid var(--green-mid); border-radius: var(--radius); padding: 18px; margin-bottom: 22px; }<br \/>\n.gsr-reads-box .reads-header { font-weight: 700; font-size: 14px; margin-bottom: 10px; color: var(--green-dark); }<br \/>\n.gsr-reads-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }<br \/>\n.gsr-reads-row input { width: 110px; padding: 8px 10px; border: 2px solid var(--border); border-radius: var(--radius); font-size: 15px; }<br \/>\n.gsr-reads-row input:focus { outline: none; border-color: var(--green); }<br \/>\n.gsr-reads-total { font-size: 14px; color: var(--text); }<br \/>\n.gsr-rec-note { font-size: 12px; color: var(--green-dark); margin-top: 8px; font-style: italic; }<\/p>\n<p>\/* Run table *\/<br \/>\n.gsr-run-options { margin-bottom: 22px; }<br \/>\n.gsr-run-options h4 { font-size: 15px; font-weight: 700; color: var(--green); margin-bottom: 10px; }<br \/>\n.gsr-run-scroll { overflow-x: auto; }<br \/>\ntable.gsr-run-table { width: 100%; border-collapse: collapse; font-size: 13.5px; white-space: nowrap; }<br \/>\ntable.gsr-run-table th  { background: var(--green); color: #fff; padding: 9px 11px; text-align: left; }<br \/>\ntable.gsr-run-table td  { padding: 9px 11px; border-bottom: 1px solid #e5e5e5; vertical-align: middle; }<br \/>\ntable.gsr-run-table tr:last-child td { border-bottom: none; }<br \/>\ntable.gsr-run-table tr.rec-row   td { background: var(--green-light); }<br \/>\ntable.gsr-run-table td.run-name { font-weight: 500; }<br \/>\n.badge { display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 10px; margin-left: 5px; vertical-align: middle; }<br \/>\n.badge-best { background: var(--green); color: #fff; }<\/p>\n<p>\/* Summary *\/<br \/>\n.gsr-summary-box { background: #fafafa; border: 1px solid #ddd; border-radius: var(--radius); overflow: hidden; margin-bottom: 18px; }<br \/>\ntable.gsr-summary-table { width: 100%; border-collapse: collapse; font-size: 15px; }<br \/>\ntable.gsr-summary-table th        { background: var(--green); color: #fff; padding: 11px 16px; text-align: left; }<br \/>\ntable.gsr-summary-table th.right  { text-align: right; }<br \/>\ntable.gsr-summary-table td        { padding: 10px 16px; border-bottom: 1px solid #e5e5e5; }<br \/>\ntable.gsr-summary-table td.right  { text-align: right; }<br \/>\ntable.gsr-summary-table td.note   { font-size: 12px; color: var(--muted); font-style: italic; padding-top: 2px; padding-bottom: 8px; border-bottom: 1px solid #e5e5e5; }<br \/>\ntable.gsr-summary-table tr.total-row td { font-weight: 700; font-size: 16px; border-top: 2px solid var(--green); background: var(--green-light); border-bottom: none; }<\/p>\n<p>.gsr-info-box { background: var(--green-light); border: 1px solid var(--green-mid); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 18px; font-size: 13px; color: var(--green-dark); line-height: 1.5; }<br \/>\n.gsr-disclaimer { font-size: 12px; color: #777; font-style: italic; margin-bottom: 22px; line-height: 1.5; }<br \/>\n.gsr-disclaimer a { color: var(--green); }<\/p>\n<p>\/* Buttons *\/<br \/>\n.gsr-btn-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }<br \/>\n.gsr-btn { padding: 11px 28px; border: none; border-radius: var(--radius); font-size: 15px; font-weight: 700; cursor: pointer; }<br \/>\n.gsr-btn-primary   { background: var(--green); color: #fff; }<br \/>\n.gsr-btn-primary:hover { background: var(--green-dark); }<br \/>\n.gsr-btn-primary:disabled { background: #aaa; cursor: not-allowed; }<br \/>\n.gsr-btn-secondary { background: #fff; color: var(--green); border: 2px solid var(--green); }<br \/>\n.gsr-btn-secondary:hover { background: var(--green-light); }<br \/>\n.gsr-btn-ghost { background: transparent; color: #666; border: 2px solid #ccc; }<br \/>\n.gsr-btn-ghost:hover { background: #f2f2f2; }<\/p>\n<p>hr.gsr-divider { border: none; border-top: 1px solid #e0e0e0; margin: 22px 0; }<\/p>\n<p>@media (max-width: 520px) {<br \/>\n  .gsr-cards { grid-template-columns: 1fr; }<br \/>\n  .gsr-btn-row { flex-direction: column; }<br \/>\n  .gsr-btn { width: 100%; text-align: center; }<br \/>\n}<br \/>\n@media print {<br \/>\n  .gsr-btn-row, .gsr-progress-wrap { display: none; }<br \/>\n}<\/p>\n<div class=\"gsr-estimator\">\n<h1 class=\"gsr-tool-title\">GSR Cost Estimator<\/h1>\n<p class=\"gsr-subtitle\">Build an estimated cost for your genomics experiment. Prices reflect FY27 internal (Dartmouth) rates.<\/p>\n<div class=\"gsr-progress-wrap\">\n<div class=\"gsr-progress-bar\" id=\"progressBar\"><\/div>\n<div class=\"gsr-progress-label\" id=\"progressLabel\"><\/div>\n<\/p><\/div>\n<div id=\"wizardContent\"><\/div>\n<\/div>\n<p>\/\/ \u2500\u2500\u2500 PRICING DATA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nvar P = {<br \/>\n  extraction:      13.84,<br \/>\n  extractionFFPE:  10.60,<br \/>\n  laborRate:      128.65,   \/\/ $\/hour<br \/>\n  tissueCellPrep: 138.75,<br \/>\n  sampleFixation: 155.32,<br \/>\n  flexTissuePrepFixation: 155.00,<br \/>\n  qubit:            2.47,<br \/>\n  fragAnalyzer:     8.68,<\/p>\n<p>  \/\/ recCycles: recommended sequencing kit cycle length for this assay<br \/>\n  bulk: {<br \/>\n    ribo:  { name: 'RNA-seq: Ribo(-)',  price: 93.05, defaultReads: 40, recCycles: 100 },<br \/>\n    e3end: { name: \"RNA-seq: 3'-End\",   price: 48.73, defaultReads: 10, recCycles: 100 },<br \/>\n    dna:   { name: 'DNA-seq',           price: 56.87, defaultReads: 30, recCycles: 300 }<br \/>\n  },<\/p>\n<p>  \/\/ readsM: maximum reads output per run; cycles: kit cycle length<br \/>\n  runs: [<br \/>\n    { id:'partial_10m',  name:'Partial run \u2014 10M reads',  price:53.27,   readsM:10,   cycles:100 },<br \/>\n    { id:'partial_50m',  name:'Partial run \u2014 50M reads',  price:151.74,  readsM:50,   cycles:100 },<br \/>\n    { id:'partial_250m', name:'Partial run \u2014 250M reads', price:802.77,  readsM:250,  cycles:100 },<br \/>\n    { id:'p1_100', name:'NextSeq P1 100-cycle', price:1179.18,  readsM:120,  cycles:100 },<br \/>\n    { id:'p1_300', name:'NextSeq P1 300-cycle', price:1413.68,  readsM:120,  cycles:300 },<br \/>\n    { id:'p2_100', name:'NextSeq P2 100-cycle', price:1505.37,  readsM:500,  cycles:100 },<br \/>\n    { id:'p2_300', name:'NextSeq P2 300-cycle', price:1797.58,  readsM:500,  cycles:300 },<br \/>\n    { id:'p2_600', name:'NextSeq P2 600-cycle', price:3531.43,  readsM:500,  cycles:600 },<br \/>\n    { id:'p3_100', name:'NextSeq P3 100-cycle', price:2103.00,  readsM:1300, cycles:100 },<br \/>\n    { id:'p3_300', name:'NextSeq P3 300-cycle', price:3111.57,  readsM:1300, cycles:300 },<br \/>\n    { id:'p4_100', name:'NextSeq P4 100-cycle', price:2807.93,  readsM:2000, cycles:100 },<br \/>\n    { id:'p4_300', name:'NextSeq P4 300-cycle', price:4283.03,  readsM:2000, cycles:300 },<br \/>\n    { id:'s2_300', name:'NovaSeq S2 300-cycle', price:7919.60,  readsM:3300, cycles:300 },<br \/>\n    { id:'s4_300', name:'NovaSeq S4 300-cycle', price:11740.69, readsM:8500, cycles:300 }<br \/>\n  ],<\/p>\n<p>  sc: {<br \/>\n    rna35:    { name: \"10x 3'\/5' RNA-seq\",         price: 1668.50 },<br \/>\n    atac:     { name: '10x ATAC-seq',              price: 1986.00 },<br \/>\n    multiome: { name: '10x Multiome (RNA + ATAC)', price: 3107.25 },<br \/>\n    flex:     { name: '10x Flex', runPrice: 422.97, samplePrice: 307.51 },<br \/>\n    hive:     { name: 'Honeycomb HIVE',            price:  837.77 }<br \/>\n  },<\/p>\n<p>  spatial: {<br \/>\n    visiumhd: { name: 'Visium HD',               price: 12887.70 },<br \/>\n    xenium:   { name: 'Xenium (Standard panel)', samplePrepPrice: 695.00, runPrice: 2520.00 },<br \/>\n    xenium5k: { name: 'Xenium (5K panel)',        samplePrepPrice: 695.00, runPrice: 7510.00 }<br \/>\n  }<br \/>\n};<\/p>\n<p>\/\/ \u2500\u2500\u2500 LABOR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\n\/\/ Returns { hours, batches, batchSize, hrsPerBatch, total }<br \/>\nfunction calcLabor(type, n) {<br \/>\n  var rate = P.laborRate, hrsPerBatch, batchSize;<br \/>\n  switch (type) {<br \/>\n    case 'dna':     hrsPerBatch = 6;  batchSize = 48; break;<br \/>\n    case 'rna':     hrsPerBatch = 8;  batchSize = 48; break;<br \/>\n    case 'sc':      hrsPerBatch = 8;  batchSize = 8;  break;<br \/>\n    case 'spatial': hrsPerBatch = 16; batchSize = 1;  break;  \/\/ Visium HD: per slide<br \/>\n    case 'xenium':  hrsPerBatch = 16; batchSize = 2;  break;  \/\/ Xenium: per 2-slide run<br \/>\n  }<br \/>\n  var batches = Math.ceil(n \/ batchSize);<br \/>\n  var hours   = batches * hrsPerBatch;<br \/>\n  return { hours: hours, batches: batches, batchSize: batchSize, hrsPerBatch: hrsPerBatch, total: hours * rate };<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STATE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nvar S = freshState();<br \/>\nfunction freshState() {<br \/>\n  return {<br \/>\n    category: null,<br \/>\n    bulkAssay: null, bulkSamples: 1, bulkReadsPerSample: null,<br \/>\n    bulkExtraction: false, bulkFFPE: false, bulkQC: false, bulkRun: null,<br \/>\n    scPlatform: null, scSamples: 1, scFixation: false, scTissuePrep: false,<br \/>\n    scCellsPerSample: 5000, scReadsPerCell: 25000, scRun: null,<br \/>\n    spatialPlatform: null, spatialSlides: 1, spatialTissuePrep: false<br \/>\n  };<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP SEQUENCES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nvar PATHS = {<br \/>\n  bulk:    ['start','bulk_assay','bulk_params','bulk_reads','bulk_summary'],<br \/>\n  sc:      ['start','sc_platform','sc_params','sc_summary'],<br \/>\n  spatial: ['start','spatial_platform','spatial_params','spatial_summary']<br \/>\n};<br \/>\nvar STEP_LABELS = {<br \/>\n  start:'Category', bulk_assay:'Assay', bulk_params:'Parameters',<br \/>\n  bulk_reads:'Sequencing', bulk_summary:'Estimate',<br \/>\n  sc_platform:'Platform', sc_params:'Parameters', sc_summary:'Estimate',<br \/>\n  spatial_platform:'Platform', spatial_params:'Parameters', spatial_summary:'Estimate'<br \/>\n};<\/p>\n<p>var currentStep = 'start';<br \/>\nfunction path()    { return S.category ? PATHS[S.category] : ['start']; }<br \/>\nfunction stepIdx() { return path().indexOf(currentStep); }<br \/>\nfunction goTo(id)  { currentStep = id; render(); }<br \/>\nfunction goBack()  { var p = path(), i = p.indexOf(currentStep); if (i &gt; 0) { currentStep = p[i-1]; render(); } }<\/p>\n<p>\/\/ \u2500\u2500\u2500 RENDER ROUTER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction render() {<br \/>\n  renderProgress();<br \/>\n  var html = '';<br \/>\n  switch (currentStep) {<br \/>\n    case 'start':            html = renderStart();          break;<br \/>\n    case 'bulk_assay':       html = renderBulkAssay();      break;<br \/>\n    case 'bulk_params':      html = renderBulkParams();     break;<br \/>\n    case 'bulk_reads':       html = renderBulkReads();      break;<br \/>\n    case 'bulk_summary':     html = renderBulkSummary();    break;<br \/>\n    case 'sc_platform':      html = renderSCPlatform();     break;<br \/>\n    case 'sc_params':        html = renderSCParams();       break;<br \/>\n    case 'sc_summary':       html = renderSCSummary();      break;<br \/>\n    case 'spatial_platform': html = renderSpatialPlat();    break;<br \/>\n    case 'spatial_params':   html = renderSpatialParams();  break;<br \/>\n    case 'spatial_summary':  html = renderSpatialSummary(); break;<br \/>\n  }<br \/>\n  document.getElementById('wizardContent').innerHTML = html;<br \/>\n  bindEvents();<br \/>\n}<\/p>\n<p>function renderProgress() {<br \/>\n  var p = path(), idx = p.indexOf(currentStep);<br \/>\n  document.getElementById('progressBar').innerHTML = p.map(function(s, i) {<br \/>\n    var cls = 'gsr-progress-step' + (i &lt; idx ? &#039; done&#039; : i === idx ? &#039; active&#039; : &#039;&#039;);<br \/>\n    return &#039;<\/p>\n<div class=\"' + cls + '\"><\/div>\n<p>';<br \/>\n  }).join('');<br \/>\n  document.getElementById('progressLabel').textContent =<br \/>\n    'Step ' + (idx+1) + ' of ' + p.length + ' \u2014 ' + (STEP_LABELS[currentStep] || '');<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: START \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderStart() {<br \/>\n  var cats = [<br \/>\n    ['bulk',    '&#x1f9ec;', 'Bulk DNA\/RNA Sequencing',  \"RNA-seq (Ribo-, 3'-End) or DNA-seq\"],<br \/>\n    ['sc',      '&#x1f52c;', 'Single Cell Genomics',      '10x Chromium: RNA-seq, ATAC, Multiome, Flex'],<br \/>\n    ['spatial', '&#x1f5fa;&#xfe0f;', 'Spatial Genomics',          'Visium HD or Xenium in situ']<br \/>\n  ];<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">What type of experiment are you planning?<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Select a category to get started.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-cards\">' +<br \/>\n    cats.map(function(c) {<br \/>\n      return '<\/p>\n<div class=\"gsr-card' + (S.category === c[0] ? ' selected' : '') +\n        '\" data-action=\"selectCat\" data-val=\"' + c[0] + '\">' +<br \/>\n        '<\/p>\n<div class=\"card-icon\">' + c[1] + '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"card-label\">' + c[2] + '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"card-desc\">'  + c[3] + '<\/div>\n<\/div>\n<p>';<br \/>\n    }).join('') + '<\/p><\/div>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-btn-row\"><button class=\"gsr-btn gsr-btn-primary\">Next \u2192<\/button><\/div>\n<p>';<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: BULK ASSAY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderBulkAssay() {<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">Select your sequencing assay<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Choose the library preparation type for your samples.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-cards\">' +<br \/>\n    Object.entries(P.bulk).map(function(e) {<br \/>\n      var k = e[0], a = e[1];<br \/>\n      return '<\/p>\n<div class=\"gsr-card' + (S.bulkAssay === k ? ' selected' : '') +\n        '\" data-action=\"selectBulkAssay\" data-val=\"' + k + '\">' +<br \/>\n        '<\/p>\n<div class=\"card-label\">' + a.name + '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"card-desc\">$' + a.price.toFixed(2) + '\/sample \u00b7 ' +<br \/>\n        a.defaultReads + 'M reads\/sample recommended<br \/>' +<br \/>\n        a.recCycles + '-cycle sequencing kit recommended<\/div>\n<\/div>\n<p>';<br \/>\n    }).join('') + '<\/p><\/div>\n<p>' +<br \/>\n    navBtns('back', 'bulk_params', !S.bulkAssay);<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: BULK PARAMS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderBulkParams() {<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">Experiment parameters<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Enter your sample count and select add-ons.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-field\">' +<br \/>\n      '<label for=\"bulkSamples\">Number of samples<\/label>' +<br \/>\n      '' +<br \/>\n    '<\/div>\n<p>' +<br \/>\n    '<\/p>\n<hr class=\"gsr-divider\">' +<br \/>\n    '<\/p>\n<div class=\"gsr-field\"><label>Optional add-ons<\/label>' +<br \/>\n      '<\/p>\n<div class=\"gsr-check-group\">' +<br \/>\n        '<label class=\"gsr-check-item\">' +<br \/>\n          '' +<br \/>\n          '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Include nucleic acid extraction<\/div>\n<p>' +<br \/>\n          '<\/p>\n<div class=\"cb-price\">$13.84\/sample (standard) \u00b7 $10.60\/sample (FFPE)<\/div>\n<p><\/span>' +<br \/>\n        '<\/label>' +<br \/>\n        '<\/p>\n<div class=\"gsr-sub-check\" id=\"ffpeWrap\">' +<br \/>\n          '<label class=\"gsr-check-item\">' +<br \/>\n            '' +<br \/>\n            '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">FFPE samples<\/div>\n<p>' +<br \/>\n            '<\/p>\n<div class=\"cb-price\">Uses FFPE extraction rate ($10.60\/sample)<\/div>\n<p><\/span>' +<br \/>\n          '<\/label>' +<br \/>\n        '<\/div>\n<p>' +<br \/>\n        '<label class=\"gsr-check-item\">' +<br \/>\n          '' +<br \/>\n          '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Include QC (Qubit + Fragment Analyzer)<\/div>\n<p>' +<br \/>\n          '<\/p>\n<div class=\"cb-price\">$' + (P.qubit + P.fragAnalyzer).toFixed(2) + '\/sample<\/div>\n<p><\/span>' +<br \/>\n        '<\/label>' +<br \/>\n      '<\/div>\n<p>' +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    navBtns('back', 'bulk_reads');<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: BULK READS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction calcRunOptions(totalReadsM, numSamples) {<br \/>\n  numSamples = numSamples || S.bulkSamples;<br \/>\n  return P.runs.map(function(r) {<br \/>\n    var n = Math.ceil(totalReadsM \/ r.readsM);<br \/>\n    var cost = n * r.price;<br \/>\n    return { id:r.id, name:r.name, readsM:r.readsM, cycles:r.cycles,<br \/>\n             numRuns:n, totalCost:cost, perSample: cost \/ numSamples };<br \/>\n  });<br \/>\n}<\/p>\n<p>function bestRun(options, recCycles) {<br \/>\n  \/\/ Prefer recommended cycle length; fall back to all options<br \/>\n  var pool = options.filter(function(r) { return r.cycles === recCycles; });<br \/>\n  if (!pool.length) pool = options;<br \/>\n  return pool.reduce(function(b, r) { return r.totalCost &lt; b.totalCost ? r : b; });<br \/>\n}<\/p>\n<p>function buildRunRows(options, recRun, recCycles, radioName, selectedId) {<br \/>\n  radioName  = radioName  || &#039;runRadio&#039;;<br \/>\n  selectedId = selectedId !== undefined ? selectedId : S.bulkRun;<br \/>\n  return options.map(function(r) {<br \/>\n    var isBest = r.id === recRun.id;<br \/>\n    var rowCls = isBest ? &#039; class=&quot;rec-row&quot;&#039; : &#039;&#039;;<br \/>\n    var badges = isBest ? &#039;<span class=\"badge badge-best\">Recommended kit<\/span>' : '';<br \/>\n    var checked = selectedId === r.id ? ' checked' : (isBest &amp;&amp; !selectedId ? ' checked' : '');<br \/>\n    var readsLabel = r.readsM &gt;= 1000 ? (r.readsM\/1000).toFixed(1) + 'B' : r.readsM + 'M';<br \/>\n    return '<\/p>\n<tr>' +<br \/>\n      '<\/p>\n<td class=\"run-name\">' + r.name + badges + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td>' + readsLabel + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td style=\"text-align:center\">' + r.numRuns + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td style=\"text-align:right\">$' + fmt(r.totalCost) + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td style=\"text-align:right\">$' + fmt(r.perSample) + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td style=\"text-align:center\"><\/td>\n<p>' +<br \/>\n      '<\/tr>\n<p>';<br \/>\n  }).join('');<br \/>\n}<\/p>\n<p>function renderBulkReads() {<br \/>\n  var assay    = P.bulk[S.bulkAssay];<br \/>\n  var rps      = S.bulkReadsPerSample !== null ? S.bulkReadsPerSample : assay.defaultReads;<br \/>\n  var totalM   = S.bulkSamples * rps;<br \/>\n  var options  = calcRunOptions(totalM);<br \/>\n  var rec      = bestRun(options, assay.recCycles);<br \/>\n  if (!S.bulkRun) S.bulkRun = rec.id;<\/p>\n<p>  return '<\/p>\n<div class=\"gsr-step-title\">Sequencing configuration<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Adjust reads per sample if needed, then select a sequencing run type.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-reads-box\">' +<br \/>\n      '<\/p>\n<div class=\"reads-header\">Reads per sample \u2014 ' + assay.name + '<\/div>\n<p>' +<br \/>\n      '<\/p>\n<div class=\"gsr-reads-row\">' +<br \/>\n        '' +<br \/>\n        '<span>million reads per sample<\/span>' +<br \/>\n      '<\/div>\n<p>' +<br \/>\n      '<\/p>\n<div class=\"gsr-reads-total\">Total reads needed: <strong id=\"totalDisplay\">' +<br \/>\n        totalM.toLocaleString() + 'M<\/strong> (' + S.bulkSamples + ' samples \u00d7 ' + rps + 'M)<\/div>\n<p>' +<br \/>\n      '<\/p>\n<div class=\"gsr-rec-note\">Recommended kit for this assay: <strong>' + assay.recCycles + '-cycle<\/strong><\/div>\n<p>' +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-run-options\">\n<h4>Select a sequencing run<\/h4>\n<p>' +<br \/>\n      '<\/p>\n<div class=\"gsr-run-scroll\">' +<br \/>\n        '<\/p>\n<table class=\"gsr-run-table\">' +<br \/>\n          '<\/p>\n<thead>\n<tr>\n<th>Run type<\/th>\n<th>Max output<\/th>\n<p>' +<br \/>\n          '<\/p>\n<th style=\"text-align:center\">Runs needed<\/th>\n<p>' +<br \/>\n          '<\/p>\n<th style=\"text-align:right\">Run cost<\/th>\n<p>' +<br \/>\n          '<\/p>\n<th style=\"text-align:right\">$\/sample<\/th>\n<p>' +<br \/>\n          '<\/p>\n<th style=\"text-align:center\">Select<\/th>\n<\/tr>\n<\/thead>\n<p>' +<br \/>\n          '<\/p>\n<tbody id=\"runTableBody\">' + buildRunRows(options, rec, assay.recCycles) + '<\/tbody>\n<p>' +<br \/>\n        '<\/table>\n<p>' +<br \/>\n      '<\/p><\/div>\n<p>' +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    navBtns('back', 'bulk_summary', false, 'View Estimate \u2192');<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: BULK SUMMARY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderBulkSummary() {<br \/>\n  var assay  = P.bulk[S.bulkAssay];<br \/>\n  var n      = S.bulkSamples;<br \/>\n  var rps    = S.bulkReadsPerSample !== null ? S.bulkReadsPerSample : assay.defaultReads;<br \/>\n  var totalM = n * rps;<br \/>\n  var run    = P.runs.find(function(r) { return r.id === S.bulkRun; });<br \/>\n  var numRuns = Math.ceil(totalM \/ run.readsM);<br \/>\n  var laborType = S.bulkAssay === 'dna' ? 'dna' : 'rna';<br \/>\n  var labor  = calcLabor(laborType, n);<\/p>\n<p>  var lines = [];<br \/>\n  lines.push({ label:'Library prep \u2014 ' + assay.name, qty:n, unit:assay.price, qtyl:'samples' });<br \/>\n  if (S.bulkExtraction) {<br \/>\n    var ep = S.bulkFFPE ? P.extractionFFPE : P.extraction;<br \/>\n    lines.push({ label:'Nucleic acid extraction' + (S.bulkFFPE ? ' (FFPE)' : ''), qty:n, unit:ep, qtyl:'samples' });<br \/>\n  }<br \/>\n  if (S.bulkQC)<br \/>\n    lines.push({ label:'QC \u2014 Qubit + Fragment Analyzer', qty:n, unit:P.qubit+P.fragAnalyzer, qtyl:'samples' });<br \/>\n  lines.push({ label:'Sequencing \u2014 ' + run.name, qty:numRuns, unit:run.price, qtyl:'runs' });<br \/>\n  lines.push({<br \/>\n    label: 'Labor',<br \/>\n    qty: labor.hours, unit: P.laborRate, qtyl: 'hrs',<br \/>\n    note: labor.batches + ' batch' + (labor.batches &gt; 1 ? 'es' : '') +<br \/>\n          ' of up to ' + labor.batchSize + ' samples \u00d7 ' + labor.hrsPerBatch + ' hrs @ $' + fmt(P.laborRate) + '\/hr'<br \/>\n  });<br \/>\n  if (S.bulkExtraction) {<br \/>\n    var exBatches = Math.ceil(n \/ 24);<br \/>\n    var exHours   = exBatches * 3;<br \/>\n    lines.push({<br \/>\n      label: 'Labor \u2014 nucleic acid isolation',<br \/>\n      qty: exHours, unit: P.laborRate, qtyl: 'hrs',<br \/>\n      note: exBatches + ' batch' + (exBatches &gt; 1 ? 'es' : '') +<br \/>\n            ' of up to 24 samples \u00d7 3 hrs @ $' + fmt(P.laborRate) + '\/hr'<br \/>\n    });<br \/>\n  }<\/p>\n<p>  return summaryHTML(<br \/>\n    'Bulk Sequencing Estimate',<br \/>\n    n + ' sample' + (n&gt;1?'s':'') + ' \u00b7 ' + assay.name + ' \u00b7 ' + rps + 'M reads\/sample',<br \/>\n    lines<br \/>\n  );<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SC PLATFORM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSCPlatform() {<br \/>\n  var plats = [<br \/>\n    ['rna35',    \"10x 3'\/5' RNA-seq\",         'Gene expression profiling'],<br \/>\n    ['atac',     '10x ATAC-seq',              'Chromatin accessibility'],<br \/>\n    ['multiome', '10x Multiome (RNA + ATAC)', 'Both modalities from the same cells'],<br \/>\n    ['flex',     '10x Flex',                  'Fixed\/FFPE samples; base run + per-sample cost'],<br \/>\n    ['hive',     'Honeycomb HIVE',            'Single-cell RNA-seq alternative']<br \/>\n  ];<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">Select your single cell platform<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Choose your 10x Chromium product or platform.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-cards\">' +<br \/>\n    plats.map(function(pl) {<br \/>\n      return '<\/p>\n<div class=\"gsr-card' + (S.scPlatform === pl[0] ? ' selected' : '') +\n        '\" data-action=\"selectSCPlatform\" data-val=\"' + pl[0] + '\">' +<br \/>\n        '<\/p>\n<div class=\"card-label\">' + pl[1] + '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"card-desc\">' + pl[2] + '<\/div>\n<\/div>\n<p>';<br \/>\n    }).join('') + '<\/p><\/div>\n<p>' +<br \/>\n    navBtns('back', 'sc_params', !S.scPlatform);<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SC PARAMS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSCParams() {<br \/>\n  var isFlex     = S.scPlatform === 'flex';<br \/>\n  var hasSeq     = ['flex','rna35','atac','multiome'].indexOf(S.scPlatform) !== -1;<br \/>\n  var noFixation = ['flex','atac','multiome','rna35'].indexOf(S.scPlatform) !== -1;<\/p>\n<p>  \/\/ Add-ons section<br \/>\n  var addOns = isFlex<br \/>\n    ? '<label class=\"gsr-check-item\">' +<br \/>\n        '' +<br \/>\n        '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Tissue\/cell prep and fixation<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"cb-price\">$' + fmt(P.flexTissuePrepFixation) + '\/sample<\/div>\n<p><\/span>' +<br \/>\n      '<\/label>'<br \/>\n    : (noFixation ? '' :<br \/>\n        '<label class=\"gsr-check-item\">' +<br \/>\n          '' +<br \/>\n          '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Sample fixation (10x)<\/div>\n<p>' +<br \/>\n          '<\/p>\n<div class=\"cb-price\">$' + P.sampleFixation.toFixed(2) + '\/sample<\/div>\n<p><\/span>' +<br \/>\n        '<\/label>') +<br \/>\n      '<label class=\"gsr-check-item\">' +<br \/>\n        '' +<br \/>\n        '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Tissue\/cell preparation<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"cb-price\">$' + P.tissueCellPrep.toFixed(2) + '\/sample<\/div>\n<p><\/span>' +<br \/>\n      '<\/label>';<\/p>\n<p>  \/\/ Sequencing section (Flex, 3'\/5' RNA, ATAC, Multiome)<br \/>\n  var scSeq = '';<br \/>\n  if (hasSeq) {<br \/>\n    var defRpc  = S.scPlatform === 'rna35' ? 30000 : 25000;<br \/>\n    var cells   = S.scCellsPerSample || 5000;<br \/>\n    var rpc     = S.scReadsPerCell   || defRpc;<br \/>\n    var totalM  = S.scSamples * cells * rpc \/ 1e6;<br \/>\n    var runMult = S.scPlatform === 'multiome' ? 2 : 1;<br \/>\n    var options = calcRunOptions(totalM, S.scSamples).map(function(r) {<br \/>\n      var nr = r.numRuns * runMult, tc = r.totalCost * runMult;<br \/>\n      return { id:r.id, name:r.name, readsM:r.readsM, cycles:r.cycles,<br \/>\n               numRuns:nr, totalCost:tc, perSample:tc \/ S.scSamples };<br \/>\n    });<br \/>\n    var rec     = bestRun(options, 100);<br \/>\n    if (!S.scRun) S.scRun = rec.id;<br \/>\n    var multiomeNote = S.scPlatform === 'multiome'<br \/>\n      ? ' <em>\u00d7 2 runs (RNA + ATAC libraries)<\/em>' : '';<br \/>\n    scSeq =<br \/>\n      '<\/p>\n<hr class=\"gsr-divider\">' +<br \/>\n      '<\/p>\n<div class=\"gsr-reads-box\">' +<br \/>\n        '<\/p>\n<div class=\"reads-header\">Sequencing depth<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"gsr-reads-row\">' +<br \/>\n          '' +<br \/>\n          '<span>cells per sample<\/span>' +<br \/>\n        '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"gsr-reads-row\">' +<br \/>\n          '' +<br \/>\n          '<span>reads per cell <em>(recommended: ' + defRpc.toLocaleString() + ')<\/em>' + multiomeNote + '<\/span>' +<br \/>\n        '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"gsr-reads-total\">Total reads needed: <strong id=\"scTotalDisplay\">' +<br \/>\n          Math.round(totalM * runMult).toLocaleString() + 'M<\/strong>' +<br \/>\n          ' (' + S.scSamples + ' sample' + (S.scSamples &gt; 1 ? 's' : '') + ' \u00d7 ' +<br \/>\n          cells.toLocaleString() + ' cells \u00d7 ' + rpc.toLocaleString() + ' reads' +<br \/>\n          (runMult &gt; 1 ? ' \u00d7 2 libraries' : '') + ')<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"gsr-rec-note\">Recommended kit: <strong>100-cycle<\/strong><\/div>\n<p>' +<br \/>\n      '<\/p><\/div>\n<p>' +<br \/>\n      '<\/p>\n<div class=\"gsr-run-options\">\n<h4>Select a sequencing run<\/h4>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"gsr-run-scroll\">' +<br \/>\n          '<\/p>\n<table class=\"gsr-run-table\">' +<br \/>\n            '<\/p>\n<thead>\n<tr>\n<th>Run type<\/th>\n<th>Max output<\/th>\n<p>' +<br \/>\n            '<\/p>\n<th style=\"text-align:center\">Runs needed<\/th>\n<p>' +<br \/>\n            '<\/p>\n<th style=\"text-align:right\">Run cost<\/th>\n<p>' +<br \/>\n            '<\/p>\n<th style=\"text-align:right\">$\/sample<\/th>\n<p>' +<br \/>\n            '<\/p>\n<th style=\"text-align:center\">Select<\/th>\n<\/tr>\n<\/thead>\n<p>' +<br \/>\n            '<\/p>\n<tbody id=\"scRunTableBody\">' + buildRunRows(options, rec, 100, 'scRunRadio', S.scRun) + '<\/tbody>\n<p>' +<br \/>\n          '<\/table>\n<p>' +<br \/>\n        '<\/p><\/div>\n<p>' +<br \/>\n      '<\/p><\/div>\n<p>';<br \/>\n  }<\/p>\n<p>  return '<\/p>\n<div class=\"gsr-step-title\">Experiment parameters<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">' +<br \/>\n    (isFlex ? 'Flex pricing: one base run cost plus a per-sample cost.' : 'Enter the number of samples for your experiment.') +<br \/>\n    '<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-field\">' +<br \/>\n      '<label for=\"scSamples\">Number of samples<\/label>' +<br \/>\n      '' +<br \/>\n      (isFlex ? '<\/p>\n<div class=\"field-note\">Run: $' + P.sc.flex.runPrice.toFixed(2) +<br \/>\n        ' + $' + P.sc.flex.samplePrice.toFixed(2) + '\/sample<\/div>\n<p>' : '') +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    '<\/p>\n<hr class=\"gsr-divider\">' +<br \/>\n    '<\/p>\n<div class=\"gsr-field\"><label>Optional add-ons<\/label>' +<br \/>\n      '<\/p>\n<div class=\"gsr-check-group\">' + addOns + '<\/div>\n<p>' +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    scSeq +<br \/>\n    navBtns('back', 'sc_summary', false, 'View Estimate \u2192');<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SC SUMMARY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSCSummary() {<br \/>\n  var n      = S.scSamples;<br \/>\n  var isFlex = S.scPlatform === 'flex';<br \/>\n  var pl     = P.sc[S.scPlatform];<br \/>\n  var labor  = calcLabor('sc', n);<br \/>\n  var lines  = [];<\/p>\n<p>  var hasSeq = ['flex','rna35','atac','multiome'].indexOf(S.scPlatform) !== -1;<\/p>\n<p>  if (isFlex) {<br \/>\n    lines.push({ label:'10x Flex \u2014 Run cost',   qty:1, unit:P.sc.flex.runPrice,    qtyl:'run'     });<br \/>\n    lines.push({ label:'10x Flex \u2014 Per sample', qty:n, unit:P.sc.flex.samplePrice, qtyl:'samples' });<br \/>\n    if (S.scTissuePrep)<br \/>\n      lines.push({ label:'Tissue\/cell prep and fixation', qty:n, unit:P.flexTissuePrepFixation, qtyl:'samples' });<br \/>\n  } else {<br \/>\n    lines.push({ label:pl.name, qty:n, unit:pl.price, qtyl:'samples' });<br \/>\n    if (S.scFixation)   lines.push({ label:'Sample fixation (10x)',   qty:n, unit:P.sampleFixation, qtyl:'samples' });<br \/>\n    if (S.scTissuePrep) lines.push({ label:'Tissue\/cell preparation', qty:n, unit:P.tissueCellPrep, qtyl:'samples' });<br \/>\n  }<br \/>\n  if (hasSeq &amp;&amp; S.scRun) {<br \/>\n    var scRunData  = P.runs.find(function(r) { return r.id === S.scRun; });<br \/>\n    var scCells    = S.scCellsPerSample || 5000;<br \/>\n    var scRpc      = S.scReadsPerCell   || (S.scPlatform === 'rna35' ? 30000 : 25000);<br \/>\n    var scTotalM   = n * scCells * scRpc \/ 1e6;<br \/>\n    var scRunMult  = S.scPlatform === 'multiome' ? 2 : 1;<br \/>\n    var scNumRuns  = Math.ceil(scTotalM \/ scRunData.readsM) * scRunMult;<br \/>\n    lines.push({<br \/>\n      label: 'Sequencing \u2014 ' + scRunData.name,<br \/>\n      qty: scNumRuns, unit: scRunData.price, qtyl: 'runs',<br \/>\n      note: S.scPlatform === 'multiome'<br \/>\n        ? n + ' sample' + (n&gt;1?'s':'') + ' \u00d7 ' + scCells.toLocaleString() +<br \/>\n          ' cells \u00d7 25,000 reads\/cell \u00d7 2 libraries (RNA + ATAC)'<br \/>\n        : n + ' sample' + (n&gt;1?'s':'') + ' \u00d7 ' + scCells.toLocaleString() +<br \/>\n          ' cells \u00d7 ' + scRpc.toLocaleString() + ' reads\/cell'<br \/>\n    });<br \/>\n  }<br \/>\n  lines.push({<br \/>\n    label: 'Labor',<br \/>\n    qty: labor.hours, unit: P.laborRate, qtyl: 'hrs',<br \/>\n    note: labor.batches + ' batch' + (labor.batches &gt; 1 ? 'es' : '') +<br \/>\n          ' of up to ' + labor.batchSize + ' samples \u00d7 ' + labor.hrsPerBatch + ' hrs @ $' + fmt(P.laborRate) + '\/hr'<br \/>\n  });<\/p>\n<p>  var seqNote = hasSeq ? '' :<br \/>\n    '<\/p>\n<div class=\"gsr-info-box\">&#x1f4cc; <strong>Sequencing not included above.<\/strong> ' +<br \/>\n    'Library sequencing is quoted separately based on your target read depth. ' +<br \/>\n    '100-cycle kits are recommended for most single cell workflows. ' +<br \/>\n    '<a href=\"\/gsr\/contact\/\" style=\"color:var(--green-dark)\">Contact the GSR<\/a> to discuss sequencing options for your project.<\/div>\n<p>';<\/p>\n<p>  return summaryHTML(<br \/>\n    'Single Cell Estimate',<br \/>\n    n + ' sample' + (n&gt;1?'s':'') + ' \u00b7 ' + (isFlex ? '10x Flex' : pl.name),<br \/>\n    lines,<br \/>\n    seqNote<br \/>\n  );<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SPATIAL PLATFORM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSpatialPlat() {<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">Select your spatial platform<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">Choose a spatial genomics platform.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-cards\">' +<br \/>\n    [<br \/>\n      ['visiumhd', 'Visium HD',              'Whole transcriptome \u00b7 FFPE tissue \u00b7 2\u00b5m bin resolution'],<br \/>\n      ['xenium',   'Xenium (Standard panel)','In situ sequencing \u00b7 targeted gene panels'],<br \/>\n      ['xenium5k', 'Xenium (5K panel)',       'In situ sequencing \u00b7 up to 5,000 genes']<br \/>\n    ].map(function(pl) {<br \/>\n      return '<\/p>\n<div class=\"gsr-card' + (S.spatialPlatform === pl[0] ? ' selected' : '') +\n        '\" data-action=\"selectSpatialPlatform\" data-val=\"' + pl[0] + '\">' +<br \/>\n        '<\/p>\n<div class=\"card-label\">' + pl[1] + '<\/div>\n<p>' +<br \/>\n        '<\/p>\n<div class=\"card-desc\">' + pl[2] + '<\/div>\n<\/div>\n<p>';<br \/>\n    }).join('') + '<\/p><\/div>\n<p>' +<br \/>\n    navBtns('back', 'spatial_params', !S.spatialPlatform);<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SPATIAL PARAMS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSpatialParams() {<br \/>\n  var isVisium = S.spatialPlatform === 'visiumhd';<br \/>\n  var isXenium = !isVisium;<br \/>\n  var addOns = isXenium<br \/>\n    ? '<\/p>\n<hr class=\"gsr-divider\">' +<br \/>\n      '<\/p>\n<div class=\"gsr-field\"><label>Optional add-ons<\/label>' +<br \/>\n        '<\/p>\n<div class=\"gsr-check-group\">' +<br \/>\n          '<label class=\"gsr-check-item\">' +<br \/>\n            '' +<br \/>\n            '<span class=\"cb-text\"><\/p>\n<div class=\"cb-label\">Xenium slide prep<\/div>\n<p>' +<br \/>\n            '<\/p>\n<div class=\"cb-price\">$' + P.spatial.xenium.samplePrepPrice.toFixed(2) + '\/slide<\/div>\n<p><\/span>' +<br \/>\n          '<\/label>' +<br \/>\n        '<\/div>\n<p>' +<br \/>\n      '<\/p><\/div>\n<p>'<br \/>\n    : '';<br \/>\n  return '<\/p>\n<div class=\"gsr-step-title\">Experiment parameters<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">' + (isVisium<br \/>\n      ? 'Each Visium HD slide cost covers 4 capture areas.'<br \/>\n      : 'Pricing is per slide. Note: each Xenium run requires 2 slides \u2014 contact us if you would like to share a run with another project.') + '<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-field\">' +<br \/>\n      '<label for=\"spatialSlides\">Number of slides<\/label>' +<br \/>\n      '' +<br \/>\n    '<\/div>\n<p>' +<br \/>\n    addOns +<br \/>\n    navBtns('back', 'spatial_summary', false, 'View Estimate \u2192');<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 STEP: SPATIAL SUMMARY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction renderSpatialSummary() {<br \/>\n  var n        = S.spatialSlides;<br \/>\n  var isVisium = S.spatialPlatform === 'visiumhd';<br \/>\n  var pl       = P.spatial[S.spatialPlatform];<br \/>\n  var labor    = calcLabor(isVisium ? 'spatial' : 'xenium', n);<br \/>\n  var lines    = [];<\/p>\n<p>  var p3_100 = P.runs.find(function(r) { return r.id === 'p3_100'; });<br \/>\n  if (isVisium) {<br \/>\n    lines.push({ label:'Visium HD \u2014 per slide (4 capture areas)', qty:n, unit:pl.price, qtyl:'slides' });<br \/>\n    lines.push({<br \/>\n      label: 'Sequencing \u2014 NextSeq P3 100-cycle',<br \/>\n      qty: n, unit: p3_100.price, qtyl: 'runs',<br \/>\n      note: 'Assumes 100% tissue coverage \u00b7 250M reads\/capture area (1,000M reads\/slide)'<br \/>\n    });<br \/>\n  } else {<br \/>\n    lines.push({ label:pl.name + ' \u2014 run cost', qty:n, unit:pl.runPrice, qtyl:'slides' });<br \/>\n    if (S.spatialTissuePrep)<br \/>\n      lines.push({ label:'Xenium slide prep', qty:n, unit:pl.samplePrepPrice, qtyl:'slides' });<br \/>\n  }<br \/>\n  lines.push({<br \/>\n    label: 'Labor',<br \/>\n    qty: labor.hours, unit: P.laborRate, qtyl: 'hrs',<br \/>\n    note: n + ' slide' + (n&gt;1?'s':'') + ' \u00d7 ' + labor.hrsPerBatch + ' hrs\/slide @ $' + fmt(P.laborRate) + '\/hr'<br \/>\n  });<\/p>\n<p>  var seqNote = '';<\/p>\n<p>  return summaryHTML(<br \/>\n    'Spatial Genomics Estimate',<br \/>\n    n + ' slide' + (n&gt;1?'s':'') + ' \u00b7 ' + pl.name,<br \/>\n    lines,<br \/>\n    seqNote<br \/>\n  );<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 SHARED SUMMARY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction summaryHTML(title, subtitle, lines, extraNote) {<br \/>\n  var total = 0;<br \/>\n  var rows = lines.map(function(l) {<br \/>\n    var cost = l.qty * l.unit;<br \/>\n    total += cost;<br \/>\n    var mainRow = '<\/p>\n<tr>' +<br \/>\n      '<\/p>\n<td>' + l.label + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td>' + l.qty + ' ' + l.qtyl + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td class=\"right\">$' + fmt(l.unit) + '<\/td>\n<p>' +<br \/>\n      '<\/p>\n<td class=\"right\"><strong>$' + fmt(cost) + '<\/strong><\/td>\n<p>' +<br \/>\n      '<\/tr>\n<p>';<br \/>\n    var noteRow = l.note<br \/>\n      ? '<\/p>\n<tr>\n<td colspan=\"4\" class=\"note\">' + l.note + '<\/td>\n<\/tr>\n<p>'<br \/>\n      : '';<br \/>\n    return mainRow + noteRow;<br \/>\n  }).join('');<\/p>\n<p>  return '<\/p>\n<div class=\"gsr-step-title\">' + title + '<\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-step-desc\">' + subtitle + '<\/p>\n<p>' +<br \/>\n    (extraNote || '') +<br \/>\n    '<\/p>\n<div class=\"gsr-summary-box\">' +<br \/>\n      '<\/p>\n<table class=\"gsr-summary-table\">' +<br \/>\n        '<\/p>\n<thead>\n<tr>\n<th>Line item<\/th>\n<th>Quantity<\/th>\n<p>' +<br \/>\n        '<\/p>\n<th class=\"right\">Unit price<\/th>\n<th class=\"right\">Subtotal<\/th>\n<\/tr>\n<\/thead>\n<p>' +<br \/>\n        '<\/p>\n<tbody>' + rows +<br \/>\n          '<\/p>\n<tr class=\"total-row\">' +<br \/>\n            '<\/p>\n<td colspan=\"3\">Estimated Total<\/td>\n<p>' +<br \/>\n            '<\/p>\n<td class=\"right\">$' + fmt(total) + '<\/td>\n<p>' +<br \/>\n          '<\/tr>\n<p>' +<br \/>\n        '<\/tbody>\n<p>' +<br \/>\n      '<\/table>\n<p>' +<br \/>\n    '<\/p><\/div>\n<p>' +<br \/>\n    '<\/p>\n<p class=\"gsr-disclaimer\">This is an estimate only, based on FY27 internal (Dartmouth) rates. ' +<br \/>\n    'Actual costs may vary depending on experimental complexity and final scope. ' +<br \/>\n    '<a href=\"mailto:gmbsr@groups.dartmouth.edu\">Contact the GSR<\/a> to discuss your project and receive a formal quote.<\/p>\n<p>' +<br \/>\n    '<\/p>\n<div class=\"gsr-btn-row\">' +<br \/>\n      '<button class=\"gsr-btn gsr-btn-ghost\" data-action=\"startOver\">\u2190 Start Over<\/button>' +<br \/>\n      '<button class=\"gsr-btn gsr-btn-primary\">Print Estimate<\/button>' +<br \/>\n    '<\/div>\n<p>';<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 HELPERS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction fmt(n) { return n.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2}); }<br \/>\nfunction navBtns(back, next, nextDisabled, nextLabel) {<br \/>\n  var bb = back === 'back'<br \/>\n    ? '<button class=\"gsr-btn gsr-btn-secondary\" data-action=\"back\">\u2190 Back<\/button>' : '';<br \/>\n  var nb = next<br \/>\n    ? '<button class=\"gsr-btn gsr-btn-primary\" data-action=\"goTo\">' + (nextLabel || 'Next \u2192') + '<\/button>' : '';<br \/>\n  return '<\/p>\n<div class=\"gsr-btn-row\">' + bb + nb + '<\/div>\n<p>';<br \/>\n}<\/p>\n<p>function saveInputs() {<br \/>\n  var v;<br \/>\n  v = document.getElementById('bulkSamples');      if (v) S.bulkSamples    = Math.max(1, parseInt(v.value)||1);<br \/>\n  v = document.getElementById('bulkExtraction');   if (v) S.bulkExtraction  = v.checked;<br \/>\n  v = document.getElementById('bulkFFPE');         if (v) S.bulkFFPE        = v.checked;<br \/>\n  v = document.getElementById('bulkQC');           if (v) S.bulkQC          = v.checked;<br \/>\n  v = document.getElementById('readsInput');       if (v) S.bulkReadsPerSample = Math.max(1, parseInt(v.value)||1);<br \/>\n  v = document.querySelector('input[name=\"runRadio\"]:checked'); if (v) S.bulkRun = v.value;<br \/>\n  v = document.getElementById('scSamples');        if (v) S.scSamples            = Math.max(1, parseInt(v.value)||1);<br \/>\n  v = document.getElementById('scFixation');       if (v) S.scFixation           = v.checked;<br \/>\n  v = document.getElementById('scTissuePrep');     if (v) S.scTissuePrep         = v.checked;<br \/>\n  v = document.getElementById('scCellsInput');   if (v) S.scCellsPerSample   = Math.max(1, parseInt(v.value)||5000);<br \/>\n  v = document.getElementById('scReadsInput');   if (v) S.scReadsPerCell      = Math.max(1, parseInt(v.value)||25000);<br \/>\n  v = document.querySelector('input[name=\"scRunRadio\"]:checked'); if (v) S.scRun = v.value;<br \/>\n  v = document.getElementById('spatialSlides');    if (v) S.spatialSlides = Math.max(1, parseInt(v.value)||1);<br \/>\n  v = document.getElementById('spatialTissuePrep');if (v) S.spatialTissuePrep = v.checked;<br \/>\n}<\/p>\n<p>\/\/ \u2500\u2500\u2500 EVENT BINDING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<br \/>\nfunction bindEvents() {<br \/>\n  document.querySelectorAll('[data-action]').forEach(function(el) {<br \/>\n    el.addEventListener('click', handleClick);<br \/>\n  });<\/p>\n<p>  var bulkEx = document.getElementById('bulkExtraction');<br \/>\n  if (bulkEx) {<br \/>\n    bulkEx.addEventListener('change', function() {<br \/>\n      S.bulkExtraction = this.checked;<br \/>\n      var w = document.getElementById('ffpeWrap');<br \/>\n      if (w) w.style.display = this.checked ? '' : 'none';<br \/>\n    });<br \/>\n  }<\/p>\n<p>  var ri = document.getElementById('readsInput');<br \/>\n  if (ri) {<br \/>\n    ri.addEventListener('input', function() {<br \/>\n      var rps = Math.max(1, parseInt(this.value)||1);<br \/>\n      S.bulkReadsPerSample = rps;<br \/>\n      var totalM = S.bulkSamples * rps;<br \/>\n      var disp = document.getElementById('totalDisplay');<br \/>\n      if (disp) disp.textContent = totalM.toLocaleString() + 'M';<br \/>\n      var tbody = document.getElementById('runTableBody');<br \/>\n      if (tbody) {<br \/>\n        var assay   = P.bulk[S.bulkAssay];<br \/>\n        var options = calcRunOptions(totalM);<br \/>\n        var rec     = bestRun(options, assay.recCycles);<br \/>\n        tbody.innerHTML = buildRunRows(options, rec, assay.recCycles);<br \/>\n        document.querySelectorAll('input[name=\"runRadio\"]').forEach(function(r) {<br \/>\n          r.addEventListener('change', function() { S.bulkRun = this.value; });<br \/>\n        });<br \/>\n      }<br \/>\n    });<br \/>\n  }<\/p>\n<p>  document.querySelectorAll('input[name=\"runRadio\"]').forEach(function(r) {<br \/>\n    r.addEventListener('change', function() { S.bulkRun = this.value; });<br \/>\n  });<\/p>\n<p>  function updateSCTotal() {<br \/>\n    var cells   = S.scCellsPerSample || 5000;<br \/>\n    var rpc     = S.scReadsPerCell   || (S.scPlatform === 'rna35' ? 30000 : 25000);<br \/>\n    var totalM  = S.scSamples * cells * rpc \/ 1e6;<br \/>\n    var runMult = S.scPlatform === 'multiome' ? 2 : 1;<br \/>\n    var disp    = document.getElementById('scTotalDisplay');<br \/>\n    if (disp) disp.textContent = Math.round(totalM * runMult).toLocaleString() + 'M';<br \/>\n    var tbody = document.getElementById('scRunTableBody');<br \/>\n    if (tbody) {<br \/>\n      var options = calcRunOptions(totalM, S.scSamples).map(function(r) {<br \/>\n        var nr = r.numRuns * runMult, tc = r.totalCost * runMult;<br \/>\n        return { id:r.id, name:r.name, readsM:r.readsM, cycles:r.cycles,<br \/>\n                 numRuns:nr, totalCost:tc, perSample:tc \/ S.scSamples };<br \/>\n      });<br \/>\n      var rec = bestRun(options, 100);<br \/>\n      tbody.innerHTML = buildRunRows(options, rec, 100, 'scRunRadio', S.scRun);<br \/>\n      document.querySelectorAll('input[name=\"scRunRadio\"]').forEach(function(r) {<br \/>\n        r.addEventListener('change', function() { S.scRun = this.value; });<br \/>\n      });<br \/>\n    }<br \/>\n  }<\/p>\n<p>  var flexCi = document.getElementById('scCellsInput');<br \/>\n  if (flexCi) flexCi.addEventListener('input', function() {<br \/>\n    S.scCellsPerSample = Math.max(1, parseInt(this.value)||5000);<br \/>\n    updateSCTotal();<br \/>\n  });<\/p>\n<p>  var flexRi = document.getElementById('scReadsInput');<br \/>\n  if (flexRi) flexRi.addEventListener('input', function() {<br \/>\n    S.scReadsPerCell = Math.max(1, parseInt(this.value)||25000);<br \/>\n    updateSCTotal();<br \/>\n  });<\/p>\n<p>  document.querySelectorAll('input[name=\"scRunRadio\"]').forEach(function(r) {<br \/>\n    r.addEventListener('change', function() { S.scRun = this.value; });<br \/>\n  });<br \/>\n}<\/p>\n<p>function handleClick(e) {<br \/>\n  var action = this.dataset.action, val = this.dataset.val;<br \/>\n  switch (action) {<br \/>\n    case 'selectCat':<br \/>\n      S.category = val; S.bulkAssay = null; S.scPlatform = null; S.spatialPlatform = null;<br \/>\n      render(); break;<br \/>\n    case 'nextFromStart':<br \/>\n      if (!S.category) return;<br \/>\n      goTo(S.category === 'bulk' ? 'bulk_assay' : S.category === 'sc' ? 'sc_platform' : 'spatial_platform');<br \/>\n      break;<br \/>\n    case 'selectBulkAssay':<br \/>\n      S.bulkAssay = val; S.bulkReadsPerSample = null; S.bulkRun = null; render(); break;<br \/>\n    case 'selectSCPlatform':<br \/>\n      S.scPlatform = val;<br \/>\n      S.scReadsPerCell = val === 'rna35' ? 30000 : 25000;<br \/>\n      S.scRun = null;<br \/>\n      render(); break;<br \/>\n    case 'selectSpatialPlatform':<br \/>\n      S.spatialPlatform = val; render(); break;<br \/>\n    case 'goTo':   saveInputs(); goTo(val); break;<br \/>\n    case 'back':   saveInputs(); goBack(); break;<br \/>\n    case 'startOver': S = freshState(); currentStep = 'start'; render(); break;<br \/>\n  }<br \/>\n}<\/p>\n<p>render();<\/p>\n","protected":false},"excerpt":{"rendered":"<p>GSR Cost Estimator :root { &#8211;green: #006845; &#8211;green-dark: #004d33; &#8211;green-light: #e8f2ee; &#8211;green-mid: #b5d4c8; &#8211;text: #222; &#8211;muted: #555; &#8211;border: #ccc; &#8211;radius: 6px; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: Arial, sans-serif; color: var(&#8211;text); font-size: 16px; background: #fff; } .gsr-estimator { max-width: 800px; margin: 0 auto; [\u2026] <\/p>\n<div class=\"clear\"><\/div>\n<p><a class=\"more_link clearfix\" href=\"https:\/\/geiselmed.dartmouth.edu\/gsr\/cost-estimator\/\" rel=\"nofollow\">Read More<\/a><\/p>\n","protected":false},"author":36,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-703","page","type-page","status-publish","hentry","author-36"],"jetpack_shortlink":"https:\/\/wp.me\/PbaUZP-bl","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/pages\/703","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/users\/36"}],"replies":[{"embeddable":true,"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/comments?post=703"}],"version-history":[{"count":6,"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/pages\/703\/revisions"}],"predecessor-version":[{"id":820,"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/pages\/703\/revisions\/820"}],"wp:attachment":[{"href":"https:\/\/geiselmed.dartmouth.edu\/gsr\/wp-json\/wp\/v2\/media?parent=703"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}