EARTH — Trefoil Hopfion
by Kikem
HTML
<div id="app">
<header>
<h1>Trefoil Hopfion</h1>
<span>Topological soliton — every field line is linked</span>
</header>
<div id="center">
<canvas id="canvas"></canvas>
<div id="overlay">
<div id="qBadge">Q = 1</div>
<div id="qLabel">HOPF INVARIANT</div>
</div>
</div>
<div id="right">
<div class="st">Display</div>
<label class="toggle"><input type="checkbox" id="chkTrefoil" checked><span>Trefoil knot T(2,3)</span></label>
<label class="toggle"><input type="checkbox" id="chkFibers" checked><span>Hopf fibers</span></label>
<label class="toggle"><input type="checkbox" id="chkTorus"><span>Reference torus</span></label>
<label class="toggle"><input type="checkbox" id="chkAuto" checked><span>Auto-rotate</span></label>
<div class="sg">
<label>Fiber count: <span class="v" id="fiberCountVal">18</span></label>
<input type="range" id="fiberCount" min="3" max="36" step="3" value="18">
</div>
<div class="sg">
<label>Fiber opacity: <span class="v" id="fiberOpVal">0.70</span></label>
<input type="range" id="fiberOp" min="0.1" max="1" step="0.05" value="0.7">
</div>
<div class="st">Topology</div>
<div class="pr"><span class="l">Knot type</span><span class="v">T(2, 3) trefoil</span></div>
<div class="pr"><span class="l">Crossing number</span><span class="v">3</span></div>
<div class="pr"><span class="l">Eisenstein norm</span><span class="v">N(2,3) = 19</span></div>
<div class="pr"><span class="l">Hopf invariant Q</span><span class="v">1</span></div>
<div class="pr"><span class="l">Fiber linking Lk</span><span class="v">1 (pairwise)</span></div>
<div class="pr"><span class="l">Generation</span><span class="v">1st (electron, u, d)</span></div>
<div class="st">What You See</div>
<div class="ib">
<strong>Gold curve</strong> — the trefoil knot. This is the topology that defines first-generation...
CSS
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',system-ui,sans-serif;background:#060810;color:#e0e0e0;overflow:hidden}
#app{display:grid;grid-template-columns:1fr 280px;grid-template-rows:52px 1fr;height:100vh}
header{grid-column:1/-1;background:#0e1320;display:flex;align-items:center;padding:0 24px;border-bottom:1px solid #1a2540}
header h1{font-size:17px;font-weight:600;color:#ffcc44}
header span{margin-left:16px;font-size:12px;color:#6a7a94}
#center{position:relative;overflow:hidden;cursor:grab}
#center:active{cursor:grabbing}
#canvas{width:100%;height:100%;display:block}
#right{background:#0b0f1a;border-left:1px solid #1a2540;overflow-y:auto;padding:16px}
.st{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#4a6a9a;margin:16px 0 8px;font-weight:600}
.st:first-child{margin-top:0}
.toggle{display:flex;align-items:center;gap:8px;font-size:12px;color:#8ab4e0;cursor:pointer;margin-bottom:6px;user-select:none}
.toggle input{accent-color:#4a8aff;width:14px;height:14px}
.sg{margin-bottom:12px}
.sg label{display:block;font-size:11px;color:#5a7a9a;margin-bottom:3px}
.sg input[type=range]{width:100%;accent-color:#4a8aff}
.sg .v{font-size:11px;color:#5a8abf;font-family:Consolas,monospace}
.pr{display:flex;justify-content:space-between;padding:4px 0;font-size:12px;border-bottom:1px solid #111825}
.pr .l{color:#5a7a9a}.pr .v{font-family:Consolas,monospace;color:#a0d0ff;font-size:11px}
.ib{background:#080c16;border:1px solid #162030;border-radius:6px;padding:12px;margin-bottom:14px;font-size:11px;line-height:1.7;color:#6a8aaa}
.ib strong{color:#c0a040}.ib em{color:#88bbff;font-style:normal}
.ib code{color:#a0c0f0;font-family:Consolas,monospace}
#overlay{position:absolute;top:16px;left:20px;pointer-events:none}
#qBadge{font-size:42px;font-weight:800;color:#ffcc44;text-shadow:0 0 30px #ffaa0060,0 0 60px #ff880030;opacity:0.7}
#qLabel{font-size:11px;color:#8a7a50;letter-spacing:1px;margin-top:-4px}
JavaScript
// ═══════════════════════════════════════════════════════════
// 3D Rotation
// ═══════════════════════════════════════════════════════════
function rX(p,a){const c=Math.cos(a),s=Math.sin(a);return{x:p.x,y:p.y*c-p.z*s,z:p.y*s+p.z*c}}
function rY(p,a){const c=Math.cos(a),s=Math.sin(a);return{x:p.x*c+p.z*s,y:p.y,z:-p.x*s+p.z*c}}
// ═══════════════════════════════════════════════════════════
// Geometry generators
// ═══════════════════════════════════════════════════════════
const TORUS_R = 1.1, TORUS_r = 0.45;
function genTrefoil(n) {
const pts = [];
for (let i = 0; i <= n; i++) {
const t = (i / n) * Math.PI * 2;
pts.push({
x: (TORUS_R + TORUS_r * Math.cos(3 * t)) * Math.cos(2 * t),
y: (TORUS_R + TORUS_r * Math.cos(3 * t)) * Math.sin(2 * t),
z: TORUS_r * Math.sin(3 * t)
});
}
return pts;
}
function genHopfFiber(theta, phi, n) {
const pts = [];
const ct = Math.cos(theta / 2), st = Math.sin(theta / 2);
const SCALE = 2.2;
for (let i = 0; i <= n; i++) {
const a = (i / n) * Math.PI * 2;
const x1 = ct * Math.cos(a);
const x2 = ct * Math.sin(a);
const x3 = st * Math.cos(a - phi);
const x4 = st * Math.sin(a - phi);
const denom = 1 + x4;
if (denom < 0.12) { pts.push(null); continue; }
const s = SCALE / denom;
pts.push({ x: x1 * s, y: x2 * s, z: x3 * s });
}
return pts;
}
function genTorusWire(nM, nL, ppc) {
const curves = [];
// Meridians
for (let i = 0; i < nM; i++) {
const theta = (i / nM) * Math.PI * 2;
const pts = [];
for (let j = 0; j <= ppc; j++) {
const phi = (j / ppc) * Math.PI * 2;
pts.push({
x: (TORUS_R + TORUS_r * Math.cos(phi)) * Math.cos(theta),
y: (TORUS_R + TORUS_r * Math.cos(phi)) * Math.sin(theta),
z: TORUS_r * Math.sin(phi)
});
}
curves.push(pts);
}
// Longitudes
for (let i = 0; i < nL; i++) {
const phi = (i / nL) * Math.PI * 2;
...