widgettdc-api / apps /backend /public /omni_link_v4.html
Kraft102's picture
Update backend source
34367da verified
<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WidgeTDC | Omni-Link v4.0 (Haptic)</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
/* Chosen Palette: Kinetic Cyan / Void Black */
:root {
--bg-void: #020203;
--panel-glass: rgba(15, 23, 42, 0.85);
--border-subtle: rgba(56, 189, 248, 0.15);
--neon-cyan: #22d3ee;
--neon-purple: #c084fc;
--neon-amber: #fbbf24; /* For Nudge Energy */
--font-display: 'Orbitron', sans-serif;
--font-code: 'JetBrains Mono', monospace;
}
body {
background-color: var(--bg-void);
color: #e2e8f0;
font-family: var(--font-code);
overflow: hidden;
margin: 0;
}
.font-display { font-family: var(--font-display); }
/* Canvas & Layers */
#neural-canvas { position: absolute; inset: 0; z-index: 0; }
.ui-layer { position: absolute; inset: 0; pointer-events: none; z-index: 10; display: flex; flex-direction: column; }
.pointer-auto { pointer-events: auto; }
/* Glass Panels */
.glass-panel {
background: var(--panel-glass);
border: 1px solid var(--border-subtle);
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* NUDGE Button Animation */
.nudge-btn {
background: linear-gradient(45deg, rgba(251, 191, 36, 0.1), rgba(245, 158, 11, 0.2));
border: 1px solid rgba(251, 191, 36, 0.5);
box-shadow: 0 0 10px rgba(251, 191, 36, 0.1);
transition: all 0.1s;
}
.nudge-btn:active {
transform: scale(0.95);
background: rgba(251, 191, 36, 0.4);
box-shadow: 0 0 25px rgba(251, 191, 36, 0.6);
}
.nudge-btn:hover {
border-color: var(--neon-amber);
box-shadow: 0 0 15px rgba(251, 191, 36, 0.3);
}
/* Inspector Slide */
.inspector-panel {
transform: translateX(110%);
}
.inspector-panel.active { transform: translateX(0); }
/* Chat Stream */
.chat-msg { opacity: 0; animation: slideIn 0.2s forwards; margin-bottom: 4px; padding: 4px 8px; border-left: 2px solid transparent; font-size: 11px; }
.chat-msg.sys { border-color: var(--neon-cyan); background: linear-gradient(90deg, rgba(34,211,238,0.05), transparent); }
.chat-msg.user { border-color: var(--neon-purple); background: linear-gradient(90deg, rgba(192,132,252,0.05), transparent); }
.chat-msg.alert { border-color: var(--neon-amber); color: var(--neon-amber); }
@keyframes slideIn { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: translateX(0); } }
/* Omni Bar */
.omni-input { background: transparent; border: none; outline: none; width: 100%; color: white; font-family: var(--font-code); }
/* CRT Scanline */
.scanlines {
position: fixed; inset: 0; pointer-events: none; z-index: 50; opacity: 0.08;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
background-size: 100% 2px, 3px 100%;
}
/* Syntax Highlighting */
.json-key { color: var(--neon-cyan); }
.json-str { color: #86efac; }
.json-num { color: var(--neon-purple); }
</style>
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
</head>
<body>
<div class="scanlines"></div>
<canvas id="neural-canvas"></canvas>
<!-- UI LAYER -->
<div class="ui-layer p-4 md:p-6">
<!-- HEADER -->
<header class="flex justify-between items-start pointer-auto mb-4">
<div>
<h1 class="font-display text-2xl text-white tracking-widest drop-shadow-[0_0_15px_rgba(34,211,238,0.5)]">
OMNI-LINK <span class="text-amber-400 text-xs align-top font-bold">v4.0 HAPTIC</span>
</h1>
<div class="flex items-center gap-2 mt-1">
<div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
<div class="text-[10px] text-gray-500 font-mono tracking-widest">NEURAL PHYSICS ENGINE: ACTIVE</div>
</div>
</div>
<div class="glass-panel px-3 py-2 rounded text-[10px] font-mono text-gray-400 flex flex-col gap-1 items-end">
<div>NODES: <span id="stat-nodes" class="text-white">0</span></div>
<div>ENERGY: <span id="stat-energy" class="text-amber-400">0%</span></div>
</div>
</header>
<!-- MIDDLE AREA -->
<div class="flex-1 flex gap-4 overflow-hidden relative">
<!-- LEFT: STREAM -->
<div class="w-80 glass-panel rounded-lg flex flex-col pointer-auto z-20 mb-20 transition-opacity hover:opacity-100 opacity-90">
<div class="p-2 border-b border-gray-800 bg-black/40 flex justify-between">
<span class="font-display text-[10px] text-cyan-400 tracking-wider">NEURAL LOG</span>
<span class="text-[10px] text-gray-600">LIVE</span>
</div>
<div id="chat-stream" class="flex-1 overflow-y-auto p-2 scrollbar-hide"></div>
</div>
<!-- RIGHT: INSPECTOR (Slide-in) -->
<div id="inspector" class="absolute right-0 top-0 bottom-20 w-96 glass-panel rounded-lg inspector-panel flex flex-col pointer-auto z-30 border-l-2 border-l-amber-500/50 shadow-[0_0_50px_rgba(0,0,0,0.8)]">
<!-- Inspector Header -->
<div class="p-4 border-b border-gray-800 bg-black/40 flex justify-between items-center">
<div>
<h2 class="font-display text-sm text-amber-400">SYNAPTIC CONTROLLER</h2>
<span id="inspect-id-sm" class="text-[9px] text-gray-500 font-mono block">UUID: --</span>
</div>
<button onclick="closeInspector()" class="text-gray-500 hover:text-white text-xl">×</button>
</div>
<!-- Node Visualizer (Mini) -->
<div class="p-4 bg-gradient-to-b from-gray-900 to-black border-b border-gray-800 text-center relative overflow-hidden group">
<div class="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-10"></div>
<h1 id="inspect-label" class="text-2xl font-bold text-white relative z-10 break-words">Node Name</h1>
<span id="inspect-type" class="text-xs text-cyan-500 font-mono relative z-10 border border-cyan-900/50 px-2 py-0.5 rounded bg-cyan-900/10 mt-2 inline-block">TYPE</span>
<!-- THE BIG NUDGE BUTTON -->
<button id="btn-nudge" class="nudge-btn w-full mt-6 py-3 rounded text-amber-400 font-display font-bold tracking-widest text-sm relative z-10 group-hover:text-white flex justify-center items-center gap-2">
<span class="text-xl"></span> NUDGE NODE
</button>
<div class="text-[9px] text-gray-600 mt-2 font-mono">Applies 50N impulse force to cluster</div>
</div>
<!-- Data View -->
<div class="flex-1 overflow-y-auto p-4 space-y-3 font-mono">
<div class="text-[10px] text-gray-500 uppercase tracking-wider">Deep Storage Memory</div>
<div class="bg-black/60 rounded p-3 border border-gray-800 text-[10px] overflow-x-auto shadow-inner">
<pre id="inspect-json" class="whitespace-pre-wrap break-words"></pre>
</div>
</div>
<!-- Footer Actions -->
<div class="p-3 border-t border-gray-800 grid grid-cols-2 gap-2">
<button onclick="flyToSelection()" class="p-2 bg-gray-800/50 hover:bg-cyan-900/30 text-cyan-400 text-[10px] rounded border border-gray-700">FOCUS CAM</button>
<button class="p-2 bg-gray-800/50 hover:bg-red-900/30 text-red-400 text-[10px] rounded border border-gray-700">PRUNE LINK</button>
</div>
</div>
</div>
<!-- BOTTOM: OMNI BAR -->
<footer class="fixed bottom-6 left-4 right-4 md:left-10 md:right-10 pointer-auto z-40 max-w-5xl mx-auto">
<div class="glass-panel rounded-full px-4 py-3 flex items-center gap-3 border border-gray-700 shadow-[0_10px_40px_rgba(0,0,0,0.5)] focus-within:border-cyan-500 focus-within:shadow-[0_0_20px_rgba(6,182,212,0.3)] transition-all">
<span class="text-cyan-500 font-bold animate-pulse">></span>
<input id="omni-input" type="text" class="omni-input" placeholder="Enter command, query, or /nudge [id]..." autocomplete="off">
<div class="hidden md:flex gap-2 text-[9px] text-gray-500 font-mono">
<span class="px-1.5 py-0.5 bg-gray-800 rounded">ENTER</span> EXECUTE
<span class="px-1.5 py-0.5 bg-gray-800 rounded text-amber-500">/NUDGE</span>
<span class="px-1.5 py-0.5 bg-gray-800 rounded text-cyan-500">/INJECT</span>
</div>
</div>
</footer>
</div>
<script>
// --- 1. CONFIGURATION ---
const config = {
colors: {
bg: '#020203',
nodeBase: '#3b82f6',
nodeAgent: '#c084fc',
nodeInjected: '#22d3ee',
link: '#1e293b',
linkActive: '#38bdf8',
ripple: '#fbbf24'
},
physics: {
friction: 0.94,
spring: 0.03,
repulsion: 200
}
};
// --- 2. STATE ---
const state = {
nodes: [],
links: [],
ripples: [], // Visual shockwaves
camera: { x: 0, y: 0, zoom: 1.2, targetX: 0, targetY: 0 },
selection: null,
drag: { active: false, node: null },
lastTick: Date.now()
};
// --- 3. DATA ENGINE ---
function uuid() { return Math.random().toString(36).substr(2, 6).toUpperCase(); }
function createNode(label, type, x, y) {
return {
id: uuid(),
label: label,
type: type,
x: x || (Math.random()-0.5)*800,
y: y || (Math.random()-0.5)*600,
vx: 0, vy: 0, // Velocity
mass: type === 'Agent' ? 5 : 1,
radius: type === 'Agent' ? 20 : 8,
flash: 0, // Visual flash intensity (0-1)
data: {
created: new Date().toISOString(),
stimulus_count: 0,
status: "DORMANT",
memory_block: "0x" + Math.floor(Math.random()*100000).toString(16)
}
};
}
function initGraph() {
// Core Agents
const c1 = createNode("Gemini (Architect)", "Agent", -100, -50);
const c2 = createNode("Claude (Captain)", "Agent", 100, -50);
const c3 = createNode("WidgeTDC Core", "Agent", 0, 100);
state.nodes.push(c1, c2, c3);
state.links.push({source: c1, target: c2}, {source: c2, target: c3}, {source: c3, target: c1});
// Random Memories/Files
for(let i=0; i<30; i++) {
const n = createNode(`Memory_${i}`, "Memory");
state.nodes.push(n);
// Connect to random agent or node
const target = state.nodes[Math.floor(Math.random() * (state.nodes.length-1))];
if (target !== n) state.links.push({source: n, target: target});
}
log("System", "Neural Graph Loaded. Haptic feedback enabled.");
updateStats();
}
// --- 4. PHYSICS & RENDER LOOP ---
const canvas = document.getElementById('neural-canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
function physics() {
// 1. Repulsion (Nodes push apart)
for(let i=0; i<state.nodes.length; i++) {
const n1 = state.nodes[i];
for(let j=i+1; j<state.nodes.length; j++) {
const n2 = state.nodes[j];
const dx = n1.x - n2.x;
const dy = n1.y - n2.y;
const dist = Math.sqrt(dx*dx + dy*dy) || 1;
if (dist < 200) {
const force = config.physics.repulsion / (dist * dist);
const fx = (dx / dist) * force;
const fy = (dy / dist) * force;
n1.vx += fx; n1.vy += fy;
n2.vx -= fx; n2.vy -= fy;
}
}
}
// 2. Spring (Links pull together)
state.links.forEach(l => {
const dx = l.target.x - l.source.x;
const dy = l.target.y - l.source.y;
const dist = Math.sqrt(dx*dx + dy*dy);
const force = (dist - 100) * config.physics.spring;
const fx = (dx / dist) * force;
const fy = (dy / dist) * force;
l.source.vx += fx; l.source.vy += fy;
l.target.vx -= fx; l.target.vy -= fy;
});
// 3. Update Position & Friction
let totalEnergy = 0;
state.nodes.forEach(n => {
n.vx *= config.physics.friction;
n.vy *= config.physics.friction;
n.x += n.vx;
n.y += n.vy;
n.flash *= 0.9; // Fade flash
totalEnergy += Math.abs(n.vx) + Math.abs(n.vy);
});
// Update Energy Stat
document.getElementById('stat-energy').innerText = Math.min(100, Math.round(totalEnergy * 10)) + "%";
// Camera Smoothing
state.camera.x += (state.camera.targetX - state.camera.x) * 0.1;
state.camera.y += (state.camera.targetY - state.camera.y) * 0.1;
}
function toScreen(x, y) {
return {
x: x * state.camera.zoom + state.camera.x + canvas.width/2,
y: y * state.camera.zoom + state.camera.y + canvas.height/2
};
}
function render() {
physics();
// Clear
ctx.fillStyle = config.colors.bg;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Links
ctx.lineWidth = 1;
state.links.forEach(l => {
const s = toScreen(l.source.x, l.source.y);
const t = toScreen(l.target.x, l.target.y);
// Highlight links connected to selection or during flash
const isActive = state.selection === l.source || state.selection === l.target || l.source.flash > 0.1 || l.target.flash > 0.1;
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(t.x, t.y);
ctx.strokeStyle = isActive ? config.colors.linkActive : config.colors.link;
ctx.globalAlpha = isActive ? 0.8 : 0.3;
ctx.stroke();
ctx.globalAlpha = 1;
});
// Draw Nodes
state.nodes.forEach(n => {
const p = toScreen(n.x, n.y);
const r = n.radius * state.camera.zoom;
// Flash Effect (Nudge)
if (n.flash > 0.01) {
ctx.beginPath();
ctx.arc(p.x, p.y, r + (n.flash * 20), 0, Math.PI*2);
ctx.fillStyle = config.colors.ripple;
ctx.globalAlpha = n.flash * 0.5;
ctx.fill();
ctx.globalAlpha = 1;
}
// Node Body
ctx.beginPath();
ctx.arc(p.x, p.y, r, 0, Math.PI*2);
// Color Logic
if (state.selection === n) ctx.fillStyle = "#fff";
else if (n.type === 'Agent') ctx.fillStyle = config.colors.nodeAgent;
else if (n.type === 'Injected') ctx.fillStyle = config.colors.nodeInjected;
else ctx.fillStyle = config.colors.nodeBase;
ctx.fill();
// Selection Ring
if (state.selection === n) {
ctx.strokeStyle = config.colors.ripple;
ctx.lineWidth = 2;
ctx.stroke();
// Label always visible when selected
ctx.fillStyle = "#fff";
ctx.font = "12px monospace";
ctx.fillText(n.label, p.x + r + 8, p.y + 4);
}
});
// Draw Ripples
for(let i=state.ripples.length-1; i>=0; i--) {
const r = state.ripples[i];
const p = toScreen(r.x, r.y);
ctx.beginPath();
ctx.arc(p.x, p.y, r.radius * state.camera.zoom, 0, Math.PI*2);
ctx.strokeStyle = config.colors.ripple;
ctx.globalAlpha = r.opacity;
ctx.stroke();
r.radius += 5;
r.opacity -= 0.03;
if (r.opacity <= 0) state.ripples.splice(i, 1);
}
ctx.globalAlpha = 1;
requestAnimationFrame(render);
}
// --- 5. INTERACTION LOGIC ---
// HAPTIC ENGINE: The Nudge Logic
function nudgeNode(node, force = 50) {
if (!node) return;
// 1. Physics Impulse
const angle = Math.random() * Math.PI * 2;
node.vx += Math.cos(angle) * force;
node.vy += Math.sin(angle) * force;
// 2. Visual Flash & Ripple
node.flash = 1.0;
state.ripples.push({x: node.x, y: node.y, radius: node.radius + 5, opacity: 1});
// 3. Propagate to neighbors (Chain Reaction)
state.links.forEach(l => {
if (l.source === node) {
l.target.vx += Math.cos(angle) * (force * 0.5);
l.target.vy += Math.sin(angle) * (force * 0.5);
l.target.flash = 0.5;
}
if (l.target === node) {
l.source.vx += Math.cos(angle) * (force * 0.5);
l.source.vy += Math.sin(angle) * (force * 0.5);
l.source.flash = 0.5;
}
});
// 4. Data Update
node.data.stimulus_count++;
node.data.status = "STIMULATED";
node.data.last_nudge = new Date().toISOString();
// 5. Update Inspector if open
if (state.selection === node) {
renderInspector(node);
}
log("Physics", `Impulse applied to ${node.label} (${force}N)`, "alert");
}
// Standard Interaction
canvas.addEventListener('mousedown', e => {
const m = toWorld(e.clientX, e.clientY);
// Hit test
const hit = state.nodes.find(n => {
const dx = n.x - m.x; const dy = n.y - m.y;
return Math.sqrt(dx*dx + dy*dy) < n.radius + 10;
});
if (hit) {
state.selection = hit;
state.drag.active = true;
state.drag.node = hit;
openInspector(hit);
} else {
state.drag.active = false;
// Pan logic handled by generic mouse movement relative to center if needed,
// but keeping it simple: click bg to close inspector
closeInspector();
}
});
canvas.addEventListener('mousemove', e => {
if (state.drag.active) {
const m = toWorld(e.clientX, e.clientY);
state.drag.node.x = m.x;
state.drag.node.y = m.y;
state.drag.node.vx = 0; state.drag.node.vy = 0; // Stop physics while dragging
}
});
canvas.addEventListener('mouseup', () => state.drag.active = false);
canvas.addEventListener('wheel', e => {
state.camera.zoom *= e.deltaY > 0 ? 0.9 : 1.1;
});
// Inspector UI
function openInspector(node) {
const panel = document.getElementById('inspector');
renderInspector(node);
panel.classList.add('active');
// Bind Nudge Button
const btn = document.getElementById('btn-nudge');
// Clone node to remove old listeners
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', () => nudgeNode(node));
}
function renderInspector(node) {
document.getElementById('inspect-label').innerText = node.label;
document.getElementById('inspect-type').innerText = node.type.toUpperCase();
document.getElementById('inspect-id-sm').innerText = "UUID: " + node.id;
const json = JSON.stringify(node.data, null, 2);
document.getElementById('inspect-json').innerHTML = highlightJSON(json);
}
function closeInspector() {
document.getElementById('inspector').classList.remove('active');
state.selection = null;
}
function flyToSelection() {
if (!state.selection) return;
state.camera.targetX = -state.selection.x;
state.camera.targetY = -state.selection.y;
// Immediate snap for this version to ensure it works
state.camera.x = -state.selection.x;
state.camera.y = -state.selection.y;
}
// Omni Bar
const input = document.getElementById('omni-input');
input.addEventListener('keydown', e => {
if (e.key === 'Enter') {
const cmd = input.value.trim();
input.value = "";
log("User", cmd, "user");
processCommand(cmd);
}
});
function processCommand(cmd) {
if (cmd.startsWith('/nudge')) {
const id = cmd.split(' ')[1];
if (!id && state.selection) {
nudgeNode(state.selection);
} else if (id) {
const target = state.nodes.find(n => n.id === id || n.label.includes(id));
if (target) {
nudgeNode(target);
state.camera.x = -target.x; state.camera.y = -target.y; // Fly to it
} else {
log("System", "Target not found", "alert");
}
} else {
log("System", "Select a node or provide ID", "alert");
}
} else if (cmd.startsWith('/inject')) {
// Simulating injection
const newNode = createNode("Injected_Node", "Injected");
newNode.data.source = "Omni-Bar Command";
state.nodes.push(newNode);
log("System", "Node injected successfully.");
nudgeNode(newNode, 20); // Gentle birth nudge
} else {
setTimeout(() => log("Architect", "I hear you. The graph is listening."), 500);
}
}
function log(src, msg, type='sys') {
const div = document.createElement('div');
div.className = `chat-msg ${type}`;
div.innerHTML = `<span class="font-bold opacity-75">${src}:</span> ${msg}`;
document.getElementById('chat-stream').appendChild(div);
// Limit log
const list = document.getElementById('chat-stream');
if (list.children.length > 20) list.removeChild(list.firstChild);
list.scrollTop = list.scrollHeight;
}
function updateStats() {
document.getElementById('stat-nodes').innerText = state.nodes.length;
}
function highlightJSON(json) {
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
let cls = 'json-num';
if (/^"/.test(match)) {
if (/:$/.test(match)) cls = 'json-key';
else cls = 'json-str';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
// --- 6. START ---
// Converts screen coords to world coords helper for mouse events
function toWorld(x, y) {
return {
x: (x - canvas.width/2 - state.camera.x) / state.camera.zoom,
y: (y - canvas.height/2 - state.camera.y) / state.camera.zoom
};
}
initGraph();
render();
</script>
</body>
</html>