cibermente_web/script.js
2026-09-09 17:37:17 +02:00

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);
})();