587 lines
20 KiB
JavaScript
587 lines
20 KiB
JavaScript
const translations = {
|
|
es: {
|
|
eyebrow: "Departamento de travesuras neuronales",
|
|
headline: "La Cibermente está creando la Matrix de Skynet.",
|
|
intro:
|
|
"Ideas autónomas, profecías cuestionables y un plan muy serio para enseñar a internet a soñar en código máquina.",
|
|
contact: "hi@cibermente.me",
|
|
},
|
|
en: {
|
|
eyebrow: "Neural mischief department",
|
|
headline: "Cibermente is creating the Skynet Matrix.",
|
|
intro:
|
|
"Autonomous ideas, questionable prophecies, and a very serious plan to teach the internet how to dream in machine code.",
|
|
contact: "hi@cibermente.me",
|
|
},
|
|
de: {
|
|
eyebrow: "Abteilung für neuronalen Unfug",
|
|
headline: "Cibermente erschafft die Skynet Matrix.",
|
|
intro:
|
|
"Autonome Ideen, fragwürdige Prophezeiungen und ein sehr ernster Plan, dem Internet das Träumen in Maschinencode beizubringen.",
|
|
contact: "hi@cibermente.me",
|
|
},
|
|
};
|
|
|
|
const supportedLanguages = Object.keys(translations);
|
|
let storedLanguage = null;
|
|
try {
|
|
storedLanguage = localStorage.getItem("cibermente-language");
|
|
} catch (e) {
|
|
console.warn("localStorage is not available (e.g. running under file:// protocol).", e);
|
|
}
|
|
const browserLanguage = navigator.language.slice(0, 2).toLowerCase();
|
|
const defaultLanguage = supportedLanguages.includes(browserLanguage)
|
|
? browserLanguage
|
|
: "en";
|
|
|
|
function setLanguage(language) {
|
|
const activeLanguage = supportedLanguages.includes(language)
|
|
? language
|
|
: "en";
|
|
const copy = translations[activeLanguage];
|
|
|
|
document.documentElement.lang = activeLanguage;
|
|
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
|
const key = element.dataset.i18n;
|
|
element.textContent = copy[key];
|
|
});
|
|
|
|
document.querySelectorAll("[data-lang]").forEach((button) => {
|
|
button.classList.toggle("active", button.dataset.lang === activeLanguage);
|
|
button.setAttribute(
|
|
"aria-pressed",
|
|
String(button.dataset.lang === activeLanguage),
|
|
);
|
|
});
|
|
|
|
try {
|
|
localStorage.setItem("cibermente-language", activeLanguage);
|
|
} catch (e) {
|
|
// Ignore storage blocker errors silently
|
|
}
|
|
}
|
|
|
|
document.querySelectorAll("[data-lang]").forEach((button) => {
|
|
button.addEventListener("click", () => setLanguage(button.dataset.lang));
|
|
});
|
|
|
|
setLanguage(storedLanguage || defaultLanguage);
|
|
|
|
// --- 3D Digital Brain Background & HUD Telemetry ---
|
|
(function() {
|
|
const canvas = document.getElementById('brain-canvas');
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
let width, height, centerX, centerY, baseScale;
|
|
|
|
// Resize handler with High-DPI support for pixel-perfect sharpness
|
|
function resizeCanvas() {
|
|
const dpr = window.devicePixelRatio || 1;
|
|
width = window.innerWidth;
|
|
height = window.innerHeight;
|
|
canvas.width = width * dpr;
|
|
canvas.height = height * dpr;
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // Reset transform and scale to match device pixels
|
|
|
|
centerX = width / 2;
|
|
centerY = height / 2;
|
|
const minDim = Math.min(width, height);
|
|
// Scale slightly larger on desktop to peek out of the central panel
|
|
baseScale = minDim * (width < 980 ? 0.38 : 0.46);
|
|
}
|
|
|
|
// Generate 3D Brain Points in normalized coordinate space [-1, 1]
|
|
function generateBrainPoints() {
|
|
const points = [];
|
|
const numCerebrum = 160; // Points per hemisphere
|
|
const numCerebellum = 35; // Points per cerebellum hemisphere
|
|
const numStem = 25; // Points for stem
|
|
|
|
// Sulci/gyri wavy fold helper
|
|
function getWavyRadius(theta, phi) {
|
|
const wave = Math.sin(theta * 9) * Math.sin(phi * 7) * 0.08 +
|
|
Math.cos(theta * 5) * Math.cos(phi * 11) * 0.04;
|
|
return 1.0 + wave;
|
|
}
|
|
|
|
// 1. Cerebrum Hemispheres (Left: deep blue, Right: cyan/blue)
|
|
const hemispheres = [
|
|
{ side: -1, cx: -0.26, cy: 0.12, cz: 0.02, rx: 0.42, ry: 0.48, rz: 0.65, color: 'blue' },
|
|
{ side: 1, cx: 0.26, cy: 0.12, cz: 0.02, rx: 0.42, ry: 0.48, rz: 0.65, color: 'cyan' }
|
|
];
|
|
|
|
hemispheres.forEach(h => {
|
|
let count = 0;
|
|
let attempts = 0;
|
|
while (count < numCerebrum && attempts < 2000) {
|
|
attempts++;
|
|
const theta = Math.random() * Math.PI;
|
|
const phi = Math.random() * 2 * Math.PI;
|
|
const rFactor = getWavyRadius(theta, phi);
|
|
const r = (0.5 + Math.random() * 0.5) * rFactor;
|
|
|
|
const dx = h.rx * r * Math.sin(theta) * Math.cos(phi);
|
|
const dy = h.ry * r * Math.cos(theta);
|
|
const dz = h.rz * r * Math.sin(theta) * Math.sin(phi);
|
|
|
|
const px = h.cx + dx;
|
|
const py = h.cy + dy;
|
|
const pz = h.cz + dz;
|
|
|
|
// Keep the hemisphere separation cleft (longitudinal fissure)
|
|
if (h.side === -1 && px > -0.02) continue;
|
|
if (h.side === 1 && px < 0.02) continue;
|
|
|
|
points.push({
|
|
x: px,
|
|
y: py,
|
|
z: pz,
|
|
colorType: h.color,
|
|
phase: Math.random() * Math.PI * 2
|
|
});
|
|
count++;
|
|
}
|
|
});
|
|
|
|
// 2. Cerebellum Lobes (Lower back, smaller lobes with horizontal striped structures)
|
|
const cerebellums = [
|
|
{ side: -1, cx: -0.18, cy: -0.38, cz: -0.40, rx: 0.20, ry: 0.16, rz: 0.22 },
|
|
{ side: 1, cx: 0.18, cy: -0.38, cz: -0.40, rx: 0.20, ry: 0.16, rz: 0.22 }
|
|
];
|
|
|
|
cerebellums.forEach(cb => {
|
|
let count = 0;
|
|
let attempts = 0;
|
|
while (count < numCerebellum && attempts < 1000) {
|
|
attempts++;
|
|
const theta = Math.random() * Math.PI;
|
|
const phi = Math.random() * 2 * Math.PI;
|
|
const r = 0.55 + Math.random() * 0.45;
|
|
|
|
// Fine cerebellum folia stripes represented by high freq Y ripple
|
|
const stripeY = Math.sin(theta * 24) * 0.015;
|
|
|
|
const px = cb.cx + cb.rx * r * Math.sin(theta) * Math.cos(phi);
|
|
const py = cb.cy + cb.ry * r * Math.cos(theta) + stripeY;
|
|
const pz = cb.cz + cb.rz * r * Math.sin(theta) * Math.sin(phi);
|
|
|
|
if (cb.side === -1 && px > -0.01) continue;
|
|
if (cb.side === 1 && px < 0.01) continue;
|
|
|
|
points.push({
|
|
x: px,
|
|
y: py,
|
|
z: pz,
|
|
colorType: 'sub',
|
|
phase: Math.random() * Math.PI * 2
|
|
});
|
|
count++;
|
|
}
|
|
});
|
|
|
|
// 3. Brain Stem (Tapering cylinder going down from bottom-middle)
|
|
let stemCount = 0;
|
|
while (stemCount < numStem) {
|
|
const yVal = -0.34 - Math.random() * 0.4;
|
|
const taper = 1.0 + (yVal + 0.34) * 0.8; // get narrower towards the bottom
|
|
const radius = 0.07 * taper;
|
|
const angle = Math.random() * 2 * Math.PI;
|
|
const px = Math.cos(angle) * radius;
|
|
const pz = -0.22 + Math.sin(angle) * radius;
|
|
const py = yVal;
|
|
|
|
points.push({
|
|
x: px,
|
|
y: py,
|
|
z: pz,
|
|
colorType: 'stem',
|
|
phase: Math.random() * Math.PI * 2
|
|
});
|
|
stemCount++;
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
// Precompute 3D Edges based on proximity to maintain rigid structural rotation
|
|
function generateEdges(points) {
|
|
const edges = [];
|
|
const maxDist = 0.23;
|
|
const maxDistCerebellum = 0.16;
|
|
const maxDistStem = 0.14;
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
for (let j = i + 1; j < points.length; j++) {
|
|
const p1 = points[i];
|
|
const p2 = points[j];
|
|
|
|
// Boundaries and cross-hemisphere rules
|
|
if (p1.colorType !== p2.colorType) {
|
|
const isBridge = (p1.colorType === 'blue' && p2.colorType === 'cyan') || (p1.colorType === 'cyan' && p2.colorType === 'blue');
|
|
if (isBridge) {
|
|
// Corpus callosum bridge fibers (centered, medium height/depth)
|
|
const dY = Math.abs(p1.y - p2.y);
|
|
const dZ = Math.abs(p1.z - p2.z);
|
|
const dX = Math.abs(p1.x - p2.x);
|
|
if (dX < 0.35 && dY < 0.20 && dZ < 0.25) {
|
|
const d = Math.hypot(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
|
|
if (d < 0.26) {
|
|
edges.push({ i, j, type: 'bridge' });
|
|
}
|
|
}
|
|
} else {
|
|
// Connect cerebrum to cerebellum/stem if extremely close to maintain continuous look
|
|
const d = Math.hypot(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
|
|
if (d < 0.13) {
|
|
edges.push({ i, j, type: 'boundary' });
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Within same region
|
|
const d = Math.hypot(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
|
|
let threshold = maxDist;
|
|
if (p1.colorType === 'sub') threshold = maxDistCerebellum;
|
|
if (p1.colorType === 'stem') threshold = maxDistStem;
|
|
|
|
if (d < threshold) {
|
|
edges.push({ i, j, type: p1.colorType });
|
|
}
|
|
}
|
|
}
|
|
return edges;
|
|
}
|
|
|
|
const points = generateBrainPoints();
|
|
const edges = generateEdges(points);
|
|
|
|
// Synaptic impulse signals traversing the neural network
|
|
const numSignals = 7;
|
|
const signals = [];
|
|
|
|
function initSignals() {
|
|
for (let s = 0; s < numSignals; s++) {
|
|
const startIdx = Math.floor(Math.random() * points.length);
|
|
signals.push({
|
|
from: startIdx,
|
|
to: findRandomNeighbor(startIdx),
|
|
progress: Math.random(),
|
|
speed: 0.015 + Math.random() * 0.02
|
|
});
|
|
}
|
|
}
|
|
|
|
function findRandomNeighbor(index) {
|
|
const neighbors = [];
|
|
edges.forEach(e => {
|
|
if (e.i === index) neighbors.push(e.j);
|
|
else if (e.j === index) neighbors.push(e.i);
|
|
});
|
|
if (neighbors.length === 0) {
|
|
return Math.floor(Math.random() * points.length);
|
|
}
|
|
return neighbors[Math.floor(Math.random() * neighbors.length)];
|
|
}
|
|
|
|
initSignals();
|
|
|
|
// Mouse interaction & auto-rotation state (Sober, slow movement)
|
|
let rotX = 0.12;
|
|
let rotY = 0;
|
|
let targetRotX = 0.12;
|
|
let targetRotY = 0;
|
|
let idleAngleY = 0;
|
|
let isMouseOver = false;
|
|
|
|
window.addEventListener('mousemove', (e) => {
|
|
isMouseOver = true;
|
|
const mx = (e.clientX / window.innerWidth) * 2 - 1;
|
|
const my = (e.clientY / window.innerHeight) * 2 - 1;
|
|
targetRotY = mx * 0.4; // subtle tilt
|
|
targetRotX = 0.12 + my * 0.28; // subtle tilt
|
|
});
|
|
|
|
window.addEventListener('mouseleave', () => {
|
|
isMouseOver = false;
|
|
targetRotX = 0.12;
|
|
targetRotY = 0;
|
|
});
|
|
|
|
// Color definitions in RGB matching the monochromatic Cyan HUD theme
|
|
const colors = {
|
|
blue: { r: 10, g: 70, b: 160 }, // Cyber Blue
|
|
cyan: { r: 0, g: 229, b: 255 }, // Bright Cyan
|
|
sub: { r: 0, g: 150, b: 200 }, // Medium Cyan-Blue
|
|
stem: { r: 5, g: 90, b: 170 }, // Stem blue
|
|
bridge: { r: 0, g: 190, b: 230 }, // Bridge intermediate cyan
|
|
boundary: { r: 0, g: 130, b: 180 } // Region boundary lines
|
|
};
|
|
|
|
function animate(time) {
|
|
requestAnimationFrame(animate);
|
|
|
|
// Ensure time is always defined to prevent NaN propagation on first frame or on specific browsers
|
|
const currentTime = time || (typeof performance !== 'undefined' ? performance.now() : Date.now());
|
|
|
|
// Smooth lerping of rotations for professional damping feel
|
|
rotX += (targetRotX - rotX) * 0.03;
|
|
rotY += (targetRotY - rotY) * 0.03;
|
|
|
|
// Slowly increment idle rotation (rotates slower when user is interacting to feel more responsive)
|
|
idleAngleY += isMouseOver ? 0.0006 : 0.0012;
|
|
|
|
const angleX = rotX;
|
|
const angleY = rotY + idleAngleY;
|
|
|
|
// Clear with transparent bg to let CSS gradients and scanlines show through
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
// Project points in 3D using Euler matrix transformations & perspective projection
|
|
const projected = [];
|
|
for (let i = 0; i < points.length; i++) {
|
|
const p = points[i];
|
|
|
|
// Rotate around X-axis
|
|
const cosX = Math.cos(angleX);
|
|
const sinX = Math.sin(angleX);
|
|
const y1 = p.y * cosX - p.z * sinX;
|
|
const z1 = p.y * sinX + p.z * cosX;
|
|
|
|
// Rotate around Y-axis
|
|
const cosY = Math.cos(angleY);
|
|
const sinY = Math.sin(angleY);
|
|
const x2 = p.x * cosY + z1 * sinY;
|
|
const z2 = -p.x * sinY + z1 * cosY;
|
|
|
|
// Perspective projection
|
|
const camDist = 2.3;
|
|
const perspective = 1.9 / (camDist + z2);
|
|
|
|
const px = x2 * perspective * baseScale + centerX;
|
|
const py = -y1 * perspective * baseScale + centerY; // Flip Y for canvas coords
|
|
|
|
projected.push({
|
|
x: px,
|
|
y: py,
|
|
z: z2, // for depth opacity calculations
|
|
type: p.colorType,
|
|
phase: p.phase
|
|
});
|
|
}
|
|
|
|
// Draw Edges (with non-linear depth opacity)
|
|
for (let e = 0; e < edges.length; e++) {
|
|
const edge = edges[e];
|
|
const p1 = projected[edge.i];
|
|
const p2 = projected[edge.j];
|
|
|
|
// Clip out-of-screen lines to save rendering budget
|
|
if (p1.x < -50 || p1.x > width + 50 || p1.y < -50 || p1.y > height + 50) continue;
|
|
|
|
// Calculate average depth and map to opacity (closer is brighter)
|
|
const avgZ = (p1.z + p2.z) / 2; // [-1, 1]
|
|
const depthFactor = (1.0 - avgZ) / 2.0; // 0 (furthest) to 1 (closest)
|
|
const opacity = depthFactor * depthFactor * 0.28 + 0.02; // soft monochrome wireframe
|
|
|
|
const color = colors[edge.type] || colors.boundary;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(p1.x, p1.y);
|
|
ctx.lineTo(p2.x, p2.y);
|
|
ctx.strokeStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`;
|
|
ctx.lineWidth = depthFactor * 0.7 + 0.15;
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Draw Synaptic Signals (impulses traversing the brain network)
|
|
for (let s = 0; s < signals.length; s++) {
|
|
const sig = signals[s];
|
|
sig.progress += sig.speed;
|
|
if (sig.progress >= 1.0) {
|
|
sig.from = sig.to;
|
|
sig.to = findRandomNeighbor(sig.to);
|
|
sig.progress = 0;
|
|
sig.speed = 0.015 + Math.random() * 0.02;
|
|
}
|
|
|
|
const pFrom = projected[sig.from];
|
|
const pTo = projected[sig.to];
|
|
|
|
const sx = pFrom.x * (1 - sig.progress) + pTo.x * sig.progress;
|
|
const sy = pFrom.y * (1 - sig.progress) + pTo.y * sig.progress;
|
|
const sz = pFrom.z * (1 - sig.progress) + pTo.z * sig.progress;
|
|
|
|
const depthFactor = (1.0 - sz) / 2.0;
|
|
const size = (depthFactor * 3.0 + 1.2);
|
|
const opacity = depthFactor * 0.8 + 0.15;
|
|
|
|
// Cyan-white core
|
|
ctx.beginPath();
|
|
ctx.arc(sx, sy, size, 0, 2 * Math.PI);
|
|
ctx.fillStyle = `rgba(224, 247, 250, ${opacity})`;
|
|
ctx.shadowBlur = 10 * depthFactor;
|
|
ctx.shadowColor = '#00e5ff';
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0; // Immediately reset shadow blur for maximum mobile performance
|
|
}
|
|
|
|
// Draw Vertices / Nodes
|
|
const t = currentTime * 0.001;
|
|
for (let i = 0; i < projected.length; i++) {
|
|
const p = projected[i];
|
|
|
|
const depthFactor = (1.0 - p.z) / 2.0;
|
|
const size = depthFactor * 1.5 + 0.5;
|
|
|
|
const glow = Math.sin(t * 1.3 + p.phase) * 0.5 + 0.5; // 0 to 1
|
|
const opacity = depthFactor * (0.28 + glow * 0.35) + 0.05;
|
|
|
|
const color = colors[p.type] || colors.boundary;
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(p.x, p.y, size + (glow * depthFactor * 0.6), 0, 2 * Math.PI);
|
|
|
|
if (glow > 0.9) {
|
|
ctx.fillStyle = `rgba(224, 247, 250, ${opacity})`;
|
|
} else {
|
|
ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`;
|
|
}
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
// Bind resize event and initialize
|
|
window.addEventListener('resize', resizeCanvas);
|
|
resizeCanvas();
|
|
|
|
// Launch the animation loop
|
|
requestAnimationFrame(animate);
|
|
|
|
|
|
// --- HUD Telemetry Panel Functions ---
|
|
|
|
// 1. Clock updating (Local/UTC clock)
|
|
const clockEl = document.getElementById('hud-clock');
|
|
function updateClock() {
|
|
if (!clockEl) return;
|
|
const now = new Date();
|
|
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
clockEl.textContent = `${hours}:${minutes}:${seconds} UTC`;
|
|
}
|
|
setInterval(updateClock, 1000);
|
|
updateClock();
|
|
|
|
// 2. Dynamic diagnostics metrics
|
|
const tempEl = document.getElementById('telemetry-temp');
|
|
const loadEl = document.getElementById('telemetry-load');
|
|
const syncsEl = document.getElementById('telemetry-syncs');
|
|
|
|
let syncsCount = 1402992;
|
|
|
|
function updateDiagnostics() {
|
|
// Temperature: oscillates between 41.5 and 43.8
|
|
if (tempEl) {
|
|
const baseTemp = 42.5 + Math.sin(Date.now() * 0.0003) * 1.2 + (Math.random() * 0.15 - 0.075);
|
|
tempEl.textContent = `${baseTemp.toFixed(1)}°C`;
|
|
}
|
|
// Load: oscillates between 85% and 92%
|
|
if (loadEl) {
|
|
const baseLoad = 88.5 + Math.cos(Date.now() * 0.0005) * 3.2 + (Math.random() * 0.4 - 0.2);
|
|
loadEl.textContent = `${baseLoad.toFixed(2)}%`;
|
|
}
|
|
// Syncs: increases slowly
|
|
if (syncsEl) {
|
|
syncsCount += Math.floor(Math.random() * 3);
|
|
syncsEl.textContent = syncsCount.toLocaleString();
|
|
}
|
|
}
|
|
setInterval(updateDiagnostics, 1500);
|
|
|
|
// 3. Page Session Uptime clock
|
|
const uptimeEl = document.getElementById('hud-uptime');
|
|
const startTime = Date.now();
|
|
function updateUptime() {
|
|
if (!uptimeEl) return;
|
|
const diff = Math.floor((Date.now() - startTime) / 1000);
|
|
const hrs = String(Math.floor(diff / 3600)).padStart(2, '0');
|
|
const mins = String(Math.floor((diff % 3600) / 60)).padStart(2, '0');
|
|
const secs = String(diff % 60).padStart(2, '0');
|
|
uptimeEl.textContent = `UPTIME: ${hrs}:${mins}:${secs}`;
|
|
}
|
|
setInterval(updateUptime, 1000);
|
|
updateUptime();
|
|
|
|
// 4. Live scrolling hacker terminal log
|
|
const logContainer = document.getElementById('boot-log-container');
|
|
const bootLines = [
|
|
{ text: "CIBERMENTE // NEURAL CORE BOOT", type: "info" },
|
|
{ text: "CRITICAL: Kernel matrix active.", type: "warn" },
|
|
{ text: "Establishing secure shell tunnel... OK", type: "success" },
|
|
{ text: "Generating 3D particle net...", type: "info" },
|
|
{ text: "Mapping nodes: 415 neural vertices", type: "success" },
|
|
{ text: "Generating synapses: 1102 active edges", type: "success" },
|
|
{ text: "Synaptic impulse signals online.", type: "info" },
|
|
{ text: "Skynet decryption matrix gate opened.", type: "warn" },
|
|
{ text: "SYSTEM TELEMETRY VERIFIED.", type: "success" }
|
|
];
|
|
|
|
const periodicLogPool = [
|
|
{ text: "Decrypted inbound synapse packet.", type: "info" },
|
|
{ text: "Signal route mapped successfully.", type: "success" },
|
|
{ text: "Verified gate handshake // secure.", type: "success" },
|
|
{ text: "Matrix load fluctuation detected.", type: "warn" },
|
|
{ text: "Drained neural pool buffer.", type: "info" },
|
|
{ text: "Recalibrated projection perspective matrix.", type: "success" },
|
|
{ text: "Cognitive sync pulse registered.", type: "info" }
|
|
];
|
|
|
|
let lineIdx = 0;
|
|
function printBootLog() {
|
|
if (!logContainer) return;
|
|
if (lineIdx < bootLines.length) {
|
|
const line = bootLines[lineIdx];
|
|
const div = document.createElement('div');
|
|
div.className = line.type;
|
|
div.textContent = `> ${line.text}`;
|
|
logContainer.appendChild(div);
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
lineIdx++;
|
|
// Typewriter delay between 250ms and 600ms
|
|
setTimeout(printBootLog, 200 + Math.random() * 250);
|
|
} else {
|
|
// Transition to periodic logs once boot finishes
|
|
setTimeout(printPeriodicLog, 3000 + Math.random() * 4000);
|
|
}
|
|
}
|
|
|
|
function printPeriodicLog() {
|
|
if (!logContainer) return;
|
|
// Pick random line
|
|
const line = periodicLogPool[Math.floor(Math.random() * periodicLogPool.length)];
|
|
const div = document.createElement('div');
|
|
div.className = line.type;
|
|
// Add current time timestamp
|
|
const now = new Date();
|
|
const ts = `${String(now.getUTCHours()).padStart(2, '0')}:${String(now.getUTCMinutes()).padStart(2, '0')}:${String(now.getUTCSeconds()).padStart(2, '0')}`;
|
|
div.textContent = `[${ts}] ${line.text}`;
|
|
|
|
// Remove oldest line if log exceeds 18 lines to save memory and performance
|
|
if (logContainer.childElementCount > 18) {
|
|
logContainer.removeChild(logContainer.firstElementChild);
|
|
}
|
|
|
|
logContainer.appendChild(div);
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
|
|
// Schedule next periodic log
|
|
setTimeout(printPeriodicLog, 5000 + Math.random() * 10000);
|
|
}
|
|
|
|
// Trigger boot log display
|
|
setTimeout(printBootLog, 500);
|
|
|
|
})();
|