From b3a329e81ae422ef3d10376ef9260a985304374e Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Mon, 1 Jun 2026 23:44:57 +0000 Subject: [PATCH] Add 6 more alive-feeling effects: parallax, sparklines, flash, glitch, alert pulse, hover rise - Mouse parallax: panels tilt in 3D toward cursor + columns shift depth (perspective:1200px on layout) - Sparklines: mini 25-point line charts under CPU/mem/disk bars (cyan/green/orange per metric) - Panel flash: system panel border pulses bright cyan on each data refresh - Glitch: JARVIS title text does chromatic aberration glitch every 35-60s (random interval) - Alert pulse: red radial overlay slowly breathes when there are unresolved alerts - Hover rise: panels lift extra 4px + brighter border on mouse hover - Bonus: metric bar shimmer (animated light sweep through filled bars) --- public_html/index.html | 206 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 4 deletions(-) diff --git a/public_html/index.html b/public_html/index.html index 6fc2a64..0e44042 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -82,9 +82,64 @@ body::after{ /* ── PANEL FLOAT + GLOW ───────────────────────────────────────────── */ @keyframes panelFloat{ - 0%,100%{transform:translateY(0px);box-shadow:0 4px 20px rgba(0,0,0,0.4),0 0 0px rgba(0,212,255,0)} - 50%{transform:translateY(-7px);box-shadow:0 16px 40px rgba(0,0,0,0.5),0 0 30px rgba(0,212,255,0.06),0 0 60px rgba(0,212,255,0.02)} + 0%,100%{transform:translateY(var(--pty,0px)) rotateX(var(--prx,0deg)) rotateY(var(--pry,0deg));box-shadow:0 4px 20px rgba(0,0,0,0.4),0 0 0px rgba(0,212,255,0)} + 50%{transform:translateY(calc(var(--pty,0px) - 7px)) rotateX(var(--prx,0deg)) rotateY(var(--pry,0deg));box-shadow:0 16px 40px rgba(0,0,0,0.5),0 0 30px rgba(0,212,255,0.06),0 0 60px rgba(0,212,255,0.02)} } +/* Panel flash when data updates */ +@keyframes panelFlash{0%{box-shadow:0 0 0 1px rgba(0,212,255,0.8),0 0 20px rgba(0,212,255,0.3)}100%{box-shadow:none}} +.panel.data-flash{animation:panelFlash 0.5s ease-out,panelFloat 7s ease-in-out infinite} + +/* Alert pulse — red ambient glow on body when alerts active */ +@keyframes alertPulse{0%,100%{opacity:0}50%{opacity:1}} +#alertOverlay{position:fixed;inset:0;pointer-events:none;z-index:1;background:radial-gradient(ellipse at 50% 50%,rgba(255,30,60,0.07) 0%,transparent 70%);animation:alertPulse 3s ease-in-out infinite;display:none} + +/* Glitch keyframes */ +@keyframes glitch1{ + 0%,100%{clip-path:inset(0 0 100% 0);transform:translate(0)} + 10%{clip-path:inset(10% 0 60% 0);transform:translate(-3px,1px)} + 20%{clip-path:inset(40% 0 30% 0);transform:translate(3px,-1px)} + 30%{clip-path:inset(70% 0 10% 0);transform:translate(-2px,2px)} + 40%{clip-path:inset(0 0 100% 0);transform:translate(0)} +} +@keyframes glitch2{ + 0%,100%{clip-path:inset(0 0 100% 0);transform:translate(0)} + 10%{clip-path:inset(60% 0 10% 0);transform:translate(3px,-1px)} + 25%{clip-path:inset(20% 0 50% 0);transform:translate(-3px,1px)} + 35%{clip-path:inset(80% 0 5% 0);transform:translate(2px,2px)} + 45%{clip-path:inset(0 0 100% 0);transform:translate(0)} +} +.tb-logo-text{position:relative;display:inline-block} +.tb-logo-text::before,.tb-logo-text::after{ + content:attr(data-text);position:absolute;top:0;left:0; + color:var(--cyan);font-family:var(--font-display);font-size:inherit;font-weight:inherit;letter-spacing:inherit; + pointer-events:none;opacity:0; +} +.tb-logo-text::before{color:rgba(255,0,80,0.8);text-shadow:2px 0 rgba(255,0,80,0.5)} +.tb-logo-text::after{color:rgba(0,255,255,0.8);text-shadow:-2px 0 rgba(0,255,255,0.5)} +.tb-logo-text.glitching::before{animation:glitch1 0.25s steps(1) forwards;opacity:1} +.tb-logo-text.glitching::after{animation:glitch2 0.25s steps(1) forwards;opacity:1} + +/* Metric bar shimmer */ +@keyframes barShimmer{0%{transform:translateX(-100%)}100%{transform:translateX(400%)}} +.metric-bar-fill::after{ + content:'';position:absolute;top:0;left:0;width:30%;height:100%; + background:linear-gradient(90deg,transparent,rgba(255,255,255,0.25),transparent); + animation:barShimmer 2.5s ease-in-out infinite; + border-radius:inherit; +} +.metric-bar-fill{position:relative;overflow:hidden} + +/* Panel hover rise */ +.panel:hover{ + transform:translateY(calc(var(--pty,0px) - 11px)) !important; + border-color:rgba(0,212,255,0.45) !important; + box-shadow:0 20px 50px rgba(0,0,0,0.55),0 0 40px rgba(0,212,255,0.1) !important; + transition:transform 0.3s ease,border-color 0.3s ease,box-shadow 0.3s ease; +} + +/* Sparkline canvas */ +.sparkline-wrap{margin:4px 0 8px;height:32px;position:relative} +.sparkline-wrap canvas{display:block;width:100%;height:32px} /* ── HUD CORNER BRACKETS ──────────────────────────────────────────── */ /* ── MINI ARC REACTOR ─────────────────────────────────────────────── */ @@ -197,6 +252,7 @@ body::after{ grid-template-rows:1fr; gap:10px;padding:10px; overflow:hidden; + perspective:1200px; transition:grid-template-columns 0.45s cubic-bezier(0.4,0,0.2,1); } #mainLayout.focus-mode{grid-template-columns:0px 1fr 0px} @@ -678,6 +734,7 @@ body::after{ +
@@ -705,7 +762,7 @@ body::after{
LOCAL --% CPU
@@ -735,18 +792,21 @@ body::after{
JARVIS SERVER 165.22.1.228
- +
CPU --%
+
MEMORY --%
+
DISK --%
+
UPTIME
--
LOAD
--
@@ -1015,6 +1075,131 @@ document.addEventListener('DOMContentLoaded', () => { }); }); +// ── MOUSE PARALLAX — panels tilt toward cursor ──────────────────────── +(function initParallax() { + const MAX_TILT = 3.5; // degrees + let mouseX = 0, mouseY = 0, raf = null; + + window.addEventListener('mousemove', e => { + mouseX = e.clientX / window.innerWidth - 0.5; // -0.5 to 0.5 + mouseY = e.clientY / window.innerHeight - 0.5; + if (!raf) raf = requestAnimationFrame(applyTilt); + }); + + function applyTilt() { + raf = null; + const rx = mouseY * MAX_TILT; + const ry = -mouseX * MAX_TILT; + document.querySelectorAll('.panel').forEach(p => { + p.style.setProperty('--prx', rx.toFixed(2) + 'deg'); + p.style.setProperty('--pry', ry.toFixed(2) + 'deg'); + }); + // Column-level parallax (skip in focus mode) + if (!document.getElementById('mainLayout')?.classList.contains('focus-mode')) { + const lp = document.getElementById('leftPanel'); + const rp = document.getElementById('rightPanel'); + const cp = document.getElementById('centerPanel'); + if (lp) lp.style.transform = `translateX(${mouseX * 5}px) translateY(${mouseY * 3}px)`; + if (rp) rp.style.transform = `translateX(${-mouseX * 5}px) translateY(${mouseY * 3}px)`; + if (cp) cp.style.transform = `translateX(${mouseX * 2}px)`; + } + } +})(); + +// ── SPARKLINES ──────────────────────────────────────────────────────── +const _sparkData = {cpu: [], mem: [], disk: []}; +const SPARK_MAX = 25; + +function pushSparkData(key, val) { + _sparkData[key].push(val); + if (_sparkData[key].length > SPARK_MAX) _sparkData[key].shift(); +} + +function drawSparkline(canvasId, data, color) { + const canvas = document.getElementById(canvasId); + if (!canvas || !data.length) return; + const wrap = canvas.parentElement; + canvas.width = wrap.clientWidth || 240; + canvas.height = 32; + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const W = canvas.width, H = canvas.height; + const min = 0, max = 100; + const step = W / (SPARK_MAX - 1); + + // Fill area under line + const grad = ctx.createLinearGradient(0, 0, 0, H); + grad.addColorStop(0, color.replace(')', ',0.35)').replace('rgb','rgba')); + grad.addColorStop(1, color.replace(')', ',0)').replace('rgb','rgba')); + ctx.beginPath(); + data.forEach((v, i) => { + const x = i * step; + const y = H - ((v - min) / (max - min)) * H; + i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); + }); + ctx.lineTo((data.length - 1) * step, H); + ctx.lineTo(0, H); + ctx.closePath(); + ctx.fillStyle = grad; + ctx.fill(); + + // Line + ctx.beginPath(); + data.forEach((v, i) => { + const x = i * step; + const y = H - ((v - min) / (max - min)) * H; + i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); + }); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.stroke(); + + // Current value dot + if (data.length > 1) { + const last = data[data.length - 1]; + const x = (data.length - 1) * step; + const y = H - ((last - min) / (max - min)) * H; + ctx.beginPath(); + ctx.arc(x, y, 2.5, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + ctx.strokeStyle = 'rgba(0,0,0,0.5)'; + ctx.lineWidth = 1; + ctx.stroke(); + } +} + +// ── PANEL DATA FLASH ────────────────────────────────────────────────── +function flashPanel(panelEl) { + if (!panelEl) return; + panelEl.classList.remove('data-flash'); + void panelEl.offsetWidth; // reflow to restart animation + panelEl.classList.add('data-flash'); + setTimeout(() => panelEl.classList.remove('data-flash'), 600); +} + +// ── ALERT PULSE ─────────────────────────────────────────────────────── +function setAlertState(hasAlerts) { + const ov = document.getElementById('alertOverlay'); + if (!ov) return; + ov.style.display = hasAlerts ? 'block' : 'none'; +} + +// ── GLITCH EFFECT ───────────────────────────────────────────────────── +(function initGlitch() { + function triggerGlitch() { + const el = document.querySelector('.tb-logo-text'); + 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); +})(); + // ── GLOBALS ────────────────────────────────────────────────────────── let sessionToken = ''; let sessionUser = ''; @@ -1382,6 +1567,17 @@ function renderSystem(s) { tickTo('cpu-val', cpu); tickTo('mem-val', mem); tickTo('disk-val', disk); + + // Sparklines + pushSparkData('cpu', cpu); + pushSparkData('mem', mem); + pushSparkData('disk', disk); + drawSparkline('spark-cpu', _sparkData.cpu, 'rgb(0,212,255)'); + drawSparkline('spark-mem', _sparkData.mem, 'rgb(0,255,136)'); + drawSparkline('spark-disk', _sparkData.disk, 'rgb(255,166,0)'); + + // Flash the system panel on data arrival + flashPanel(document.querySelector('#leftPanel .panel')); document.getElementById('uptime-val').textContent = s.uptime || '--'; document.getElementById('load-val').textContent = s.load?.['1m'] || '--'; document.getElementById('host-val').textContent = s.hostname || 'jarvis'; @@ -1735,11 +1931,13 @@ async function loadAlerts() { if (!alerts.length) { el.innerHTML = '
✓ NO ACTIVE ALERTS
'; tb.textContent='NO ALERTS'; tb.className='text-green'; + setAlertState(false); return; } tb.textContent=alerts.length+' ALERT'+(alerts.length>1?'S':''); tb.className='text-red'; + setAlertState(true); el.innerHTML = alerts.map(a => { const ctxKey = 'alert_' + a.id;