Drop this in codepen to mess around with it <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>MC-944H · HAUSDORFF-TUNED SPIN-ICE</title>
<style>
@import url('
fonts.googleapis.com/css2?fa…');
:root {
--bg: #010204;
--panel: rgba(6,10,16,0.92);
--border: rgba(200,50,58,0.35);
--text:
#f8fafc;
--muted:
#64748b;
--crimson:
#c8323a;
--bone:
#e8d8b4;
--gold:
#fdd878;
}
body,html{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);font-family:'Inter',sans-serif;user-select:none;-webkit-user-select:none;touch-action:none;}
#scene-container{position:absolute;top:0;left:0;width:100vw;height:100vh;z-index:0;}
.panel{position:absolute;background:var(--panel);border:1px solid var(--border);padding:14px;border-radius:4px;z-index:10;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 10px 40px rgba(0,0,0,0.9);box-sizing:border-box;}
#inst-panel{top:14px;left:14px;width:340px;border-top:3px solid var(--crimson);}
#diag-panel{top:14px;right:14px;width:320px;}
#ctrl-panel{bottom:28px;left:14px;width:340px;}
@media(max-width:768px){
.panel{width:calc(100vw - 28px)!important;left:14px!important;}
#diag-panel{top:auto;bottom:220px;right:14px;}
#ctrl-panel{bottom:20px;}
.math-block{display:none;}
#inst-panel{max-height:35vh;overflow-y:auto;}
}
.tag{font-family:'Fira Code',monospace;font-size:9px;color:var(--crimson);text-transform:uppercase;margin-bottom:4px;font-weight:600;letter-spacing:1px;}
h1{margin:0 0 2px 0;font-size:15px;font-weight:700;letter-spacing:0.5px;text-transform:uppercase;}
h2{margin:0 0 8px 0;font-size:10px;color:var(--muted);letter-spacing:1px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:5px;text-transform:uppercase;}
.row{display:flex;justify-content:space-between;font-size:10px;margin-bottom:5px;font-family:'Fira Code',monospace;border-bottom:1px dashed rgba(255,255,255,0.05);padding-bottom:3px;}
.lbl{color:var(--muted);}
.val{color:
#fff;font-weight:600;}
.inv-hl{color:var(--crimson);font-weight:700;}
.math-block{font-family:'Fira Code',monospace;font-size:9px;color:
#cbd5e1;line-height:1.5;margin:10px 0;background:rgba(0,0,0,0.6);padding:8px 10px;border:1px solid rgba(255,255,255,0.05);border-radius:2px;border-left:2px solid var(--crimson);}
.math-block .func{color:
#818cf8;}
.math-block .var{color:
#34d399;}
.math-block .comment{color:
#64748b;}
.control-group{margin-top:8px;}
.control-header{display:flex;justify-content:space-between;font-size:9px;color:var(--muted);margin-bottom:4px;font-family:'Fira Code',monospace;}
input[type=range]{width:100%;height:2px;background:rgba(255,255,255,0.1);outline:none;-webkit-appearance:none;border-radius:2px;}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;background:var(--crimson);border-radius:50%;cursor:pointer;box-shadow:0 0 6px rgba(200,50,58,0.4);}
#ctrl-inj::-webkit-slider-thumb{background:var(--bone);box-shadow:0 0 8px rgba(232,216,180,0.4);}
.btn-grid{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-top:8px;}
.btn{background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.1);color:var(--muted);padding:7px;cursor:pointer;text-transform:uppercase;font-size:8px;font-weight:600;font-family:'Fira Code',monospace;transition:0.2s;text-align:center;border-radius:2px;}
.btn.active{background:rgba(200,50,58,0.15);color:
#fff;border-color:var(--crimson);}
.heat-legend{display:flex;align-items:center;margin-top:10px;height:5px;border-radius:3px;background:linear-gradient(90deg,
#1a0a0c,
#c8323a,
#e8d8b4);}
.heat-labels{display:flex;justify-content:space-between;font-size:8px;color:#888;margin-top:3px;font-family:'Fira Code';}
#inv-badge{position:absolute;bottom:80px;right:14px;background:rgba(200,50,58,0.12);border:1px solid rgba(200,50,58,0.3);padding:6px 10px;border-radius:3px;font-family:'Fira Code',monospace;font-size:8px;color:var(--bone);text-align:center;backdrop-filter:blur(6px);z-index:10;}
#inv-badge span{color:var(--crimson);font-size:10px;}
</style>
</head>
<body>
<div id="scene-container"></div>
<div id="inst-panel" class="panel">
<div class="tag">MC-944H · HAUSDORFF TUNED</div>
<h1>PYROCHLORE SPIN-ICE</h1>
<h2>cos θ = 1/3 INVARIANT CONSTRAINT</h2>
<div class="row"><span class="lbl">STOICHIOMETRY</span><span class="val">Dy₂Ti₂O₇</span></div>
<div class="row"><span class="lbl">TUNING</span><span class="val inv-hl">cos θ = 1/3</span></div>
<div class="row"><span class="lbl">DECAY RATE</span><span class="val inv-hl">1/6 (was 1/2)</span></div>
<div class="row"><span class="lbl">B-FIELD CUTOFF</span><span class="val inv-hl">3 T (was 2 T)</span></div>
<div class="row"><span class="lbl">CONFINEMENT</span><span class="val inv-hl">L_c ≈ 20.4 (Hausdorff)</span></div>
<div class="math-block">
<span class="comment">// Hausdorff substitutions:</span><br>
<span class="var">γ_decay</span> = cos θ / 2 = <span class="func">1/6</span><br>
<span class="var">B_sat</span> = 1 / cos θ = <span class="func">3 T</span><br>
<span class="var">L_c</span> = A₀ · π / arccos(1/3) ≈ <span class="func">20.4</span><br>
<span class="comment">// Dirac string persists 3× longer.</span><br>
<span class="comment">// Monopoles confined to Hausdorff shell.</span>
</div>
</div>
<div id="diag-panel" class="panel">
<div class="tag">DIAGNOSTIC LAYER</div>
<h2>HEATMAP OUTPUT</h2>
<div class="btn-grid" id="diag-group">
<button class="btn active" data-diag="0">0: DIRAC STRING</button>
<button class="btn" data-diag="1">1: SPIN STATE</button>
<button class="btn" data-diag="2">2: THERMAL</button>
<button class="btn" data-diag="3">3: Z-DEPTH</button>
<button class="btn" data-diag="4" style="grid-column:span 2;">4: INVARIANT RATIO</button>
</div>
<div class="heat-legend" id="heat-bar"></div>
<div class="heat-labels" id="heat-lbls"><span>COLD</span><span>HOT</span></div>
</div>
<div id="ctrl-panel" class="panel">
<h2>HAUSDORFF TUNED CONTROLS</h2>
<div class="control-group" style="margin-top:0;">
<div class="control-header"><span>Monopole Injection</span><span id="lbl-inj">25%</span></div>
<input type="range" id="ctrl-inj" min="0.0" max="1.0" step="0.05" value="0.25">
</div>
<div class="control-group">
<div class="control-header"><span>B-field (B_z) <span style="color:var(--crimson)">sat @ 3 T</span></span><span id="lbl-mag">0.0 T</span></div>
<input type="range" id="ctrl-mag" min="0.0" max="5.0" step="0.1" value="0.0">
</div>
<div class="control-group">
<div class="control-header"><span>Temperature</span><span id="lbl-temp">4 K</span></div>
<input type="range" id="ctrl-temp" min="1" max="100" step="1" value="4">
</div>
</div>
<div id="inv-badge">cos θ = <span>1/3</span> · HAUSDORFF VETO ACTIVE</div>
<script src="
cdnjs.cloudflare.com/ajax/li…"></script>
<script>
const COS_THETA = 1 / 3;
const SIN_THETA = Math.sqrt(8) / 3;
const THETA_HAUS = Math.acos(COS_THETA);
const STRING_DECAY = COS_THETA / 2;
const B_SAT = 1 / COS_THETA;
const HAUS_LENGTH = 8.0 * (Math.PI / THETA_HAUS);
const INJECTION_SCALE = 5.0 * COS_THETA;
const WANDER_BASE = 200.0 * COS_THETA;
const SPIN_SHIFT = COS_THETA * 1.5;
const ALIGN_THRESH = 1 / COS_THETA;
const LATTICE_SIZE = 12, A_0 = 8.0;
const pState = { injection: 0.25, bField: 0.0, temp: 4.0, diagMode: 0 };
const container = document.getElementById('scene-container');
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5));
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x010204);
scene.fog = new THREE.FogExp2(0x010204, 0.006);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
camera.position.set(0, 30, 110);
function makeGlow() {
const c = document.createElement('canvas'); c.width = 64; c.height = 64;
const ctx = c.getContext('2d');
const g = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
g.addColorStop(0, 'rgba(232,216,180,1)'); g.addColorStop(0.2, 'rgba(232,216,180,0.8)');
g.addColorStop(0.5, 'rgba(232,216,180,0.2)'); g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = g; ctx.fillRect(0, 0, 64, 64);
const t = new THREE.Texture(c); t.needsUpdate = true; return t;
}
const glowTex = makeGlow();
const crystalGroup = new
THREE.Group(); scene.add(crystalGroup);
const fccBasis = [[0,0,0],[0.5,0.5,0],[0.5,0,0.5],[0,0.5,0.5]];
const diaOff = [0.25, 0.25, 0.25];
const basePos = [];
const half = (LATTICE_SIZE * A_0) / 2;
for (let x = 0; x < LATTICE_SIZE; x )
for (let y = 0; y < LATTICE_SIZE; y )
for (let z = 0; z < LATTICE_SIZE; z )
for (let b = 0; b < 4; b ) {
basePos.push({ x: (x fccBasis[b][0]) * A_0 - half, y: (y fccBasis[b][1]) * A_0 - half, z: (z fccBasis[b][2]) * A_0 - half });
basePos.push({ x: (x fccBasis[b][0] diaOff[0]) * A_0 - half, y: (y fccBasis[b][1] diaOff[1]) * A_0 - half, z: (z fccBasis[b][2] diaOff[2]) * A_0 - half });
}
const N = basePos.length;
const geo = new THREE.BufferGeometry();
const pos = new Float32Array(N * 3), col = new Float32Array(N * 3);
const atoms = [];
for (let i = 0; i < N; i ) {
const bp = basePos[i];
atoms[i] = { bx: bp.x, by: bp.y, bz: bp.z, spin: Math.random() > 0.5 ? 1 : -1, lastFlip: 999 };
}
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
geo.setAttribute('color', new THREE.BufferAttribute(col, 3));
const cloud = new THREE.Points(geo, new THREE.PointsMaterial({ size: 3.5, map: glowTex, vertexColors: true, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false }));
crystalGroup.add(cloud);
const shellGeo = new THREE.SphereGeometry(HAUS_LENGTH * 1.5, 24, 16);
const shellEdges = new THREE.EdgesGeometry(shellGeo);
const shellLine = new THREE.LineSegments(shellEdges, new THREE.LineBasicMaterial({ color: 0xc8323a, transparent: true, opacity: 0.08 }));
crystalGroup.add(shellLine);
const NM = 50, monos = [];
for (let i = 0; i < NM; i ) monos.push({ x:0, y:0, z:0, vx:0, vy:0, vz:0, active:false, life:0 });
function heatColor(t, out, off, mode) {
t = Math.max(0, Math.min(1, t)); let r, g, b;
if (mode === 0) { r = 0.1 t * 0.85; g = 0.03 t * 0.82; b = 0.05 t * 0.65; }
else if (mode === 1) { if (t > 0.5) { r = 0.91; g = 0.79; b = 0.55; } else { r = 0.50; g = 0.12; b = 0.14; } }
else if (mode === 2) { r = 0.1 t * 0.7; g = 0.05 t * 0.2; b = 0.05 t * 0.1; }
else if (mode === 3) { r = t * 0.5; g = t * 0.4; b = t * 0.6; }
else { r = t * 0.85 (1 - t) * 0.5; g = t * 0.75 (1 - t) * 0.05; b = t * 0.50 (1 - t) * 0.08; }
out[off] = r; out[off 1] = g; out[off 2] = b;
}
const ui = { inj: document.getElementById('ctrl-inj'), mag: document.getElementById('ctrl-mag'), temp: document.getElementById('ctrl-temp') };
ui.inj.oninput = e => { pState.injection =
e.target.value; document.getElementById('lbl-inj').textContent = Math.round(pState.injection * 100) "%"; };
ui.mag.oninput = e => { pState.bField =
e.target.value; document.getElementById('lbl-mag').textContent = pState.bField.toFixed(1) " T"; };
ui.temp.oninput = e => { pState.temp =
e.target.value; document.getElementById('lbl-temp').textContent = pState.temp.toFixed(0) " K"; };
document.querySelectorAll('
#diag-group .btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('
#diag-group .btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
pState.diagMode = parseInt(btn.dataset.diag);
const bar = document.getElementById('heat-bar'), lbl = document.getElementById('heat-lbls');
const maps = [
[['ground','diric string'], 'linear-gradient(90deg,
#1a0a0c,
#c8323a,
#e8d8b4)'],
[['spin -1','spin 1'], 'linear-gradient(90deg,
#801a1e,
#e8d8b4)'],
[['rigid','hot'], 'linear-gradient(90deg,
#1a0a0c,
#c8323a)'],
[['near','far'], 'linear-gradient(90deg,
#1a0a0c,
#6a5080)'],
[['exact','violated'], 'linear-gradient(90deg,
#801a1e,
#c8323a,
#e8d8b4)']
];
const m = maps[pState.diagMode] || maps[0];
bar.style.background = m[1]; lbl.innerHTML = `<span>${m[0][0]}</span><span>${m[0][1]}</span>`;
});
});
let dragging = false, pt = { x: 0, y: 0 };
let camA = { theta: Math.PI / 4, phi: Math.PI / 3, rad: 90 };
function updCam() {
camera.position.x = camA.rad * Math.sin(camA.phi) * Math.cos(camA.theta);
camera.position.y = camA.rad * Math.cos(camA.phi);
camera.position.z = camA.rad * Math.sin(camA.phi) * Math.sin(camA.theta);
camera.lookAt(0, 0, 0);
}
const ev = (el, evt, fn) => el.addEventListener(evt, fn);
const r3d = renderer.domElement;
ev(r3d, 'touchstart', e => { dragging = true; pt.x = e.touches[0].clientX; pt.y = e.touches[0].clientY; });
ev(r3d, 'touchmove', e => { if(!dragging)return; camA.theta -= (e.touches[0].clientX - pt.x) * 0.01; camA.phi -= (e.touches[0].clientY - pt.y) * 0.01; camA.phi = Math.max(0.1, Math.min(Math.PI - 0.1, camA.phi)); pt.x = e.touches[0].clientX; pt.y = e.touches[0].clientY; });
ev(r3d, 'touchend', () => { dragging = false; });
ev(r3d, 'mousedown', e => { dragging = true; pt.x = e.clientX; pt.y = e.clientY; });
ev(r3d, 'mousemove', e => { if(!dragging)return; camA.theta -= (e.clientX - pt.x) * 0.01; camA.phi -= (e.clientY - pt.y) * 0.01; camA.phi = Math.max(0.1, Math.min(Math.PI - 0.1, camA.phi)); pt.x = e.clientX; pt.y = e.clientY; });
ev(r3d, 'mouseup', () => { dragging = false; });
ev(r3d, 'wheel', e => { camA.rad = e.deltaY * 0.05; camA.rad = Math.max(30, Math.min(200, camA.rad)); });
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const dt = clock.getDelta(), sdt = Math.min(dt, 0.03);
const activeInj = pState.injection * Math.max(0, 1 - pState.bField / B_SAT);
for (let m = 0; m < NM; m ) {
const mono = monos[m];
if (!
mono.active) {
if (Math.random() < activeInj * sdt * INJECTION_SCALE) {
mono.active = true; mono.x = (Math.random() - 0.5) * half * 1.5; mono.y = (Math.random() - 0.5) * half * 1.5; mono.z = (Math.random() - 0.5) * half * 1.5;
mono.vx = (Math.random() - 0.5) * 60; mono.vy = (Math.random() - 0.5) * 60; mono.vz = (Math.random() - 0.5) * 60;
mono.life = 0;
}
} else {
mono.life = sdt; mono.x = mono.vx * sdt; mono.y = mono.vy * sdt; mono.z = mono.vz * sdt;
mono.vx = (Math.random() - 0.5) * WANDER_BASE * sdt; mono.vy = (Math.random() - 0.5) * WANDER_BASE * sdt; mono.vz = (Math.random() - 0.5) * WANDER_BASE * sdt;
const dist = Math.sqrt(mono.x*mono.x mono.y*mono.y mono.z*mono.z);
if (dist > HAUS_LENGTH * 1.5) { const over = (dist - HAUS_LENGTH * 1.5) / (HAUS_LENGTH * 0.3); const f = Math.min(over, 3) * 120 * sdt; mono.vx -= (mono.x / dist) * f; mono.vy -= (mono.y / dist) * f; mono.vz -= (mono.z / dist) * f; }
if (
mono.life > 1.5 || Math.abs(mono.x) > half*1.5 || Math.abs(mono.y) > half*1.5 || Math.abs(mono.z) > half*1.5)
mono.active = false;
}
}
const thermal = Math.max(0, (pState.temp - 4) / 100) * 1.5;
for (let i = 0; i < N; i ) {
const ad = atoms[i]; ad.lastFlip = sdt;
for (let m = 0; m < NM; m ) { if (monos[m].active) { const d2 = (ad.bx - monos[m].x)**2 (
ad.by - monos[m].y)**2 (
ad.bz - monos[m].z)**2; if (d2 < 25) { ad.spin *= -1; ad.lastFlip = 0; } } }
if (pState.bField > 0 && Math.random() > 0.95 && Math.random() * ALIGN_THRESH < pState.bField) ad.spin = 1;
const jx = (Math.random() - 0.5) * thermal, jy = (Math.random() - 0.5) * thermal, jz = (Math.random() - 0.5) * thermal;
const sx = ad.bx jx ad.spin * SPIN_SHIFT, sy =
ad.by jy ad.spin * SPIN_SHIFT, sz =
ad.bz jz ad.spin * SPIN_SHIFT;
pos[i*3] = sx; pos[i*3 1] = sy; pos[i*3 2] = sz;
let d = 0;
if (pState.diagMode === 0) d = Math.max(0, 1 - ad.lastFlip * STRING_DECAY);
else if (pState.diagMode === 1) d = ad.spin > 0 ? 1 : 0;
else if (pState.diagMode === 2) d = Math.min(1, Math.sqrt(jx*jx jy*jy jz*jz) / 1.5);
else if (pState.diagMode === 3) d = (sz half) / (half * 2);
else { let ls = 0, nn = 0; for(let j=0;j<N&&nn<6;j ){const d2=(atoms[j].bx-ad.bx)**2 (atoms[j].by-ad.by)**2 (atoms[j].bz-ad.bz)**2;if(d2<A_0*A_0*0.6&&d2>0){ls =atoms[j].spin;nn ;}} d=Math.min(1,Math.abs(ls)/(nn||1)); }
heatColor(d, col, i*3, pState.diagMode);
}
geo.attributes.position.needsUpdate = true; geo.attributes.color.needsUpdate = true;
if (!dragging) { camA.theta = 0.002; crystalGroup.rotation.x = Math.sin(clock.getElapsedTime() * 0.1) * 0.1; }
shellLine.material.opacity = 0.05 (monos.filter(m =>
m.active).length / NM) * 0.2;
updCam(); renderer.render(scene, camera);
}
window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });
updCam(); animate();
</script>
</body>
</html>