diff --git a/index.html b/index.html
index 437e34a..4c13ca9 100644
--- a/index.html
+++ b/index.html
@@ -1257,7 +1257,7 @@
projects - list active web projects & links
skills - show competencies & skills
music - music play | pause | vol <0-100>
- bg <mode> - change background (horizon, neural, matrix, video)
+ bg <mode> - change background (liquid, horizon, matrix, video)
catpic - fetch a fresh cat image
whoami - print current session bio
clear - clear terminal screen
@@ -1341,12 +1341,12 @@
case 'bg':
case 'background':
- if (arg === 'cyber' || arg === 'horizon') {
+ if (arg === 'liquid' || arg === 'void' || arg === 'fluid') {
+ setBgMode('liquid-void');
+ out.textContent = 'background set to: Liquid Void Fluid';
+ } else if (arg === 'cyber' || arg === 'horizon') {
setBgMode('cyber-horizon');
out.textContent = 'background set to: Cyber 3D Horizon';
- } else if (arg === 'neural' || arg === 'stars' || arg === 'mesh') {
- setBgMode('neural-mesh');
- out.textContent = 'background set to: Neural Star-Mesh';
} else if (arg === 'matrix' || arg === 'rain') {
setBgMode('matrix-rain');
out.textContent = 'background set to: KatOS Digital Rain';
@@ -1355,7 +1355,7 @@
out.textContent = 'background set to: Ambient Video';
} else {
cycleBgMode();
- out.textContent = `background switched to: ${bgModes[currentBgModeIdx].name}\nusage: bg [horizon | neural | matrix | video]`;
+ out.textContent = `background switched to: ${bgModes[currentBgModeIdx].name}\nusage: bg [liquid | horizon | matrix | video]`;
}
break;
@@ -1629,8 +1629,8 @@
}
const bgModes = [
+ { id: 'liquid-void', name: 'Liquid Void Fluid' },
{ id: 'cyber-horizon', name: 'Cyber 3D Horizon' },
- { id: 'neural-mesh', name: 'Neural Star-Mesh' },
{ id: 'matrix-rain', name: 'KatOS Digital Rain' },
{ id: 'video-ambient', name: 'Ambient Video' }
];
@@ -1669,12 +1669,14 @@
if (!silent) showToast(`[ BG: ${mode.name} ]`);
}
- // Mouse Tracking with Smooth Lerp
+ // Interactive Mouse Tracking (Position & Kinetic Velocity)
const bgMouse = {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
targetX: window.innerWidth / 2,
targetY: window.innerHeight / 2,
+ vx: 0,
+ vy: 0,
active: false
};
@@ -1688,28 +1690,58 @@
bgMouse.active = false;
});
- // Star / Constellation Node Pool
- const STAR_COUNT = 85;
- const stars = [];
- function initStars() {
- stars.length = 0;
+ // Gravitational Shockwaves on Click
+ const shockwaves = [];
+ window.addEventListener('mousedown', e => {
+ if (e.target.closest('.window-body') || e.target.closest('input') || e.target.closest('.player-btn')) return;
+ shockwaves.push({
+ x: e.clientX,
+ y: e.clientY,
+ radius: 10,
+ maxRadius: Math.max(window.innerWidth, window.innerHeight) * 0.45,
+ alpha: 0.65,
+ power: 1.2
+ });
+ });
+
+ // ── Liquid Void Kinetic Particle Fluid ──────────────────────────────────
+ const FLUID_PARTICLE_COUNT = 380;
+ const fluidParticles = [];
+
+ function initFluidParticles() {
+ fluidParticles.length = 0;
const W = bgCanvas.width;
const H = bgCanvas.height;
- for (let i = 0; i < STAR_COUNT; i++) {
- stars.push({
- x: Math.random() * W,
- y: Math.random() * H,
- vx: (Math.random() - 0.5) * 0.35,
- vy: (Math.random() - 0.5) * 0.3,
- size: 0.8 + Math.random() * 1.8,
- alpha: 0.2 + Math.random() * 0.65,
- pulse: Math.random() * Math.PI * 2,
- pulseSpeed: 0.02 + Math.random() * 0.03
+ for (let i = 0; i < FLUID_PARTICLE_COUNT; i++) {
+ const x = Math.random() * W;
+ const y = Math.random() * H;
+ fluidParticles.push({
+ x: x,
+ y: y,
+ oldX: x,
+ oldY: y,
+ vx: (Math.random() - 0.5) * 0.5,
+ vy: (Math.random() - 0.5) * 0.5,
+ speed: 0.85 + Math.random() * 1.3,
+ size: 0.7 + Math.random() * 1.7,
+ alpha: 0.22 + Math.random() * 0.65,
+ life: Math.random() * 500,
+ maxLife: 300 + Math.random() * 600
});
}
}
- // Matrix Rain Columns
+ // Incompressible Curl-Noise Flow Field Generator (Zero Divergence)
+ function getCurlVector(x, y, t) {
+ const s1 = 0.0016;
+ const s2 = 0.0034;
+ const vx = -s1 * Math.sin(x * s1 + t * 0.00035) * Math.sin(y * s1 + t * 0.0003) + 0.45 * s2 * Math.cos((x + y) * s2 - t * 0.00045);
+ const vy = -(s1 * Math.cos(x * s1 + t * 0.00035) * Math.cos(y * s1 + t * 0.0003) + 0.45 * s2 * Math.cos((x + y) * s2 - t * 0.00045));
+ const len = Math.hypot(vx, vy) || 1;
+ return { vx: vx / len, vy: vy / len };
+ }
+
+ // Matrix Rain Setup
let mCols = 0;
let mDrops = [];
const mChars = '01KATOSOIUPOYTARCHLINUX7942#$><;:~^._.^';
@@ -1721,13 +1753,13 @@
function resizeBgCanvas() {
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight;
- initStars();
+ initFluidParticles();
initMatrixDrops();
}
window.addEventListener('resize', resizeBgCanvas);
resizeBgCanvas();
- // Audio frequency extractor helper
+ // Audio frequency extractor
function getAudioBeat() {
if (!isPlaying || !analyser) return { bass: 0, mid: 0, energy: 0 };
const freqData = new Uint8Array(analyser.frequencyBinCount);
@@ -1739,11 +1771,8 @@
return { bass, mid, energy: (bass * 0.65 + mid * 0.35) };
}
- // Expanding shockwave pulse on bass hits
- const shockwaves = [];
- let lastBeatTime = 0;
+ let lastAudioBeat = 0;
let gridOffset = 0;
- let radarAngle = 0;
function renderBgFrame(timestamp) {
requestAnimationFrame(renderBgFrame);
@@ -1751,24 +1780,37 @@
const H = bgCanvas.height;
if (W === 0 || H === 0) return;
- // Smooth mouse lerp
- bgMouse.x += (bgMouse.targetX - bgMouse.x) * 0.08;
- bgMouse.y += (bgMouse.targetY - bgMouse.y) * 0.08;
+ // Mouse velocity & position tracking
+ bgMouse.vx = (bgMouse.targetX - bgMouse.x);
+ bgMouse.vy = (bgMouse.targetY - bgMouse.y);
+ bgMouse.x += bgMouse.vx * 0.1;
+ bgMouse.y += bgMouse.vy * 0.1;
const audio = getAudioBeat();
- // Trigger beat shockwave on intense bass
- if (audio.bass > 0.65 && timestamp - lastBeatTime > 340) {
- lastBeatTime = timestamp;
+ // Audio beat drop shockwave
+ if (audio.bass > 0.68 && timestamp - lastAudioBeat > 320) {
+ lastAudioBeat = timestamp;
shockwaves.push({
x: W / 2,
- y: H * 0.65,
- radius: 8,
- maxRadius: Math.max(W, H) * 0.8,
- alpha: 0.55
+ y: H / 2,
+ radius: 12,
+ maxRadius: Math.max(W, H) * 0.75,
+ alpha: 0.5,
+ power: 1.5
});
}
+ // Update shockwaves
+ for (let i = shockwaves.length - 1; i >= 0; i--) {
+ const sw = shockwaves[i];
+ sw.radius += 6.5;
+ sw.alpha *= 0.955;
+ if (sw.radius > sw.maxRadius || sw.alpha < 0.01) {
+ shockwaves.splice(i, 1);
+ }
+ }
+
const currentMode = bgModes[currentBgModeIdx].id;
if (currentMode === 'video-ambient') {
@@ -1777,120 +1819,139 @@
}
if (currentMode === 'matrix-rain') {
- // High-definition monochrome KatOS matrix rain
+ // High-definition monochrome matrix rain
bgCtx.fillStyle = 'rgba(0, 0, 0, 0.09)';
bgCtx.fillRect(0, 0, W, H);
bgCtx.font = '12px "JetBrains Mono", monospace';
-
for (let i = 0; i < mDrops.length; i++) {
const char = mChars.charAt(Math.floor(Math.random() * mChars.length));
const x = i * 18;
const y = mDrops[i] * 18;
-
if (y >= 0) {
bgCtx.fillStyle = '#ffffff';
bgCtx.shadowColor = '#ffffff';
bgCtx.shadowBlur = 8;
bgCtx.fillText(char, x, y);
bgCtx.shadowBlur = 0;
-
bgCtx.fillStyle = 'rgba(210, 210, 210, 0.45)';
const prevChar = mChars.charAt(Math.floor(Math.random() * mChars.length));
bgCtx.fillText(prevChar, x, y - 18);
}
-
- if (y > H && Math.random() > 0.978) {
- mDrops[i] = 0;
- }
+ if (y > H && Math.random() > 0.978) mDrops[i] = 0;
mDrops[i]++;
}
return;
}
- // Default modes: 'cyber-horizon' or 'neural-mesh'
- bgCtx.fillStyle = 'rgba(0, 0, 0, 0.22)';
- bgCtx.fillRect(0, 0, W, H);
+ if (currentMode === 'liquid-void') {
+ // ── "Liquid Void" Incompressible Kinetic Particle Fluid ─────────────
+ bgCtx.fillStyle = 'rgba(0, 0, 0, 0.14)';
+ bgCtx.fillRect(0, 0, W, H);
- const isHorizon = (currentMode === 'cyber-horizon');
- const horizonY = isHorizon ? H * 0.65 : H + 200;
-
- // 1. Draw Starfield & Constellation Network
- const activeStars = isHorizon ? stars.filter(s => s.y < horizonY + 25) : stars;
-
- for (let i = 0; i < activeStars.length; i++) {
- const s = activeStars[i];
- s.x += s.vx * (1 + audio.bass * 0.5);
- s.y += s.vy * (1 + audio.bass * 0.5);
- s.pulse += s.pulseSpeed;
-
- if (s.x < 0) s.x = W;
- if (s.x > W) s.x = 0;
- if (s.y < 0) s.y = isHorizon ? horizonY : H;
- if (s.y > (isHorizon ? horizonY + 15 : H)) s.y = 0;
-
- // Mouse gravity pull (subtle warp)
- const dx = bgMouse.x - s.x;
- const dy = bgMouse.y - s.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (bgMouse.active && dist < 140) {
- const force = (1 - dist / 140) * 0.015;
- s.x += dx * force;
- s.y += dy * force;
-
- // Glowing laser thread connecting cursor to node
- bgCtx.strokeStyle = `rgba(255, 255, 255, ${(1 - dist / 140) * 0.32})`;
- bgCtx.lineWidth = 1;
+ // Render expanding shockwaves
+ for (let i = 0; i < shockwaves.length; i++) {
+ const sw = shockwaves[i];
+ bgCtx.strokeStyle = `rgba(255, 255, 255, ${sw.alpha * 0.35})`;
+ bgCtx.lineWidth = 1.2;
bgCtx.beginPath();
- bgCtx.moveTo(bgMouse.x, bgMouse.y);
- bgCtx.lineTo(s.x, s.y);
+ bgCtx.arc(sw.x, sw.y, sw.radius, 0, Math.PI * 2);
bgCtx.stroke();
}
- // Connections between neighboring stars
- for (let j = i + 1; j < activeStars.length; j++) {
- const s2 = activeStars[j];
- const cdx = s.x - s2.x;
- const cdy = s.y - s2.y;
- const cDist = Math.sqrt(cdx * cdx + cdy * cdy);
- if (cDist < 85) {
- const lineAlpha = (1 - cDist / 85) * 0.16 * (1 + audio.mid * 0.8);
- bgCtx.strokeStyle = `rgba(255, 255, 255, ${lineAlpha})`;
- bgCtx.lineWidth = 0.8;
- bgCtx.beginPath();
- bgCtx.moveTo(s.x, s.y);
- bgCtx.lineTo(s2.x, s2.y);
- bgCtx.stroke();
+ const flowSpeed = (1.4 + audio.bass * 2.2);
+
+ for (let i = 0; i < fluidParticles.length; i++) {
+ const p = fluidParticles[i];
+ p.oldX = p.x;
+ p.oldY = p.y;
+
+ // 1. Natural Incompressible Curl Flow Field
+ const curl = getCurlVector(p.x, p.y, timestamp);
+ p.vx += curl.vx * 0.22 * p.speed;
+ p.vy += curl.vy * 0.22 * p.speed;
+
+ // 2. Interactive Mouse Vortex & Kinetic Attraction
+ if (bgMouse.active) {
+ const mdx = bgMouse.x - p.x;
+ const mdy = bgMouse.y - p.y;
+ const mDist = Math.hypot(mdx, mdy);
+ if (mDist < 220 && mDist > 0.001) {
+ const factor = (1 - mDist / 220);
+
+ // Tangential vortex whirlpool force
+ const swirlX = -mdy / mDist;
+ const swirlY = mdx / mDist;
+ p.vx += swirlX * factor * 2.8;
+ p.vy += swirlY * factor * 2.8;
+
+ // Gentle radial pull into the vortex center
+ p.vx += (mdx / mDist) * factor * 1.1;
+ p.vy += (mdy / mDist) * factor * 1.1;
+
+ // Kinetic velocity transfer from rapid cursor movement
+ p.vx += bgMouse.vx * factor * 0.18;
+ p.vy += bgMouse.vy * factor * 0.18;
+ }
}
- }
- // Draw star node
- const glowBoost = audio.bass * 0.65;
- const currentAlpha = Math.min(1, s.alpha * (0.8 + 0.3 * Math.sin(s.pulse)) + glowBoost);
- bgCtx.fillStyle = `rgba(255, 255, 255, ${currentAlpha})`;
- bgCtx.beginPath();
- bgCtx.arc(s.x, s.y, s.size * (1 + glowBoost * 0.5), 0, Math.PI * 2);
- bgCtx.fill();
+ // 3. Shockwave Impulse on particles
+ for (let k = 0; k < shockwaves.length; k++) {
+ const sw = shockwaves[k];
+ const sdx = p.x - sw.x;
+ const sdy = p.y - sw.y;
+ const sDist = Math.hypot(sdx, sdy);
+ const ringDist = Math.abs(sDist - sw.radius);
+ if (ringDist < 45 && sDist > 0.001) {
+ const push = (1 - ringDist / 45) * 3.5 * sw.alpha * sw.power;
+ p.vx += (sdx / sDist) * push;
+ p.vy += (sdy / sDist) * push;
+ }
+ }
+
+ // Fluid viscosity / drag
+ p.vx *= 0.92;
+ p.vy *= 0.92;
+
+ p.x += p.vx * flowSpeed;
+ p.y += p.vy * flowSpeed;
+
+ // Boundary wrapping with clean respawn
+ p.life++;
+ if (p.x < -10 || p.x > W + 10 || p.y < -10 || p.y > H + 10 || p.life > p.maxLife) {
+ p.x = Math.random() * W;
+ p.y = Math.random() * H;
+ p.oldX = p.x;
+ p.oldY = p.y;
+ p.vx = 0;
+ p.vy = 0;
+ p.life = 0;
+ }
+
+ // 4. Render Silky Luminous Trail
+ const vMag = Math.hypot(p.vx, p.vy);
+ const glowAlpha = Math.min(1, p.alpha * (0.8 + audio.bass * 0.9) + vMag * 0.08);
+
+ bgCtx.strokeStyle = `rgba(255, 255, 255, ${glowAlpha})`;
+ bgCtx.lineWidth = p.size * (1 + audio.bass * 0.45);
+ bgCtx.beginPath();
+ bgCtx.moveTo(p.oldX, p.oldY);
+ bgCtx.lineTo(p.x, p.y);
+ bgCtx.stroke();
+
+ // Bright stardust head point
+ bgCtx.fillStyle = `rgba(255, 255, 255, ${Math.min(1, glowAlpha + 0.3)})`;
+ bgCtx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
+ }
+ return;
}
- // 2. Render expanding shockwaves
- for (let i = shockwaves.length - 1; i >= 0; i--) {
- const sw = shockwaves[i];
- sw.radius += 5.5;
- sw.alpha *= 0.96;
- if (sw.radius > sw.maxRadius || sw.alpha < 0.01) {
- shockwaves.splice(i, 1);
- continue;
- }
- bgCtx.strokeStyle = `rgba(255, 255, 255, ${sw.alpha * 0.28})`;
- bgCtx.lineWidth = 1.5;
- bgCtx.beginPath();
- bgCtx.ellipse(sw.x, sw.y, sw.radius, sw.radius * 0.35, 0, 0, Math.PI * 2);
- bgCtx.stroke();
- }
+ if (currentMode === 'cyber-horizon') {
+ // ── Clean 3D Perspective Horizon (Zero text or clutter) ──────────────
+ bgCtx.fillStyle = 'rgba(0, 0, 0, 0.22)';
+ bgCtx.fillRect(0, 0, W, H);
+ const horizonY = H * 0.65;
- // 3. Render 3D Perspective Cyber-Grid (if in cyber-horizon mode)
- if (isHorizon) {
- // Horizon glow aura
+ // Horizon glow
const hGrad = bgCtx.createLinearGradient(0, horizonY - 35, 0, horizonY + 25);
hGrad.addColorStop(0, 'rgba(255, 255, 255, 0)');
hGrad.addColorStop(0.5, `rgba(255, 255, 255, ${0.1 + audio.bass * 0.2})`);
@@ -1898,7 +1959,7 @@
bgCtx.fillStyle = hGrad;
bgCtx.fillRect(0, horizonY - 35, W, 60);
- // Horizon sharp divider line
+ // Horizon sharp divider
bgCtx.strokeStyle = `rgba(255, 255, 255, ${0.4 + audio.bass * 0.4})`;
bgCtx.lineWidth = 1;
bgCtx.beginPath();
@@ -1906,7 +1967,7 @@
bgCtx.lineTo(W, horizonY);
bgCtx.stroke();
- // Longitudinal radiating perspective lines
+ // Longitudinal radiating lines
const lineCount = 28;
const centerX = W / 2;
const bottomSpread = W * 1.4;
@@ -1929,8 +1990,6 @@
const progress = (k + gridOffset) / transLines;
const py = horizonY + (H - horizonY) * Math.pow(progress, 2.4);
const tAlpha = Math.min(0.35, progress * 0.38 + audio.bass * 0.15);
-
- // Audio-reactive terrain ripple across the line
bgCtx.strokeStyle = `rgba(255, 255, 255, ${tAlpha})`;
bgCtx.beginPath();
const stepX = 20;