qv / script.js
QuercusVelutina's picture
Here is the proposal: A Modular, Tag-less Quality-Control & Selection System for Protein Production in E. coli
e75b376 verified
Raw
History Blame Contribute Delete
11.6 kB
// Scrollytelling functionality
class ScrollytellingEngine {
constructor() {
this.scenes = document.querySelectorAll('.scene');
this.progressBar = document.querySelector('.progress-bar');
this.currentScene = 0;
this.init();
}
init() {
// Set up intersection observer for scene detection
this.setupIntersectionObserver();
// Set up scroll progress tracking
this.setupScrollProgress();
// Initialize first scene
this.activateScene(0);
}
setupIntersectionObserver() {
const options = {
root: null,
rootMargin: '0px',
threshold: this.buildThresholdList()
};
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const sceneIndex = parseInt(entry.target.dataset.scene);
this.activateScene(sceneIndex);
}
});
}, options);
this.scenes.forEach(scene => {
this.observer.observe(scene);
});
}
buildThresholdList() {
const thresholds = [];
const numSteps = 20;
for (let i = 1.0; i <= numSteps; i++) {
const ratio = i / numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}
setupScrollProgress() {
window.addEventListener('scroll', () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollProgress = (scrollTop / scrollHeight) * 100;
if (this.progressBar) {
this.progressBar.style.width = `${scrollProgress}%`;
}
});
}
activateScene(index) {
// Update all scenes
this.scenes.forEach((scene, i) => {
if (i === index) {
scene.classList.add('active');
} else {
scene.classList.remove('active');
}
});
this.currentScene = index;
this.updateVisualization(index);
}
updateVisualization(sceneIndex) {
const visualizationContainer = document.querySelector('.sticky > div');
if (!visualizationContainer) return;
// Update visualization based on current scene
switch(sceneIndex) {
case 0:
visualizationContainer.innerHTML = this.createScene0Visualization();
break;
case 1:
visualizationContainer.innerHTML = this.createScene1Visualization();
break;
case 2:
visualizationContainer.innerHTML = this.createScene2Visualization();
break;
case 3:
visualizationContainer.innerHTML = this.createScene3Visualization();
break;
case 4:
visualizationContainer.innerHTML = this.createScene4Visualization();
break;
case 5:
visualizationContainer.innerHTML = this.createScene5Visualization();
break;
case 6:
visualizationContainer.innerHTML = this.createScene6Visualization();
break;
case 7:
visualizationContainer.innerHTML = this.createScene7Visualization();
break;
case 8:
visualizationContainer.innerHTML = this.createScene8Visualization();
break;
default:
visualizationContainer.innerHTML = this.createDefaultVisualization();
}
}
createScene0Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Cheater Dynamics</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<circle cx="100" cy="150" r="20" fill="#10b981" class="good-cell" />
<circle cx="200" cy="150" r="20" fill="#ef4444" class="cheater-cell" />
<text x="100" y="190" fill="#d1d5db" text-anchor="middle" font-size="12">Good Cell</text>
<text x="200" y="190" fill="#d1d5db" text-anchor="middle" font-size="12">Cheater</text>
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Type-2 Cheaters: Transcribe but fail to translate</text>
</svg>
</div>
`;
}
createScene1Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Constraints & Requirements</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<text x="200" y="100" fill="#60a5fa" text-anchor="middle" font-size="16" font-weight="bold">No Tags Allowed</text>
<circle cx="150" cy="150" r="15" fill="#ef4444" />
<circle cx="250" cy="150" r="15" fill="#ef4444" />
<text x="150" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">His-tag</text>
<text x="250" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">Intein</text>
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Pristine Product, Universal QC</text>
</svg>
</div>
`;
}
createScene2Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Dual Stress Sensors</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<circle cx="120" cy="120" r="15" fill="#f59e0b" class="sensor-active" />
<circle cx="280" cy="120" r="15" fill="#f59e0b" class="sensor-active" />
<text x="120" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">σ32 Sensor</text>
<text x="280" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">σE/Cpx Sensor</text>
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Native Stress Pathways</text>
</svg>
</div>
`;
}
createScene3Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Production Gate Logic</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<text x="200" y="120" fill="#60a5fa" text-anchor="middle" font-size="16" font-weight="bold">(Scyt ∨ Senv) ∧ PM</text>
<rect x="180" y="140" width="40" height="20" fill="#8b5cf6" class="gate-active" rx="4" />
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Stress OR + ProductionMode AND</text>
</svg>
</div>
`;
}
createScene4Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Actuator & Latch</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<path d="M 100 150 L 150 150 L 150 130 L 180 150 L 150 170 L 150 150" fill="#dc2626" class="latch-flipped" />
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Flp-ssrA + FRT-Toxin</text>
</svg>
</div>
`;
}
createScene5Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Translation Certificate</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<path d="M 120 140 L 160 140 L 160 160 L 120 160 Z" fill="#059669" class="certificate-issued" />
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Ribosomal Re-initiation</text>
</svg>
</div>
`;
}
createScene6Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Dual-Plug Antidote</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<circle cx="120" cy="120" r="15" fill="#3b82f6" class="antidote-active" />
<circle cx="280" cy="120" r="15" fill="#3b82f6" class="antidote-active" />
<text x="120" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">Repressor</text>
<text x="280" y="180" fill="#d1d5db" text-anchor="middle" font-size="12">sRNA</text>
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Transcriptional + Translational Suppression</text>
</svg>
</div>
`;
}
createScene7Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">System Partitioning</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<rect x="80" y="80" width="120" height="40" fill="#1e40af" rx="4" />
<rect x="220" y="80" width="120" height="40" fill="#1e40af" rx="4" />
<text x="140" y="100" fill="#ffffff" text-anchor="middle" font-size="12">Genome Core</text>
<rect x="220" y="140" width="120" height="40" fill="#3730a3" rx="4" />
<text x="280" y="120" fill="#ffffff" text-anchor="middle" font-size="12">Plasmid App</text>
<text x="200" y="240" fill="#60a5fa" text-anchor="middle" font-size="14" font-weight="bold">Stable Decision Logic + Swappable Components</text>
</svg>
</div>
`;
}
createScene8Visualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">Performance Metrics</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<rect x="120" y="100" width="160" height="80" fill="#1e293b" rx="8" />
<text x="200" y="120" fill="#10b981" text-anchor="middle" font-size="16" font-weight="bold">≥10× Enrichment</text>
<text x="200" y="160" fill="#60a5fa" text-anchor="middle" font-size="14">Target Enrichment</text>
<text x="200" y="200" fill="#ef4444" text-anchor="middle" font-size="14">False Commit Risk ≤0.1%</text>
</svg>
</div>
`;
}
createDefaultVisualization() {
return `
<div class="w-full max-w-lg bg-gray-800 rounded-lg p-6 shadow-xl">
<h3 class="text-lg font-semibold text-blue-400 mb-4">System Overview</h3>
<svg viewBox="0 0 400 300" class="system-diagram">
<rect x="50" y="50" width="300" height="200" fill="#1e293b" rx="8" />
<text x="200" y="150" fill="#60a5fa" text-anchor="middle" dominant-baseline="middle" font-size="16" font-weight="bold">Synthetic Biology QC System</text>
</svg>
</div>
`;
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new ScrollytellingEngine();
// Set up reduced motion support
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.body.classList.add('reduced-motion');
}
});