From c8e002091e8f82d0e2c4ec85389eca170c703d2a Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Tue, 2 Jun 2026 00:27:27 +0000 Subject: [PATCH] 10 more alive-feeling effects: HUD corners, data stream, topology, boot seq, vignette, EKG, audio ring, typewriter, static burst, color cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ① HUD corner rings: animated scanning arcs in all 4 screen corners with tick marks, edge lines, moving dot ② Data stream columns: 22 subtle falling hex/glyph columns behind everything (Matrix-adjacent, very low opacity) ③ Network topology canvas: live node constellation above the network list with pulsing travel dots on connections ④ HUD boot sequence: topbar/panels slide in from edges with staggered timing on every login ⑤ Breathing vignette: screen edges slowly pulse + shifts to red when alerts are active ⑥ EKG heartbeat: scrolling ECG waveform in bottom bar (P-QRS-T complex, green glow) ⑦ Audio waveform ring: animated bar ring around the arc reactor during mic/TTS activity ⑧ Typewriter: JARVIS chat responses type out character by character with adaptive speed + cursor blink ⑨ Static noise burst: random panel gets 280ms noise overlay every 75-130 seconds ⑩ Ambient color cycle: --cyan slowly drifts between blue-cyan and green-cyan over 70s loop --- public_html/index.html | 388 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 381 insertions(+), 7 deletions(-) diff --git a/public_html/index.html b/public_html/index.html index 06e4a35..8f3dd39 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -759,11 +759,60 @@ body::after{ .text-orange{color:var(--orange)} .text-red{color:var(--red)} .text-dim{color:var(--text-dim)} + +/* ① HUD CORNER RINGS */ +#hudCornersCanvas{position:fixed;inset:0;z-index:3;pointer-events:none} + +/* ② DATA STREAM COLUMNS */ +#dataStreamCanvas{position:fixed;inset:0;z-index:0;pointer-events:none} + +/* ③ NETWORK TOPOLOGY */ +#topoCanvas{display:block;width:100%;flex-shrink:0;cursor:default;border-bottom:1px solid var(--panel-border);margin-bottom:6px} + +/* ④ BOOT SEQUENCE */ +@keyframes bootLeft{0%{opacity:0;transform:translateX(-70px)}100%{opacity:1;transform:none}} +@keyframes bootRight{0%{opacity:0;transform:translateX(70px)}100%{opacity:1;transform:none}} +@keyframes bootDown{0%{opacity:0;transform:translateY(-18px)}100%{opacity:1;transform:none}} +@keyframes bootCenter{0%{opacity:0;transform:scale(0.94) translateY(14px)}100%{opacity:1;transform:none}} +.boot-left{animation:bootLeft 0.55s cubic-bezier(0.4,0,0.2,1) both} +.boot-right{animation:bootRight 0.55s cubic-bezier(0.4,0,0.2,1) both} +.boot-top{animation:bootDown 0.4s ease both} +.boot-center{animation:bootCenter 0.65s cubic-bezier(0.4,0,0.2,1) both} + +/* ⑤ BREATHING EDGE VIGNETTE */ +#vignetteOverlay{position:fixed;inset:0;pointer-events:none;z-index:1; + background:radial-gradient(ellipse at 50% 50%,transparent 32%,rgba(0,2,18,0.6) 100%); + animation:vignettePulse 5s ease-in-out infinite} +#vignetteOverlay.alert-vignette{background:radial-gradient(ellipse at 50% 50%,transparent 32%,rgba(20,0,8,0.65) 100%)} +@keyframes vignettePulse{0%,100%{opacity:0.75}50%{opacity:1}} + +/* ⑥ EKG HEARTBEAT */ +#ekgWrap{flex:1;max-width:180px;display:flex;align-items:center;overflow:hidden} +#ekgCanvas{display:block;width:100%;height:22px;opacity:0.8} + +/* ⑦ AUDIO RING */ +.tb-reactor{position:relative} +#audioRingCanvas{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%); + width:60px;height:60px;pointer-events:none;z-index:4} + +/* ⑧ TYPEWRITER CURSOR */ +@keyframes cursorBlink{0%,100%{opacity:1}49%{opacity:1}50%,99%{opacity:0}} +.type-cursor{display:inline-block;width:6px;height:0.82em;background:var(--cyan);margin-left:1px; + vertical-align:text-bottom;animation:cursorBlink 0.7s step-end infinite} + +/* ⑨ STATIC NOISE BURST */ +@keyframes staticBurst{0%{opacity:0}10%{opacity:1}90%{opacity:1}100%{opacity:0}} +.panel-noise-layer{position:absolute;inset:0;pointer-events:none;z-index:20;border-radius:var(--r); + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.1'/%3E%3C/svg%3E"); + background-size:100% 100%;mix-blend-mode:screen;animation:staticBurst 0.28s ease forwards} + +
+
@@ -797,7 +846,7 @@ body::after{
@@ -940,6 +989,7 @@ body::after{
NETWORK STATUS
+
@@ -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; }