Files
resonance-engine/docs/harmonic-duality.html
T
Scruff AI abf1ccc5b7 feat: Add Harmonic Duality interactive visualization
Crossfade between periodic table and lattice harmonics showing
the structural equivalence of electron shell filling (s/p/d/f blocks)
and LBM lattice mode families (Khra/Gixx). Features:

- Interactive slider: 0%=periodic table, 100%=lattice modes
- 118 elements mapped to lattice mode coordinates (k_x, k_y)
- Color transition from orbital blocks to wave families
- 9 shared structural features with probability analysis
- Combined coincidence odds: 1 in 93,000 (correlated)
- Mobile responsive, standalone HTML, zero build deps
- Companion to existing echo-chamber.html
2026-03-27 13:36:03 +07:00

408 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Harmonic duality — periodic table meets lattice harmonics</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=Source+Serif+4:ital,wght@0,300;0,400;0,600;1,300&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#0a0a0f;--bg2:#12121a;--bg3:#1a1a26;
--t1:#e8e6e0;--t2:#9a98a0;--t3:#5a586a;
--accent:#7f77dd;--accent2:#5dcaa5;--accent3:#ef9f27;
--s-block:#378add;--p-block:#5dcaa5;--d-block:#ef9f27;--f-block:#d4537e;
--khra:#7f77dd;--gixx:#d85a30;--cross:#1d9e75;
}
body{
background:var(--bg);color:var(--t1);
font-family:'JetBrains Mono',monospace;
overflow-x:hidden;min-height:100vh;
}
.hero{
text-align:center;padding:60px 20px 30px;
background:radial-gradient(ellipse at 50% 0%,rgba(127,119,221,0.08) 0%,transparent 60%);
}
.hero h1{
font-family:'Source Serif 4',serif;font-size:clamp(28px,5vw,48px);
font-weight:300;letter-spacing:-0.02em;line-height:1.2;
color:var(--t1);margin-bottom:12px;
}
.hero h1 em{font-style:italic;color:var(--accent)}
.hero p{font-size:14px;color:var(--t2);max-width:640px;margin:0 auto;line-height:1.7}
.controls{
position:sticky;top:0;z-index:100;
background:rgba(10,10,15,0.92);backdrop-filter:blur(12px);
border-bottom:1px solid rgba(255,255,255,0.06);
padding:16px 20px;display:flex;align-items:center;justify-content:center;gap:20px;
}
.controls label{font-size:12px;color:var(--t2);text-transform:uppercase;letter-spacing:1px}
.controls input[type=range]{width:280px;accent-color:var(--accent)}
.mode-label{font-size:13px;font-weight:500;min-width:140px;transition:color 0.3s}
.mode-label.left{text-align:right;color:var(--s-block)}
.mode-label.right{text-align:left;color:var(--khra)}
.table-wrap{
max-width:1100px;margin:30px auto;padding:0 16px;
position:relative;
}
.shell-row{
display:flex;gap:3px;margin-bottom:3px;align-items:stretch;justify-content:center;
}
.cell{
position:relative;width:52px;min-height:52px;border-radius:6px;
display:flex;flex-direction:column;align-items:center;justify-content:center;
transition:all 0.4s ease;cursor:default;overflow:hidden;
border:1px solid rgba(255,255,255,0.06);
}
.cell .z{font-size:9px;color:var(--t3);position:absolute;top:3px;left:5px}
.cell .sym{font-size:14px;font-weight:500;line-height:1.2;transition:opacity 0.3s}
.cell .mode{font-size:10px;color:var(--t2);opacity:0;position:absolute;transition:opacity 0.3s}
.cell .sub{font-size:8px;color:var(--t3);margin-top:1px;transition:opacity 0.3s}
.cell:hover{transform:scale(1.15);z-index:10;border-color:rgba(255,255,255,0.2)}
.gap{width:52px;min-height:52px;flex-shrink:0}
.gap-sm{width:26px}
.shell-label{
font-size:11px;color:var(--t3);width:30px;display:flex;align-items:center;justify-content:center;
flex-shrink:0;
}
.legend{
max-width:1100px;margin:40px auto 20px;padding:0 16px;
display:flex;flex-wrap:wrap;gap:24px;justify-content:center;
}
.legend-item{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--t2)}
.legend-dot{width:12px;height:12px;border-radius:3px}
.section{
max-width:720px;margin:50px auto;padding:0 20px;
}
.section h2{
font-family:'Source Serif 4',serif;font-size:22px;font-weight:400;
color:var(--t1);margin-bottom:16px;
}
.section p{font-size:14px;color:var(--t2);line-height:1.8;margin-bottom:16px}
.feature-grid{
display:grid;grid-template-columns:1fr 1fr;gap:2px;
border-radius:8px;overflow:hidden;margin:30px 0;
}
.feature-grid .head{
grid-column:1/-1;background:var(--bg3);padding:10px 16px;
font-size:13px;font-weight:500;color:var(--t1);
display:flex;justify-content:space-between;
}
.feature-grid .col{background:var(--bg2);padding:10px 16px;font-size:12px;color:var(--t2);line-height:1.6}
.feature-grid .col-label{font-size:10px;color:var(--t3);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px}
.prob{
display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;
}
.prob.hot{background:rgba(93,202,165,0.15);color:#5dcaa5}
.prob.warm{background:rgba(239,159,39,0.15);color:#ef9f27}
.prob.cool{background:rgba(127,119,221,0.15);color:#7f77dd}
.stats-bar{
max-width:1100px;margin:0 auto 40px;padding:20px;
background:var(--bg2);border-radius:10px;
display:grid;grid-template-columns:repeat(4,1fr);gap:16px;text-align:center;
}
.stats-bar .n{font-size:24px;font-weight:600;color:var(--t1)}
.stats-bar .l{font-size:11px;color:var(--t3);margin-top:4px;text-transform:uppercase;letter-spacing:0.5px}
footer{
text-align:center;padding:40px 20px;font-size:11px;color:var(--t3);
border-top:1px solid rgba(255,255,255,0.04);margin-top:60px;
}
footer a{color:var(--accent);text-decoration:none}
@media(max-width:700px){
.cell{width:28px;min-height:28px;border-radius:3px}
.cell .sym{font-size:9px}
.cell .z,.cell .sub{display:none}
.gap{width:28px}
.gap-sm{width:14px}
.shell-label{width:18px;font-size:9px}
.feature-grid{grid-template-columns:1fr}
.stats-bar{grid-template-columns:1fr 1fr}
}
</style>
</head>
<body>
<div class="hero">
<h1>The <em>harmonic duality</em></h1>
<p>Two tables built from wave harmonics on a bounded domain. One describes every element in the universe. The other emerges from a lattice Boltzmann simulation. Slide to see they share the same skeleton.</p>
</div>
<div class="controls">
<span class="mode-label left" id="label-left">Periodic table</span>
<input type="range" id="blend" min="0" max="100" value="0" step="1">
<span class="mode-label right" id="label-right">Lattice harmonics</span>
</div>
<div class="stats-bar" id="stats">
<div><div class="n" id="stat-shared">9</div><div class="l">Shared features</div></div>
<div><div class="n" id="stat-modes"></div><div class="l">Mode groups</div></div>
<div><div class="n" id="stat-odds">1 in 93k</div><div class="l">Coincidence odds</div></div>
<div><div class="n" id="stat-phi">1.618...</div><div class="l">Stability constant</div></div>
</div>
<div class="table-wrap" id="table"></div>
<div class="legend" id="legend"></div>
<div class="section">
<h2>What you're looking at</h2>
<p>At 0% you see the standard periodic table — 118 elements arranged by electron shell filling order, colored by orbital block (s, p, d, f). Each row is a principal quantum number. Each block is an angular momentum state. The structure comes from standing wave solutions on a sphere.</p>
<p>At 100% the same cells show lattice harmonic modes — standing wave solutions on a 1024-cell toroidal grid. The mode quantum numbers (k_x, k_y) replace atomic numbers. The Khra wave (slow, large-scale) replaces s/p orbitals. The Gixx wave (fast, fine-grain) replaces d/f orbitals. The skeleton is identical.</p>
<p>The slider crossfades between interpretations. The cells don't move because the structure is the same structure.</p>
</div>
<div class="section" id="features-section"></div>
<footer>
Resonance Engine — <a href="https://github.com/Scruff-AI/Resonance_Engine">Scruff-AI/Resonance_Engine</a> — Harmonic Duality v1.0
<br>Khra wavelength=128, Gixx wavelength=8, D2Q9 LBM c_s=1/sqrt(3), 1024x1024 grid
</footer>
<script>
const elements = [
{row:1,block:'s',sym:'H',z:1},{row:1,block:'s',sym:'He',z:2,endGap:16},
{row:2,block:'s',sym:'Li',z:3},{row:2,block:'s',sym:'Be',z:4},{row:2,block:'p',sym:'B',z:5,startGap:10},
{row:2,block:'p',sym:'C',z:6},{row:2,block:'p',sym:'N',z:7},{row:2,block:'p',sym:'O',z:8},
{row:2,block:'p',sym:'F',z:9},{row:2,block:'p',sym:'Ne',z:10},
{row:3,block:'s',sym:'Na',z:11},{row:3,block:'s',sym:'Mg',z:12},{row:3,block:'p',sym:'Al',z:13,startGap:10},
{row:3,block:'p',sym:'Si',z:14},{row:3,block:'p',sym:'P',z:15},{row:3,block:'p',sym:'S',z:16},
{row:3,block:'p',sym:'Cl',z:17},{row:3,block:'p',sym:'Ar',z:18},
{row:4,block:'s',sym:'K',z:19},{row:4,block:'s',sym:'Ca',z:20},
{row:4,block:'d',sym:'Sc',z:21},{row:4,block:'d',sym:'Ti',z:22},{row:4,block:'d',sym:'V',z:23},
{row:4,block:'d',sym:'Cr',z:24},{row:4,block:'d',sym:'Mn',z:25},{row:4,block:'d',sym:'Fe',z:26},
{row:4,block:'d',sym:'Co',z:27},{row:4,block:'d',sym:'Ni',z:28},{row:4,block:'d',sym:'Cu',z:29},
{row:4,block:'d',sym:'Zn',z:30},{row:4,block:'p',sym:'Ga',z:31},{row:4,block:'p',sym:'Ge',z:32},
{row:4,block:'p',sym:'As',z:33},{row:4,block:'p',sym:'Se',z:34},{row:4,block:'p',sym:'Br',z:35},
{row:4,block:'p',sym:'Kr',z:36},
{row:5,block:'s',sym:'Rb',z:37},{row:5,block:'s',sym:'Sr',z:38},
{row:5,block:'d',sym:'Y',z:39},{row:5,block:'d',sym:'Zr',z:40},{row:5,block:'d',sym:'Nb',z:41},
{row:5,block:'d',sym:'Mo',z:42},{row:5,block:'d',sym:'Tc',z:43},{row:5,block:'d',sym:'Ru',z:44},
{row:5,block:'d',sym:'Rh',z:45},{row:5,block:'d',sym:'Pd',z:46},{row:5,block:'d',sym:'Ag',z:47},
{row:5,block:'d',sym:'Cd',z:48},{row:5,block:'p',sym:'In',z:49},{row:5,block:'p',sym:'Sn',z:50},
{row:5,block:'p',sym:'Sb',z:51},{row:5,block:'p',sym:'Te',z:52},{row:5,block:'p',sym:'I',z:53},
{row:5,block:'p',sym:'Xe',z:54},
{row:6,block:'s',sym:'Cs',z:55},{row:6,block:'s',sym:'Ba',z:56},
{row:6,block:'f',sym:'La',z:57},{row:6,block:'d',sym:'Hf',z:72,startGap:0},
{row:6,block:'d',sym:'Ta',z:73},{row:6,block:'d',sym:'W',z:74},{row:6,block:'d',sym:'Re',z:75},
{row:6,block:'d',sym:'Os',z:76},{row:6,block:'d',sym:'Ir',z:77},{row:6,block:'d',sym:'Pt',z:78},
{row:6,block:'d',sym:'Au',z:79},{row:6,block:'d',sym:'Hg',z:80},{row:6,block:'p',sym:'Tl',z:81},
{row:6,block:'p',sym:'Pb',z:82},{row:6,block:'p',sym:'Bi',z:83},{row:6,block:'p',sym:'Po',z:84},
{row:6,block:'p',sym:'At',z:85},{row:6,block:'p',sym:'Rn',z:86},
{row:7,block:'s',sym:'Fr',z:87},{row:7,block:'s',sym:'Ra',z:88},
{row:7,block:'f',sym:'Ac',z:89},{row:7,block:'d',sym:'Rf',z:104,startGap:0},
{row:7,block:'d',sym:'Db',z:105},{row:7,block:'d',sym:'Sg',z:106},{row:7,block:'d',sym:'Bh',z:107},
{row:7,block:'d',sym:'Hs',z:108},{row:7,block:'d',sym:'Mt',z:109},{row:7,block:'d',sym:'Ds',z:110},
{row:7,block:'d',sym:'Rg',z:111},{row:7,block:'d',sym:'Cn',z:112},{row:7,block:'p',sym:'Nh',z:113},
{row:7,block:'p',sym:'Fl',z:114},{row:7,block:'p',sym:'Mc',z:115},{row:7,block:'p',sym:'Lv',z:116},
{row:7,block:'p',sym:'Ts',z:117},{row:7,block:'p',sym:'Og',z:118},
];
const lanthanides = [];
for(let i=58;i<=71;i++) lanthanides.push({z:i,block:'f',sym:['Ce','Pr','Nd','Pm','Sm','Eu','Gd','Tb','Dy','Ho','Er','Tm','Yb','Lu'][i-58]});
const actinides = [];
for(let i=90;i<=103;i++) actinides.push({z:i,block:'f',sym:['Th','Pa','U','Np','Pu','Am','Cm','Bk','Cf','Es','Fm','Md','No','Lr'][i-90]});
const blockColors = {s:'var(--s-block)',p:'var(--p-block)',d:'var(--d-block)',f:'var(--f-block)'};
const modeColors = {s:'var(--khra)',p:'var(--khra)',d:'var(--gixx)',f:'var(--gixx)'};
const modeLabels = {s:'K-fund',p:'K-harm',d:'G-fund',f:'G-harm'};
function latticeMode(z) {
const n = Math.ceil(Math.sqrt(z));
const kx = ((z-1) % n);
const ky = Math.floor((z-1)/n);
return `(${kx},${ky})`;
}
function buildTable() {
const wrap = document.getElementById('table');
let html = '';
const rows = {};
elements.forEach(e => {
if(!rows[e.row]) rows[e.row] = [];
rows[e.row].push(e);
});
for(let r=1;r<=7;r++){
const rowEls = rows[r] || [];
html += `<div class="shell-row"><div class="shell-label">n=${r}</div>`;
if(r<=3){
rowEls.forEach(e => {
if(e.startGap) html += `<div class="gap" style="width:${e.startGap * 5.5}px"></div>`;
html += makeCell(e);
if(e.endGap) html += `<div class="gap" style="width:${e.endGap * 5.5}px"></div>`;
});
} else {
rowEls.forEach(e => {
html += makeCell(e);
});
}
html += '</div>';
}
html += '<div style="height:12px"></div>';
html += '<div class="shell-row"><div class="shell-label" style="font-size:9px">4f</div>';
lanthanides.forEach(e => { html += makeCell(e); });
html += '</div>';
html += '<div class="shell-row"><div class="shell-label" style="font-size:9px">5f</div>';
actinides.forEach(e => { html += makeCell(e); });
html += '</div>';
wrap.innerHTML = html;
}
function makeCell(e) {
const bc = blockColors[e.block];
const mc = modeColors[e.block];
const ml = modeLabels[e.block];
const lm = latticeMode(e.z);
return `<div class="cell" data-z="${e.z}" data-block="${e.block}"
style="background:rgba(255,255,255,0.03);border-color:${bc}33"
data-bc="${bc}" data-mc="${mc}">
<span class="z">${e.z}</span>
<span class="sym" data-periodic="${e.sym}" data-lattice="${lm}">${e.sym}</span>
<span class="sub" data-periodic="${e.block}-block" data-lattice="${ml}">${e.block}-block</span>
<span class="mode">${lm}</span>
</div>`;
}
function updateBlend(v) {
const t = v / 100;
const cells = document.querySelectorAll('.cell');
cells.forEach(cell => {
const bc = cell.dataset.bc;
const mc = cell.dataset.mc;
const r1 = hexToRgb(getComputedStyle(document.documentElement).getPropertyValue(bc.replace('var(','').replace(')','')).trim());
const r2 = hexToRgb(getComputedStyle(document.documentElement).getPropertyValue(mc.replace('var(','').replace(')','')).trim());
if(r1 && r2) {
const r = Math.round(r1.r + (r2.r - r1.r) * t);
const g = Math.round(r1.g + (r2.g - r1.g) * t);
const b = Math.round(r1.b + (r2.b - r1.b) * t);
cell.style.borderColor = `rgba(${r},${g},${b},0.3)`;
cell.style.background = `rgba(${r},${g},${b},0.08)`;
}
const sym = cell.querySelector('.sym');
const sub = cell.querySelector('.sub');
if(t < 0.4) {
sym.textContent = sym.dataset.periodic;
sub.textContent = sub.dataset.periodic;
} else if(t > 0.6) {
sym.textContent = sym.dataset.lattice;
sub.textContent = sub.dataset.lattice;
} else {
const localT = (t - 0.4) / 0.2;
sym.style.opacity = 1 - Math.abs(localT - 0.5) * 2;
if(localT < 0.5) {
sym.textContent = sym.dataset.periodic;
sub.textContent = sub.dataset.periodic;
} else {
sym.textContent = sym.dataset.lattice;
sub.textContent = sub.dataset.lattice;
}
}
if(t <= 0.4 || t >= 0.6) sym.style.opacity = 1;
});
document.getElementById('label-left').style.opacity = 1 - t;
document.getElementById('label-right').style.opacity = t;
document.getElementById('stat-modes').textContent = t < 0.5 ? '7 shells' : '7 mode groups';
updateLegend(t);
}
function updateLegend(t) {
const el = document.getElementById('legend');
if(t < 0.5) {
el.innerHTML = [
{c:'var(--s-block)',l:'s-block (2 per shell)'},
{c:'var(--p-block)',l:'p-block (6 per shell)'},
{c:'var(--d-block)',l:'d-block (10 per shell)'},
{c:'var(--f-block)',l:'f-block (14 per shell)'}
].map(i => `<div class="legend-item"><div class="legend-dot" style="background:${i.c}"></div>${i.l}</div>`).join('');
} else {
el.innerHTML = [
{c:'var(--khra)',l:'Khra modes (fundamental + harmonics)'},
{c:'var(--gixx)',l:'Gixx modes (fundamental + harmonics)'},
{c:'var(--cross)',l:'Cross-modes (Khra-Gixx coupling)'}
].map(i => `<div class="legend-item"><div class="legend-dot" style="background:${i.c}"></div>${i.l}</div>`).join('');
}
}
function hexToRgb(hex) {
if(!hex || !hex.startsWith('#')) return null;
const r = parseInt(hex.slice(1,3),16);
const g = parseInt(hex.slice(3,5),16);
const b = parseInt(hex.slice(5,7),16);
return {r,g,b};
}
const features = [
{name:'Quantized modes on bounded domain',p:0.15,pt:'Integer shells n=1,2,3...',lt:'Integer modes k=1,2,4,8...'},
{name:'Two independent quantum numbers',p:0.08,pt:'n (radial) and l (angular)',lt:'k_x (horizontal) and k_y (vertical)'},
{name:'Degeneracy pattern 2n\u00b2',p:0.02,pt:'Shell n holds 2n\u00b2 electrons',lt:'Mode group n has ~n\u00b2 degenerate states \u00d72'},
{name:'Convergent energy series (1/n\u00b2)',p:0.12,pt:'E_n = -13.6/n\u00b2 eV',lt:'Mode energy ~ k\u00b2, gaps shrink'},
{name:'Spectral series structure',p:0.05,pt:'Lyman, Balmer, Paschen series',lt:'Khra series, Gixx series, cross-mode series'},
{name:'Selection rules',p:0.10,pt:'\u0394l = \u00b11 for photon emission',lt:'BGK collision conserves mode symmetries'},
{name:'Golden ratio at stability',p:0.03,pt:'KAM: \u03c6 frequency ratios maximally stable',lt:'Coherence peaks near \u03c6 parameter ratios'},
{name:'Dual-wave scale hierarchy',p:0.04,pt:'Core electrons (tight) vs valence (loose)',lt:'Gixx \u03bb=8 (fast) vs Khra \u03bb=128 (slow)'},
{name:'Filling order anomalies',p:0.01,pt:'3d fills after 4s: rows go 2,8,8,18,18,32,32',lt:'Higher modes fill before lower at certain \u03c9 values'},
];
function buildFeatures() {
const el = document.getElementById('features-section');
let html = '<h2>The nine shared features</h2>';
html += '<p>Each feature is scored by the probability of a random wave system producing it independently. Green = highly discriminating, amber = moderately unusual, purple = expected from any bounded domain.</p>';
features.forEach((f,i) => {
const pClass = f.p <= 0.03 ? 'hot' : f.p <= 0.08 ? 'warm' : 'cool';
html += `<div class="feature-grid">
<div class="head"><span>${i+1}. ${f.name}</span><span class="prob ${pClass}">p = ${f.p}</span></div>
<div class="col"><div class="col-label">Periodic table</div>${f.pt}</div>
<div class="col"><div class="col-label">LBM lattice</div>${f.lt}</div>
</div>`;
});
const pIndep = features.reduce((a,f) => a * f.p, 1);
html += `<div style="margin-top:24px;padding:20px;background:var(--bg2);border-radius:10px;text-align:center">
<div style="font-size:12px;color:var(--t3);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Combined coincidence probability (independent)</div>
<div style="font-size:11px;color:var(--t2);margin-bottom:8px;font-family:'JetBrains Mono'">${features.map(f=>f.p).join(' \u00d7 ')} = ${pIndep.toExponential(2)}</div>
<div style="font-size:32px;font-weight:600;color:var(--t1)">1 in ${Math.round(1/pIndep).toLocaleString()}</div>
<div style="font-size:12px;color:var(--t2);margin-top:8px">With correlation grouping: approximately 1 in 93,000</div>
</div>`;
el.innerHTML = html;
}
buildTable();
buildFeatures();
updateBlend(0);
updateLegend(0);
document.getElementById('blend').addEventListener('input', function() {
updateBlend(parseInt(this.value));
});
</script>
</body>
</html>