+
@@ -1001,7 +1051,8 @@ body::after{
AGENTS --
-
+
+
JARVIS v2.0 · SECURITY LEVEL ALPHA · UPDATED --:--:--
@@ -1218,8 +1269,9 @@ function flashPanel(panelEl) {
// ── ALERT PULSE ───────────────────────────────────────────────────────
function setAlertState(hasAlerts) {
const ov = document.getElementById('alertOverlay');
- if (!ov) return;
- ov.style.display = hasAlerts ? 'block' : 'none';
+ if (ov) ov.style.display = hasAlerts ? 'block' : 'none';
+ const vg = document.getElementById('vignetteOverlay');
+ if (vg) vg.classList.toggle('alert-vignette', hasAlerts);
}
// ── FACE TRACKING — reactor follows face position ─────────────────────
@@ -1306,13 +1358,299 @@ function stopFaceTracking() {
if (!el) return;
el.classList.add('glitching');
setTimeout(() => el.classList.remove('glitching'), 280);
- // Schedule next glitch: 35-60s random interval
setTimeout(triggerGlitch, 35000 + Math.random() * 25000);
}
- // First glitch after 20s
setTimeout(triggerGlitch, 20000);
})();
+// ① HUD CORNER RINGS ──────────────────────────────────────────────────
+(function initHudCorners() {
+ const canvas = document.getElementById('hudCornersCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ let W, H, t = 0;
+ function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
+
+ function drawCorner(cx, cy, a0, a1) {
+ const R = 62, R2 = R + 16;
+ // Edge lines
+ ctx.strokeStyle = 'rgba(0,212,255,0.35)'; ctx.lineWidth = 1;
+ const edgeLen = 28;
+ // two short lines along the screen edges from the corner point
+ const midA = (a0 + a1) / 2;
+ const ax = Math.cos(a0), ay = Math.sin(a0);
+ const bx = Math.cos(a1), by = Math.sin(a1);
+ ctx.beginPath(); ctx.moveTo(cx + ax*(R+6), cy + ay*(R+6)); ctx.lineTo(cx + ax*(R+6+edgeLen), cy + ay*(R+6+edgeLen)); ctx.stroke();
+ ctx.beginPath(); ctx.moveTo(cx + bx*(R+6), cy + by*(R+6)); ctx.lineTo(cx + bx*(R+6+edgeLen), cy + by*(R+6+edgeLen)); ctx.stroke();
+
+ // Primary arc
+ ctx.beginPath(); ctx.arc(cx, cy, R, a0, a1);
+ ctx.strokeStyle = 'rgba(0,212,255,0.5)'; ctx.lineWidth = 1.2; ctx.stroke();
+ // Outer arc
+ ctx.beginPath(); ctx.arc(cx, cy, R2, a0 + 0.12, a1 - 0.12);
+ ctx.strokeStyle = 'rgba(0,212,255,0.18)'; ctx.lineWidth = 0.6; ctx.stroke();
+
+ // Tick marks
+ const ticks = 14;
+ for (let i = 0; i <= ticks; i++) {
+ const a = a0 + (a1 - a0) * (i / ticks);
+ const big = i % 7 === 0;
+ const len = big ? 9 : (i % 2 === 0 ? 5 : 3);
+ ctx.beginPath();
+ ctx.moveTo(cx + Math.cos(a) * (R - 2), cy + Math.sin(a) * (R - 2));
+ ctx.lineTo(cx + Math.cos(a) * (R - 2 - len), cy + Math.sin(a) * (R - 2 - len));
+ ctx.strokeStyle = big ? 'rgba(0,212,255,0.8)' : 'rgba(0,212,255,0.3)';
+ ctx.lineWidth = big ? 1 : 0.5; ctx.stroke();
+ }
+
+ // Animated scanning dot
+ const dotA = a0 + ((a1 - a0) * ((t * 0.35) % 1));
+ ctx.beginPath(); ctx.arc(cx + Math.cos(dotA) * R, cy + Math.sin(dotA) * R, 2.5, 0, Math.PI*2);
+ ctx.fillStyle = 'rgba(0,212,255,1)';
+ ctx.shadowColor = 'rgba(0,212,255,0.9)'; ctx.shadowBlur = 8; ctx.fill(); ctx.shadowBlur = 0;
+
+ // Small numeric labels
+ ctx.font = '7px Share Tech Mono,monospace'; ctx.fillStyle = 'rgba(0,212,255,0.55)';
+ ctx.fillText(Math.round((Math.abs(a0) / (Math.PI*2)) * 360) + '°',
+ cx + Math.cos(a0) * (R + 20), cy + Math.sin(a0) * (R + 20));
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, W, H); t += 0.01;
+ drawCorner(0, 0, 0, Math.PI*0.5);
+ drawCorner(W, 0, Math.PI*0.5, Math.PI);
+ drawCorner(0, H, Math.PI*1.5, Math.PI*2);
+ drawCorner(W, H, Math.PI, Math.PI*1.5);
+ requestAnimationFrame(draw);
+ }
+ resize(); draw();
+ window.addEventListener('resize', resize);
+})();
+
+// ② DATA STREAM COLUMNS ───────────────────────────────────────────────
+(function initDataStream() {
+ const canvas = document.getElementById('dataStreamCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ let W, H;
+ const CHARS = '0123456789ABCDEF◈◉▸▹⟩⟨⬡░▒';
+ const COL_COUNT = 22;
+ let cols = [];
+
+ function resize() {
+ W = canvas.width = window.innerWidth;
+ H = canvas.height = window.innerHeight;
+ cols = [];
+ for (let i = 0; i < COL_COUNT; i++) {
+ const x = (i / COL_COUNT) * W + Math.random() * (W / COL_COUNT) * 0.6;
+ cols.push({ x, y: Math.random() * H, speed: Math.random() * 0.7 + 0.25,
+ len: Math.floor(Math.random() * 14 + 5), chars: [],
+ alpha: Math.random() * 0.035 + 0.015, tick: 0 });
+ }
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, W, H);
+ ctx.font = '11px Share Tech Mono,monospace';
+ for (const c of cols) {
+ c.y += c.speed;
+ if (c.y - c.len * 14 > H) { c.y = -c.len * 14; c.alpha = Math.random() * 0.035 + 0.015; }
+ if (++c.tick > 7) { c.tick = 0; c.chars[0] = CHARS[Math.floor(Math.random() * CHARS.length)]; }
+ for (let i = 0; i < c.len; i++) {
+ if (!c.chars[i]) c.chars[i] = CHARS[Math.floor(Math.random() * CHARS.length)];
+ const cy = c.y - i * 14;
+ if (cy < -14 || cy > H + 14) continue;
+ const a = c.alpha * (1 - i / c.len);
+ ctx.fillStyle = i === 0 ? `rgba(180,240,255,${Math.min(a*4,0.15)})` : `rgba(0,185,225,${a})`;
+ ctx.fillText(c.chars[i], c.x, cy);
+ }
+ }
+ requestAnimationFrame(draw);
+ }
+ resize(); draw();
+ window.addEventListener('resize', resize);
+})();
+
+// ③ NETWORK TOPOLOGY ──────────────────────────────────────────────────
+let _topoNodes = [], _topoT = 0, _topoRunning = false;
+
+function renderTopology(devices) {
+ const canvas = document.getElementById('topoCanvas');
+ if (!canvas) return;
+ const W = canvas.parentElement?.clientWidth || 260;
+ canvas.width = W; canvas.height = 100;
+
+ _topoNodes = devices.slice(0, 18).map((d, i, arr) => {
+ const angle = (i / arr.length) * Math.PI * 2 - Math.PI / 2;
+ const rx = W * 0.36, ry = 36;
+ return {
+ x: W/2 + Math.cos(angle) * rx * (0.6 + (i%3)*0.18),
+ y: 50 + Math.sin(angle) * ry * (0.7 + (i%2)*0.3),
+ label: (d.name || d.ip || '?').split('.')[0].substring(0, 9),
+ on: !!(d.alive || d.status === 'online'),
+ agent: d.source === 'agent',
+ phase: Math.random() * Math.PI * 2,
+ };
+ });
+
+ if (!_topoRunning) { _topoRunning = true; _drawTopo(); }
+}
+
+function _drawTopo() {
+ requestAnimationFrame(_drawTopo);
+ const canvas = document.getElementById('topoCanvas');
+ if (!canvas || !_topoNodes.length) return;
+ const ctx = canvas.getContext('2d');
+ const W = canvas.width, H = canvas.height;
+ _topoT += 0.018;
+ ctx.clearRect(0, 0, W, H);
+
+ // Connections
+ for (let i = 0; i < _topoNodes.length; i++) {
+ for (let j = i+1; j < _topoNodes.length; j++) {
+ const a = _topoNodes[i], b = _topoNodes[j];
+ if (!a.on || !b.on) continue;
+ const dx = b.x-a.x, dy = b.y-a.y, dist = Math.hypot(dx,dy);
+ if (dist > W * 0.55) continue;
+ ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y);
+ ctx.strokeStyle = 'rgba(0,180,220,0.13)'; ctx.lineWidth = 0.5; ctx.stroke();
+ // travelling pulse
+ const p = (_topoT * 0.4 + a.phase) % 1;
+ ctx.beginPath(); ctx.arc(a.x+dx*p, a.y+dy*p, 1.5, 0, Math.PI*2);
+ ctx.fillStyle = 'rgba(0,212,255,0.7)'; ctx.fill();
+ }
+ }
+
+ // Nodes
+ for (const n of _topoNodes) {
+ const pulse = 0.5 + Math.sin(_topoT*1.8 + n.phase) * 0.3;
+ const col = n.on ? (n.agent ? '0,255,136' : '0,212,255') : '255,50,80';
+ const a = n.on ? pulse * 0.7 : 0.2;
+ if (n.on) {
+ const g = ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,9);
+ g.addColorStop(0,`rgba(${col},${(a*0.5).toFixed(2)})`); g.addColorStop(1,`rgba(${col},0)`);
+ ctx.beginPath(); ctx.arc(n.x,n.y,9,0,Math.PI*2); ctx.fillStyle=g; ctx.fill();
+ }
+ ctx.beginPath(); ctx.arc(n.x,n.y, n.agent ? 4 : 3, 0, Math.PI*2);
+ ctx.fillStyle = `rgba(${col},${a.toFixed(2)})`; ctx.fill();
+ ctx.font = '6.5px Share Tech Mono,monospace';
+ ctx.fillStyle = n.on ? 'rgba(180,230,255,0.65)' : 'rgba(255,100,100,0.45)';
+ ctx.fillText(n.label, n.x+5, n.y+3);
+ }
+}
+
+// ④ EKG HEARTBEAT ────────────────────────────────────────────────────
+(function initEKG() {
+ const canvas = document.getElementById('ekgCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ let W, H, data = [], phase = 0;
+
+ function resize() {
+ const wrap = document.getElementById('ekgWrap');
+ W = canvas.width = wrap ? Math.max(100, wrap.clientWidth) : 180;
+ H = canvas.height = 22;
+ data = new Array(W).fill(0);
+ }
+
+ function ekgVal(p) {
+ const c = p % 1;
+ if (c < 0.28) return 0;
+ if (c < 0.38) return Math.sin((c-0.28)/0.10*Math.PI) * 0.22; // P bump
+ if (c < 0.43) return 0; // PR flat
+ if (c < 0.45) return -(c-0.43)/0.02 * 0.18; // Q dip
+ if (c < 0.465){ const f=(c-0.45)/0.015; return f<0.5?f*2*0.95:(2-f*2)*0.95; } // R spike
+ if (c < 0.49) return -(c-0.465)/0.025 * 0.22; // S dip
+ if (c < 0.52) return -(0.22-(c-0.49)/0.03*0.22); // back to baseline
+ if (c < 0.70) return Math.sin((c-0.52)/0.18*Math.PI) * 0.28; // T wave
+ return 0;
+ }
+
+ function draw() {
+ phase += 0.0038;
+ data.shift(); data.push(ekgVal(phase));
+ ctx.clearRect(0, 0, W, H);
+ // Glow layer
+ ctx.beginPath();
+ data.forEach((v,i) => { const y=H*0.5-v*H*0.44; i===0?ctx.moveTo(i,y):ctx.lineTo(i,y); });
+ ctx.strokeStyle='rgba(0,220,100,0.2)'; ctx.lineWidth=4; ctx.stroke();
+ // Line
+ ctx.beginPath();
+ data.forEach((v,i) => { const y=H*0.5-v*H*0.44; i===0?ctx.moveTo(i,y):ctx.lineTo(i,y); });
+ ctx.strokeStyle='rgba(0,255,120,0.85)'; ctx.lineWidth=1.2; ctx.stroke();
+ requestAnimationFrame(draw);
+ }
+ resize(); draw();
+ window.addEventListener('resize', resize);
+})();
+
+// ⑤ AUDIO WAVEFORM RING ───────────────────────────────────────────────
+(function initAudioRing() {
+ const canvas = document.getElementById('audioRingCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ const CX = 30, CY = 30, BARS = 28, INNER = 20;
+
+ let t = 0;
+ function draw() {
+ t += 0.055;
+ ctx.clearRect(0, 0, 60, 60);
+ const listening = window.isListening;
+ const speaking = window.isSpeaking;
+ if (!listening && !speaking) { requestAnimationFrame(draw); return; }
+
+ for (let i = 0; i < BARS; i++) {
+ const angle = (i / BARS) * Math.PI * 2;
+ let amp;
+ if (speaking) {
+ amp = 0.35 + Math.abs(Math.sin(t*4.1+i*0.9))*0.5 + Math.abs(Math.sin(t*9+i*1.7))*0.15;
+ } else {
+ amp = 0.08 + Math.abs(Math.sin(t*1.1+i*0.6))*0.18;
+ }
+ const barLen = 3 + amp * 11;
+ ctx.beginPath();
+ ctx.moveTo(CX+Math.cos(angle)*INNER, CY+Math.sin(angle)*INNER);
+ ctx.lineTo(CX+Math.cos(angle)*(INNER+barLen), CY+Math.sin(angle)*(INNER+barLen));
+ const alpha = speaking ? 0.55+amp*0.45 : 0.25+amp*0.35;
+ ctx.strokeStyle = speaking ? `rgba(0,255,175,${alpha})` : `rgba(0,212,255,${alpha})`;
+ ctx.lineWidth = 1.8; ctx.stroke();
+ }
+ requestAnimationFrame(draw);
+ }
+ draw();
+})();
+
+// ⑥ STATIC NOISE BURSTS ───────────────────────────────────────────────
+(function initStaticBursts() {
+ function burst() {
+ const panels = document.querySelectorAll('#app .panel');
+ if (panels.length) {
+ const p = panels[Math.floor(Math.random() * panels.length)];
+ const n = document.createElement('div');
+ n.className = 'panel-noise-layer';
+ p.appendChild(n);
+ setTimeout(() => n.remove(), 320);
+ }
+ setTimeout(burst, 75000 + Math.random() * 55000);
+ }
+ setTimeout(burst, 40000 + Math.random() * 30000);
+})();
+
+// ⑦ AMBIENT COLOR CYCLE ───────────────────────────────────────────────
+(function initAmbientColor() {
+ let t = 0;
+ const root = document.documentElement;
+ function tick() {
+ t += 0.00025;
+ const g = Math.round(210 + Math.sin(t) * 22);
+ const b = Math.round(248 + Math.cos(t * 1.15) * 28);
+ root.style.setProperty('--cyan', `rgb(0,${g},${b})`);
+ root.style.setProperty('--cyan2', `rgb(0,${Math.round(g*.82)},${Math.round(b*.87)})`);
+ requestAnimationFrame(tick);
+ }
+ tick();
+})();
+
// ── GLOBALS ──────────────────────────────────────────────────────────
let sessionToken = '';
let sessionUser = '';
@@ -1405,6 +1743,20 @@ function showApp(name, greeting, silent = false) {
const app = document.getElementById('app');
app.style.display = 'flex';
+ // HUD boot sequence — staggered slide-in
+ const topBar = document.getElementById('topBar');
+ const leftPanel = document.getElementById('leftPanel');
+ const rightPanel = document.getElementById('rightPanel');
+ const centerPanel= document.getElementById('centerPanel');
+ [topBar, leftPanel, rightPanel, centerPanel].forEach(el => el && (el.style.opacity = '0'));
+ requestAnimationFrame(() => {
+ if (topBar) { topBar.style.opacity=''; topBar.style.animationDelay='0s'; topBar.classList.add('boot-top'); }
+ setTimeout(()=>{ if(leftPanel) { leftPanel.style.opacity=''; leftPanel.style.animationDelay='0s'; leftPanel.classList.add('boot-left'); }}, 120);
+ setTimeout(()=>{ if(rightPanel) { rightPanel.style.opacity=''; rightPanel.style.animationDelay='0s'; rightPanel.classList.add('boot-right'); }}, 180);
+ setTimeout(()=>{ if(centerPanel){ centerPanel.style.opacity='';centerPanel.style.animationDelay='0s';centerPanel.classList.add('boot-center');}}, 240);
+ setTimeout(()=>{ [topBar,leftPanel,rightPanel,centerPanel].forEach(el=>el?.classList.remove('boot-top','boot-left','boot-right','boot-center')); }, 1200);
+ });
+
if (!silent) {
if (greeting) {
addMessage('jarvis', greeting);
@@ -1779,6 +2131,7 @@ async function loadNetwork() {
// ── RENDER: NETWORK ───────────────────────────────────────────────────
function renderNetworkStatus(n) {
if (!n) return;
+ renderTopology(n.devices || []);
const el = document.getElementById('network-list');
if (!el) return;
const devices = n.devices || [];
@@ -2153,8 +2506,29 @@ function addMessage(role, text) {
const log = document.getElementById('chatLog');
const div = document.createElement('div');
div.className = 'msg ' + role;
- div.textContent = text;
log.appendChild(div);
+
+ if (role === 'jarvis' && text && text.length > 0) {
+ // Adaptive speed: fast for short, slower for long (feels intentional either way)
+ const msPerChar = Math.max(9, Math.min(25, 1600 / text.length));
+ const cursor = document.createElement('span');
+ cursor.className = 'type-cursor';
+ div.appendChild(cursor);
+ let i = 0;
+ const type = () => {
+ if (i < text.length) {
+ cursor.insertAdjacentText('beforebegin', text[i++]);
+ log.scrollTop = log.scrollHeight;
+ setTimeout(type, msPerChar + (text[i-1] === '.' || text[i-1] === ',' ? msPerChar * 4 : 0));
+ } else {
+ cursor.remove();
+ }
+ };
+ setTimeout(type, 0);
+ } else {
+ div.textContent = text;
+ }
+
log.scrollTop = log.scrollHeight;
return div;
}