<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- MODIFICAR: Título de la pestaña del navegador -->
<title>Telaraña Interactiva</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="text-container" id="textContainer">
<!-- MODIFICAR: Texto del título principal -->
<h1>Telaraña Interactiva</h1>
<!-- MODIFICAR: Texto del párrafo de descripción -->
<p>Mueve el cursor para tejer una telaraña animada.</p>
</div>
<!-- El resto del HTML gestiona el menú de configuración -->
<div id="controls" class="controls-hidden">
<h2>Ajustes</h2>
<div>
<label for="particleColor">Color Partícula:</label>
<input type="color" id="particleColor" value="#FFFFFF">
</div>
<div>
<label for="lineColor">Color Línea:</label>
<input type="color" id="lineColor" value="#FFFFFF">
</div>
<div>
<label for="bgColor">Color Fondo:</label>
<input type="color" id="bgColor" value="#1a1a1a">
</div>
<div>
<label for="maxParticles">Máx. Partículas:</label>
<input type="number" id="maxParticles" value="150" min="50" max="1000" step="10">
</div>
<div>
<label for="connectDistance">Dist. Conexión:</label>
<input type="number" id="connectDistance" value="100" min="20" max="100" step="10">
</div>
<div>
<label for="particleRadius">Radio Partícula:</label>
<input type="number" id="particleRadius" value="2" min="1" max="10" step="0.5">
</div>
<button id="resetParticles">Reiniciar Partículas</button>
</div>
<button id="toggleControls">⚙️</button>
<canvas id="spiderWebCanvas"></canvas>
<script src="script.js"></script>
</body>
</html>
body {
margin: 0;
padding: 0;
color: white;
/* MODIFICAR: La fuente principal para el texto de la página. */
font-family: 'Arial', sans-serif;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
min-height: 100vh;
transition: background-color 0.5s ease-in-out;
}
.text-container {
text-align: center;
padding-top: 10vh;
position: relative;
z-index: 10;
transition: color 0.5s ease-in-out;
}
h1 {
/* MODIFICAR: Tamaño del título principal. */
font-size: 3em;
margin-bottom: 0.2em;
font-weight: bold;
}
p {
/* MODIFICAR: Tamaño del texto de la descripción. */
font-size: 1.2em;
color: #cccccc;
}
#spiderWebCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#controls {
position: fixed;
top: 20px;
right: 20px;
/* MODIFICAR: Color de fondo y opacidad del menú de configuración. */
background-color: rgba(50, 50, 50, 0.95);
padding: 15px;
border-radius: 8px;
z-index: 100;
color: #f0f0f0;
font-size: 0.9em;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
transition: transform 0.3s ease-in-out;
width: 200px;
box-sizing: border-box;
}
.controls-hidden {
transform: translateX(calc(100% + 20px));
}
#toggleControls {
position: fixed;
top: 20px;
right: 20px;
z-index: 101;
/* MODIFICAR: Color del botón para abrir/cerrar el menú. */
background-color: #555;
color: #fff;
border: 1px solid #777;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
font-size: 1.5em;
line-height: 1;
transition: right 0.3s ease-in-out, background-color 0.2s;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
#toggleControls:hover {
background-color: #666;
}
#toggleControls.active {
right: calc(200px + 20px + 10px);
}
#controls h2 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.2em;
border-bottom: 1px solid #666;
padding-bottom: 5px;
}
#controls div {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
#controls label {
margin-right: 10px;
}
#controls input[type="color"] {
width: 40px;
height: 20px;
border: 1px solid #777;
padding: 0;
cursor: pointer;
}
#controls input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
#controls input[type="color"]::-webkit-color-swatch { border: none; border-radius: 3px; }
#controls input[type="number"] {
width: 60px;
padding: 3px;
background-color: #333;
color: #f0f0f0;
border: 1px solid #777;
border-radius: 3px;
}
#controls button {
/* MODIFICAR: Color del botón de 'Reiniciar Partículas'. */
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 0.9em;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin-top: 5px;
}
#controls button:hover {
background-color: #45a049;
}
const canvas = document.getElementById('spiderWebCanvas');
const ctx = canvas.getContext('2d');
const textContainer = document.getElementById('textContainer');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// --- ZONA DE MODIFICACIÓN PRINCIPAL ---
// Estos valores definen el estado inicial de la animación.
// También se pueden cambiar en vivo con el menú de configuración.
let config = {
// MODIFICAR: Color inicial de las partículas (puntos).
particleColor: '#FFFFFF',
// MODIFICAR: Color inicial de las líneas que conectan las partículas.
lineColor: '#FFFFFF',
// MODIFICAR: Color inicial del fondo de la animación.
backgroundColor: '#1a1a1a',
// MODIFICAR: Cantidad máxima de partículas en pantalla. Más partículas pueden afectar el rendimiento.
maxParticles: 150,
// MODIFICAR: Distancia máxima (en píxeles) a la que las partículas se conectarán con una línea.
connectDistance: 100,
// MODIFICAR: Radio MÁXIMO de las partículas. El tamaño real será aleatorio entre 1 y este valor.
particleRadius: 2,
// MODIFICAR: Velocidad general de movimiento de las partículas. Valores más altos = más rápido.
particleSpeedFactor: 0.3,
// MODIFICAR: El área alrededor del ratón (en píxeles) que afecta a las partículas.
mouseInteractionRadius: 100,
// MODIFICAR: Fuerza con la que el ratón empuja a las partículas. Un valor negativo las atraerá.
mouseRepulsionStrength: 0.8,
};
// --- FIN DE LA ZONA DE MODIFICACIÓN ---
const particleColorPicker = document.getElementById('particleColor');
const lineColorPicker = document.getElementById('lineColor');
const bgColorPicker = document.getElementById('bgColor');
const maxParticlesInput = document.getElementById('maxParticles');
const connectDistanceInput = document.getElementById('connectDistance');
const particleRadiusInput = document.getElementById('particleRadius');
const resetParticlesButton = document.getElementById('resetParticles');
const toggleControlsButton = document.getElementById('toggleControls');
const controlsMenu = document.getElementById('controls');
function initializeControls() {
particleColorPicker.value = config.particleColor;
lineColorPicker.value = config.lineColor;
bgColorPicker.value = config.backgroundColor;
maxParticlesInput.value = config.maxParticles;
connectDistanceInput.value = config.connectDistance;
particleRadiusInput.value = config.particleRadius;
document.body.style.backgroundColor = config.backgroundColor;
updateTextColor(config.backgroundColor);
}
particleColorPicker.addEventListener('input', (e) => config.particleColor = e.target.value);
lineColorPicker.addEventListener('input', (e) => config.lineColor = e.target.value);
bgColorPicker.addEventListener('input', (e) => {
config.backgroundColor = e.target.value;
document.body.style.backgroundColor = config.backgroundColor;
updateTextColor(config.backgroundColor);
});
maxParticlesInput.addEventListener('change', (e) => {
let value = parseInt(e.target.value);
value = Math.max(50, Math.min(1000, value));
e.target.value = value;
config.maxParticles = value;
});
connectDistanceInput.addEventListener('change', (e) => {
let value = parseInt(e.target.value);
value = Math.max(20, Math.min(100, value));
e.target.value = value;
config.connectDistance = value;
});
particleRadiusInput.addEventListener('change', (e) => {
let value = parseFloat(e.target.value);
value = Math.max(1, Math.min(10, value));
e.target.value = value;
config.particleRadius = value;
});
resetParticlesButton.addEventListener('click', () => init());
toggleControlsButton.addEventListener('click', () => {
controlsMenu.classList.toggle('controls-hidden');
toggleControlsButton.classList.toggle('active');
});
let particlesArray = [];
const mouse = { x: null, y: null };
window.addEventListener('mousemove', (event) => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
window.addEventListener('mouseout', () => { mouse.x = null; mouse.y = null; });
canvas.addEventListener('touchstart', (event) => {
event.preventDefault();
mouse.x = event.touches[0].clientX;
mouse.y = event.touches[0].clientY;
});
canvas.addEventListener('touchmove', (event) => {
event.preventDefault();
mouse.x = event.touches[0].clientX;
mouse.y = event.touches[0].clientY;
});
canvas.addEventListener('touchend', () => { mouse.x = null; mouse.y = null; });
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null;
}
function getLuminance(hexColor) {
const rgb = hexToRgb(hexColor);
if (!rgb) return 0;
const r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255;
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function updateTextColor(bgColor) {
const luminance = getLuminance(bgColor);
textContainer.style.color = (luminance > 0.5) ? '#333333' : '#FFFFFF';
textContainer.querySelector('p').style.color = (luminance > 0.5) ? '#555555' : '#CCCCCC';
}
class Particle {
constructor(x, y) {
this.x = x || Math.random() * canvas.width;
this.y = y || Math.random() * canvas.height;
// MODIFICAR: El '1' al final establece el tamaño MÍNIMO de las partículas.
this.size = Math.random() * config.particleRadius + 1;
this.speedX = (Math.random() - 0.5) * config.particleSpeedFactor * 2;
this.speedY = (Math.random() - 0.5) * config.particleSpeedFactor * 2;
}
draw() {
ctx.fillStyle = config.particleColor;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (mouse.x !== null && mouse.y !== null) {
const dxMouse = this.x - mouse.x;
const dyMouse = this.y - mouse.y;
const distanceMouse = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
if (distanceMouse < config.mouseInteractionRadius) {
const forceDirectionX = dxMouse / distanceMouse;
const forceDirectionY = dyMouse / distanceMouse;
const force = (config.mouseInteractionRadius - distanceMouse) / config.mouseInteractionRadius * config.mouseRepulsionStrength;
this.speedX += forceDirectionX * force * 0.1;
this.speedY += forceDirectionY * force * 0.1;
}
}
if (this.x + this.size > canvas.width || this.x - this.size < 0) { this.speedX *= -1; }
if (this.y + this.size > canvas.height || this.y - this.size < 0) { this.speedY *= -1; }
}
}
function init() {
particlesArray = [];
}
function connect() {
let opacityValue = 1;
const rgbLineColor = hexToRgb(config.lineColor);
if (!rgbLineColor) return;
for (let a = 0; a < particlesArray.length; a++) {
for (let b = a + 1; b < particlesArray.length; b++) {
const dx = particlesArray[a].x - particlesArray[b].x;
const dy = particlesArray[a].y - particlesArray[b].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.connectDistance) {
opacityValue = 1 - (distance / config.connectDistance);
ctx.strokeStyle = `rgba(${rgbLineColor.r}, ${rgbLineColor.g}, ${rgbLineColor.b}, ${opacityValue})`;
// MODIFICAR: Grosor de las líneas que conectan las partículas.
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(particlesArray[a].x, particlesArray[a].y);
ctx.lineTo(particlesArray[b].x, particlesArray[b].y);
ctx.stroke();
}
}
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (mouse.x !== null && mouse.y !== null && particlesArray.length < config.maxParticles) {
particlesArray.push(new Particle(mouse.x, mouse.y));
}
if (particlesArray.length > config.maxParticles) {
particlesArray.splice(0, particlesArray.length - config.maxParticles);
}
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].update();
particlesArray[i].draw();
}
connect();
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
initializeControls();
init();
animate();