LPC: Temporal Chain Navigator

@import url(‘https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap’);
body { font-family: ‘Inter’, sans-serif; background-color: #f5f5f4; color: #1c1917; }
.mono-font { font-family: ‘JetBrains+Mono’, monospace; }
.chart-container { position: relative; width: 100%; max-width: 600px; margin: 0 auto; height: 350px; }
#zSpaceChart { width: 100%; height: 100%; }
input[type=range] { -webkit-appearance: none; background: transparent; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; height: 18px; width: 18px; border-radius: 50%; background: #4f46e5; cursor: pointer; margin-top: -7px; border: 2px solid #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 4px; background: #e7e5e4; border-radius: 2px; }

LPC.CHAIN
VECTOR ACTIVE
Loading…

Chain Progression

All
nob_universe
nobDAVE

Select node…

Select node…

Transition Delta

Select a node to begin navigation.

Current State Glyph

Z-Space Vector Mapping

Lines indicate computed chronological transitions

const state = { b: 0.5, e: 0.5, st: 0.5, activeId: null, candidateId: null };
let chainData = [];
let radarChart = null;
let plotInit = false;
const limit = 500;

const els = {
status: document.getElementById(‘status-display’),
chain: document.getElementById(‘chain-steps’),
delta: document.getElementById(‘delta-report’),
glyphTitle: document.getElementById(‘glyph-title’),
domain: document.getElementById(‘domain-select’),
baseSelect: document.getElementById(‘baseline-select’),
candSelect: document.getElementById(‘candidate-select’),
inputs: {
b: document.getElementById(‘input-bottleneck’),
e: document.getElementById(‘input-energy’),
st: document.getElementById(‘input-stability’)
}
};

function normalizeNode(n) {
return {
id: n.node_id,
name: n.label || n.node_id,
b: parseFloat(n.bottleneck_index ?? 0.5) || 0.5,
e: parseFloat(n.energy_index ?? 0.5) || 0.5,
st: parseFloat(n.stability_index ?? 0.5) || 0.5,
desc: n.source_id || n.domain || ”
};
}

async function fetchJson(url) {
const res = await fetch(url, { cache: ‘no-store’ });
if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
return await res.json();
}

async function loadChain(domain = ”) {
try {
setStatus(`Loading nodes${domain ? ‘ (‘ + domain + ‘)’ : ”}…`);
const params = new URLSearchParams({ limit, offset: 0 });
if (domain) params.append(‘domain’, domain);
const data = await fetchJson(`/api/nodes?${params.toString()}`);
chainData = data.map(normalizeNode);
setStatus(`Loaded ${chainData.length} nodes${domain ? ‘ (‘ + domain + ‘)’ : ”}.`);
populateSelectors();
renderChainSteps();
const first = chainData[0];
const second = chainData[1];
if (first) selectBaseline(first.id, false);
if (second) selectCandidate(second.id, false);
updateDelta();
updateZSpace(true);
} catch (err) {
console.error(err);
setStatus(`Error: ${err.message}`);
chainData = [];
els.chain.innerHTML = ‘

Failed to load nodes.

‘;
}
}

function setStatus(text) {
if (els.status) els.status.textContent = text;
}

function populateSelectors() {
const options = chainData.map(n => {
const o = document.createElement(‘option’);
o.value = n.id;
o.textContent = n.name;
return o;
});
[els.baseSelect, els.candSelect].forEach(sel => {
sel.innerHTML = ‘Select node…’;
options.forEach(o => sel.appendChild(o.cloneNode(true)));
});
}

function renderChainSteps() {
els.chain.innerHTML = ”;
const list = chainData.slice(0, 50); // cap visible items
list.forEach((node, index) => {
const btn = document.createElement(‘button’);
btn.className = ‘w-full p-4 text-left rounded-xl border transition-all hover:shadow-md group relative overflow-hidden’;
btn.id = `btn-${node.id}`;
btn.innerHTML = `

${index + 1}
${node.name}
${node.desc}

`;
btn.onclick = () => {
selectBaseline(node.id, false);
window.history.replaceState(null, null, `#${node.id}`);
};
els.chain.appendChild(btn);
});
}

function findNode(id) {
return chainData.find(n => String(n.id) === String(id));
}

function selectBaseline(id, fromSelect) {
const node = findNode(id);
if (!node) return;
state.activeId = node.id;
state.b = node.b;
state.e = node.e;
state.st = node.st;
els.inputs.b.value = node.b;
els.inputs.e.value = node.e;
els.inputs.st.value = node.st;
if (els.baseSelect && !fromSelect) els.baseSelect.value = node.id;
document.querySelectorAll(‘#chain-steps button’).forEach(b => b.classList.remove(‘border-indigo-600’, ‘bg-indigo-50’));
const activeBtn = document.getElementById(`btn-${node.id}`);
if (activeBtn) activeBtn.classList.add(‘border-indigo-600’, ‘bg-indigo-50’);
els.glyphTitle.innerText = `Glyph: ${node.name}`;
updateVisuals();
updateDelta();
}

function selectCandidate(id, fromSelect) {
const node = findNode(id);
state.candidateId = node ? node.id : null;
if (els.candSelect && !fromSelect && node) els.candSelect.value = node.id;
updateDelta();
}

async function updateDelta() {
const report = els.delta;
if (!state.activeId && !state.candidateId) {
report.innerHTML = ‘Select a node to begin navigation.’;
return;
}
const baselineNode = findNode(state.activeId);
const candidateNode = findNode(state.candidateId);
if (baselineNode && candidateNode) {
try {
report.innerHTML = ‘Comparing…’;
const data = await fetchJson(`/api/compare?a=${encodeURIComponent(baselineNode.id)}&b=${encodeURIComponent(candidateNode.id)}`);
const d = data.delta || {};
const format = (v) => (v >= 0 ? ‘+’ : ”) + Number(v).toFixed(3);
report.innerHTML = `
Baseline: ${baselineNode.name}
Candidate: ${candidateNode.name}
ΔB: ${format(d.bottleneck_index || 0)} |
ΔE: ${format(d.energy_index || 0)} |
ΔSt: ${format(d.stability_index || 0)}
`;
} catch (err) {
report.innerHTML = ‘Compare failed; falling back to manual diff.’;
simpleDelta(report, baselineNode);
}
} else if (baselineNode) {
simpleDelta(report, baselineNode);
} else {
report.innerHTML = ‘Select a baseline node.’;
}
}

function simpleDelta(report, baselineNode) {
const idx = chainData.findIndex(n => n.id === baselineNode.id);
const nextNode = chainData[idx + 1];
if (nextNode) {
const db = (nextNode.b – baselineNode.b).toFixed(2);
const de = (nextNode.e – baselineNode.e).toFixed(2);
const dst = (nextNode.st – baselineNode.st).toFixed(2);
report.innerHTML = `
Next: ${nextNode.name}
ΔB: ${db > 0 ? ‘+’ + db : db} | ΔE: ${de > 0 ? ‘+’ + de : de} | ΔSt: ${dst > 0 ? ‘+’ + dst : dst}
`;
} else {
report.innerHTML = ‘Chain Terminus reached. Select another node.’;
}
}

function initRadar() {
const ctx = document.getElementById(‘radarChart’).getContext(‘2d’);
radarChart = new Chart(ctx, {
type: ‘radar’,
data: {
labels: [‘Lambda (B)’, ‘Delta (E)’, ‘Stability (St)’],
datasets: [{
data: [state.b, state.e, state.st],
backgroundColor: ‘rgba(79, 70, 229, 0.2)’,
borderColor: ‘rgb(79, 70, 229)’,
pointRadius: 4
}]
},
options: {
maintainAspectRatio: false,
scales: { r: { min: 0, max: 1, ticks: { display: false } } },
plugins: { legend: { display: false } }
}
});
}

function updateRadar() {
if (!radarChart) return;
radarChart.data.datasets[0].data = [state.b, state.e, state.st];
radarChart.update();
}

function drawGlyph() {
const canvas = document.getElementById(‘glyphCanvas’);
const ctx = canvas.getContext(‘2d’);
const w = canvas.width; const h = canvas.height;
ctx.clearRect(0,0,w,h);

const cx = w/2; const cy = h/2;
const radius = 30 + (state.e * 50);

ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2 * state.st);
ctx.strokeStyle = ‘#4f46e5′;
ctx.lineWidth = 2 + (state.b * 15);
ctx.lineCap = ’round’;
ctx.stroke();

ctx.beginPath();
ctx.arc(cx, cy, 3, 0, Math.PI*2);
ctx.fillStyle = ‘#1c1917’;
ctx.fill();
}

function initZSpace() {
const layout = {
margin: {l: 0, r: 0, b: 0, t: 0},
scene: {
xaxis: {title: ‘Lambda (B)’, range: [0, 1]},
yaxis: {title: ‘Delta (E)’, range: [0, 1]},
zaxis: {title: ‘Stability (St)’, range: [0, 1]}
},
paper_bgcolor: ‘rgba(0,0,0,0)’,
showlegend: false
};
Plotly.newPlot(‘zSpaceChart’, [], layout, {responsive: true, displayModeBar: false});
plotInit = true;
}

function updateZSpace(fullRedraw = false) {
if (!plotInit) return;
const xs = chainData.map(n => n.b);
const ys = chainData.map(n => n.e);
const zs = chainData.map(n => n.st);
const traces = [
{ type: ‘scatter3d’, mode: ‘lines’, x: xs, y: ys, z: zs, line: { width: 4, color: ‘#e2e8f0’ }, name: ‘Chain Vector’ },
{ type: ‘scatter3d’, mode: ‘markers+text’, x: xs, y: ys, z: zs, text: chainData.map(n => n.name), textposition: ‘top center’, marker: { size: 4, color: ‘#94a3b8’ }, name: ‘Nodes’ },
{ type: ‘scatter3d’, mode: ‘markers’, x: [state.b], y: [state.e], z: [state.st], marker: { size: 12, color: ‘#4f46e5’, line: {width: 3, color: ‘#fff’} }, name: ‘Current State’ }
];
if (fullRedraw) {
Plotly.react(‘zSpaceChart’, traces, {
margin: {l: 0, r: 0, b: 0, t: 0},
scene: {
xaxis: {title: ‘Lambda (B)’, range: [0, 1]},
yaxis: {title: ‘Delta (E)’, range: [0, 1]},
zaxis: {title: ‘Stability (St)’, range: [0, 1]}
},
paper_bgcolor: ‘rgba(0,0,0,0)’,
showlegend: false
});
} else {
Plotly.animate(‘zSpaceChart’, { data: traces }, { transition: {duration: 200}, frame: {duration: 200, redraw: false} });
}
}

function updateVisuals() {
updateRadar();
drawGlyph();
updateZSpace();
}

function bindSliders() {
Object.keys(els.inputs).forEach(key => {
els.inputs[key].addEventListener(‘input’, (e) => {
const val = parseFloat(e.target.value) || 0;
if (key === ‘b’) state.b = val;
if (key === ‘e’) state.e = val;
if (key === ‘st’) state.st = val;
state.activeId = null;
updateVisuals();
els.delta.innerHTML = ‘Manual Mode: Vectoring toward undefined target.’;
});
});
}

async function init() {
bindSliders();
initRadar();
initZSpace();
const hash = window.location.hash.replace(‘#’, ”);
await loadChain(”);
if (hash) {
const target = findNode(hash);
if (target) selectBaseline(target.id, false);
}
}

els.domain.addEventListener(‘change’, () => loadChain(els.domain.value));
els.baseSelect.addEventListener(‘change’, () => selectBaseline(els.baseSelect.value, true));
els.candSelect.addEventListener(‘change’, () => selectCandidate(els.candSelect.value, true));

window.onload = init;