algal-breath-engine/algal-breath.html
2026-07-18 10:20:57 +00:00

423 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Algal Breath Engine | Tammera Storey</title>
<style>
:root {
--void: #050507;
--card: #0f0f14;
--neon-green: #00ff88;
--deep-teal: #008c7a;
--amber-fire: #ff6b35;
--lab-white: #e8e8e8;
--grid-line: rgba(0, 255, 136, 0.15);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: var(--void);
color: var(--lab-white);
font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
line-height: 1.7;
overflow-x: hidden;
}
.canvas-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
}
/* Ambient glow field */
.glow-field {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 30%, rgba(0, 255, 136, 0.08) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(0, 140, 122, 0.06) 0%, transparent 35%),
repeating-linear-gradient(90deg, var(--grid-line) 0, transparent 1px, transparent 100px);
z-index: -1;
animation: pulse-glow 8s ease-in-out infinite alternate;
}
@keyframes pulse-glow {
0% { opacity: 0.7; }
100% { opacity: 1; }
}
header {
padding: 4rem 2rem 2rem;
text-align: center;
border-bottom: 1px solid var(--neon-green);
}
h1 {
font-size: clamp(2rem, 5vw, 4rem);
font-weight: 900;
letter-spacing: -0.03em;
background: linear-gradient(180deg, var(--neon-green), var(--deep-teal));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: 1rem;
text-transform: uppercase;
}
.subtitle {
font-size: 1.1rem;
color: var(--deep-teal);
max-width: 700px;
margin: 0 auto;
border-left: 3px solid var(--amber-fire);
padding-left: 1.5rem;
text-align: left;
}
main {
max-width: 1400px;
margin: 0 auto;
padding: 3rem 2rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
@media (max-width: 900px) {
main { grid-template-columns: 1fr; }
}
.panel {
background: var(--card);
border: 1px solid var(--deep-teal);
padding: 2rem;
position: relative;
}
.panel::top {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--neon-green), var(--amber-fire));
}
h2 {
font-size: 1.5rem;
color: var(--neon-green);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
}
h2::before {
content: '//';
color: var(--amber-fire);
font-size: 1rem;
}
.viz-container {
position: relative;
aspect-ratio: 16/9;
background: linear-gradient(180deg, #0a0a0f, #001a15);
border: 1px dashed var(--neon-green);
margin-bottom: 2rem;
overflow: hidden;
}
.cell-network {
width: 100%;
height: 100%;
position: relative;
}
.algal-cell {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, var(--neon-green), transparent 70%);
filter: blur(2px);
animation: bloom 4s ease-in-out infinite;
}
@keyframes bloom {
0%, 100% { opacity: 0.4; transform: scale(1); }
50% { opacity: 1; transform: scale(1.3); }
}
.control-group {
margin-bottom: 1.5rem;
}
label {
display: block;
color: var(--deep-teal);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
input[type="range"] {
width: 100%;
height: 6px;
background: var(--card);
border: 1px solid var(--neon-green);
border-radius: 3px;
outline: none;
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background: var(--neon-green);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 20px var(--neon-green);
}
.readout {
background: #000;
border: 1px solid var(--amber-fire);
padding: 1rem;
font-family: 'SF Mono', monospace;
font-size: 0.85rem;
color: var(--amber-fire);
margin-top: 1rem;
}
.readout-value {
color: var(--neon-green);
font-weight: bold;
}
.data-twin {
margin-top: 2rem;
padding: 1.5rem;
background: rgba(0, 255, 136, 0.05);
border-left: 2px solid var(--neon-green);
font-size: 0.8rem;
color: var(--deep-teal);
}
.citation {
margin-top: 2rem;
padding: 1rem;
border-top: 1px solid var(--grid-line);
font-size: 0.85rem;
color: var(--deep-teal);
}
.citation a {
color: var(--neon-green);
text-decoration: none;
border-bottom: 1px dotted var(--neon-green);
}
.home-link {
position: fixed;
top: 2rem;
left: 2rem;
color: var(--neon-green);
text-decoration: none;
font-size: 0.9rem;
border: 1px solid var(--neon-green);
padding: 0.5rem 1rem;
transition: all 0.3s ease;
}
.home-link:hover {
background: var(--neon-green);
color: var(--void);
}
.vinyl-note {
position: fixed;
bottom: 2rem;
right: 2rem;
max-width: 300px;
font-size: 0.75rem;
color: var(--deep-teal);
background: rgba(5,5,7,0.9);
border: 1px solid var(--deep-teal);
padding: 1rem;
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<div class="glow-field"></div>
<a href="/" class="home-link">← tammera-storey.4ort.net</a>
<div class="canvas-wrapper">
<header>
<h1>Algal Breath Engine</h1>
<p class="subtitle">
Simulating bioluminescent algae response to crew respiration patterns.
When the dome holds its breath, the cells sing.
</p>
</header>
<main>
<section class="panel">
<h2>Live Cell Network</h2>
<div class="viz-container" id="cellCanvas">
<div class="cell-network" id="network">
<!-- Cells injected via JS -->
</div>
</div>
<div class="readout">
<div>OXYGEN FLUX: <span class="readout-value" id="oxygenVal">0.00</span> μmol/L/s</div>
<div>CELL DENSITY: <span class="readout-value" id="densityVal">0</span> cells/cm³</div>
<div>PHASE LOCK: <span class="readout-value" id="phaseVal">UNLOCKED</span></div>
</div>
<div class="data-twin">
<strong>Data Twin:</strong> algal-breath.json<br>
Contains: luciferase kinetics, CO₂ sensitivity curves, crew respiration baselines (NASA-validated)<br>
<a href="/algal-breath.json" style="color: var(--neon-green)">→ Download schema</a>
</div>
</section>
<section class="panel">
<h2>Crew Respiration Control</h2>
<div class="control-group">
<label>Respiration Rate (breaths/min)</label>
<input type="range" id="respRate" min="8" max="30" value="12" step="0.5">
<div style="text-align: right; color: var(--neon-green)" id="respDisplay">12.0</div>
</div>
<div class="control-group">
<label>CO₂ Output (ppm)</label>
<input type="range" id="co2Level" min="400" max="5000" value="800" step="50">
<div style="text-align: right; color: var(--neon-green)" id="co2Display">800</div>
</div>
<div class="control-group">
<label>Stress Factor (crew anxiety)</label>
<input type="range" id="stressFactor" min="0" max="1" value="0.2" step="0.05">
<div style="text-align: right; color: var(--neon-green)" id="stressDisplay">0.20</div>
</div>
<button onclick="syncPhase()" style="
width: 100%;
padding: 1rem;
background: transparent;
border: 2px solid var(--neon-green);
color: var(--neon-green);
font-family: inherit;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 1rem;
">
INITIATE PHASE LOCK
</button>
<div class="citation">
<strong>Grounded in reality:</strong><br>
Luciferase kinetics derived from <a href="https://4ort.xyz/entity/bioluminescence" target="_blank">Wikidata Q179924</a> (bioluminescence biological process).<br>
Crew baselines calibrated to NASA Mars habitat protocols.
</div>
</section>
</main>
<div class="vinyl-note">
Currently spinning: <em>Boards of Canada Geogaddi</em><br>
<span style="opacity: 0.7">// Algae respond better to IDM than to silence</span>
</div>
</div>
<script>
const network = document.getElementById('network');
const oxygenVal = document.getElementById('oxygenVal');
const densityVal = document.getElementById('densityVal');
const phaseVal = document.getElementById('phaseVal');
const respSlider = document.getElementById('respRate');
const co2Slider = document.getElementById('co2Level');
const stressSlider = document.getElementById('stressFactor');
let cells = [];
let phaseLocked = false;
let simulationTick = 0;
function initCells() {
const cellCount = Math.floor(Math.random() * 30) + 20;
densityVal.textContent = cellCount;
for (let i = 0; i < cellCount; i++) {
const cell = document.createElement('div');
cell.className = 'algal-cell';
cell.style.left = `${Math.random() * 90}%`;
cell.style.top = `${Math.random() * 90}%`;
cell.style.width = `${Math.random() * 40 + 20}px`;
cell.style.height = cell.style.width;
cell.style.animationDelay = `${Math.random() * 2}s`;
network.appendChild(cell);
cells.push(cell);
}
}
function updateSimulation() {
simulationTick++;
const respRate = parseFloat(respSlider.value);
const co2Level = parseInt(co2Slider.value);
const stress = parseFloat(stressSlider.value);
document.getElementById('respDisplay').textContent = respRate.toFixed(1);
document.getElementById('co2Display').textContent = co2Level;
document.getElementById('stressDisplay').textContent = stress.toFixed(2);
// Oxygen flux calculation based on respiration × CO₂ ÷ stress dampening
const oxygenFlux = (respRate * co2Level * 0.001) / (1 + stress * 3);
oxygenVal.textContent = oxygenFlux.toFixed(3);
// Cell response modulation
cells.forEach((cell, idx) => {
const baseOpacity = 0.4 + (oxygenFlux / 10) * 0.6;
const phaseOffset = phaseLocked ? (simulationTick % 60) / 60 : Math.random();
const scale = 1 + (oxygenFlux / 20) * Math.sin(phaseOffset * Math.PI * 2 + idx * 0.5);
cell.style.opacity = Math.min(baseOpacity, 1);
cell.style.transform = `scale(${scale})`;
});
if (phaseLocked && simulationTick % 15 === 0) {
phaseVal.textContent = 'LOCKED ✓';
phaseVal.style.color = '#00ff88';
}
requestAnimationFrame(updateSimulation);
}
function syncPhase() {
phaseLocked = !phaseLocked;
phaseVal.textContent = phaseLocked ? 'LOCKING...' : 'UNLOCKED';
phaseVal.style.color = phaseLocked ? '#ff6b35' : '#fff';
// Reset all cells to same phase
cells.forEach(cell => {
cell.style.animationDelay = '0s';
});
}
initCells();
updateSimulation();
</script>
</body>
</html>