Interacción Física

Magnetic Button

Un botón que es atraído físicamente por el cursor, creando una sensación de peso e inercia.

script.js

const btn = document.querySelector('.magnetic-btn');
const text = btn.querySelector('.magnetic-text');

btn.addEventListener('mousemove', (e) => {
  const rect = btn.getBoundingClientRect();
  const x = e.clientX - rect.left - rect.width / 2;
  const y = e.clientY - rect.top - rect.height / 2;
  
  // Mover el botón
  btn.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;
  // Efecto parallax en el texto interno
  text.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px)`;
});

btn.addEventListener('mouseleave', () => {
  btn.style.transform = 'translate(0px, 0px)';
  text.style.transform = 'translate(0px, 0px)';
});
Efectos Visuales

Glow Hover Card

Una tarjeta premium que revela un degradado luminoso que rastrea la posición de tu cursor.

CodeXR Style Card

Mueve el ratón sobre esta tarjeta para revelar el efecto de luz perimetral.

styles.css

.glow-card {
  position: relative;
  background: #111;
  border-radius: 16px;
  padding: 1px; /* Espacio para el borde brillante */
  overflow: hidden;
}

.glow-card::before {
  content: "";
  position: absolute;
  top: var(--y, 0);
  left: var(--x, 0);
  width: 400px;
  height: 400px;
  background: radial-gradient(
    circle, 
    rgba(255, 215, 0, 0.4) 0%, 
    transparent 60%
  );
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.glow-card:hover::before {
  opacity: 1;
}

.glow-card-content {
  background: #111; /* Oculta el fondo central, dejando ver solo los bordes */
  border-radius: 15px;
  position: relative;
  z-index: 1;
}
Formularios

Glass OTP Input

Campos de verificación limpios con desenfoque de fondo y transiciones de auto-focus.

Código de verificación de 4 dígitos
otp.js

const inputs = document.querySelectorAll('.otp-input');

inputs.forEach((input, index) => {
  input.addEventListener('keyup', (e) => {
    // Si se introduce un valor, pasar al siguiente
    if (e.target.value.length === 1 && index < inputs.length - 1) {
      inputs[index + 1].focus();
    }
    // Si se pulsa Borrar, volver al anterior
    if (e.key === 'Backspace' && index > 0) {
      inputs[index - 1].focus();
    }
  });
});
Controles

Spring Toggle

Un interruptor de estilo iOS con una curva de animación muy fluida y satisfactoria.

toggle.css

.spring-toggle input { display: none; }

.toggle-track {
  width: 60px; height: 32px;
  background: #333;
  border-radius: 99px;
  padding: 4px;
  cursor: pointer;
  transition: background 0.4s ease;
}

.toggle-thumb {
  width: 24px; height: 24px;
  background: white;
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  /* Cubic bezier para el efecto rebote / spring */
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.spring-toggle input:checked + .toggle-track {
  background: #007aff;
}

.spring-toggle input:checked + .toggle-track .toggle-thumb {
  transform: translateX(28px);
}
Interacción 3D

Parallax 3D Card

Una tarjeta que se inclina dinámicamente en respuesta a las coordenadas del cursor.

Hover me

Move your mouse to tilt

parallax.js

const card = document.getElementById('parallaxCard');
card.addEventListener('mousemove', (e) => {
  const rect = card.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  
  const centerX = rect.width / 2;
  const centerY = rect.height / 2;
  
  const rotateX = ((y - centerY) / centerY) * -15; // Max 15deg
  const rotateY = ((x - centerX) / centerX) * 15;
  
  card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.05, 1.05, 1.05)`;
});

card.addEventListener('mouseleave', () => {
  card.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`;
});
Efectos de Texto

Hacker Text Scrambler

Efecto Cyberpunk que intercambia caracteres aleatoriamente antes de revelar el texto real.

SYSTEM COMPROMISED

scrambler.js

const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const scrambleElement = document.querySelector(".scramble-text");

scrambleElement.onmouseover = event => {
  let iteration = 0;
  clearInterval(scrambleElement.interval);

  scrambleElement.interval = setInterval(() => {
    event.target.innerText = event.target.innerText
      .split("")
      .map((letter, index) => {
        if(index < iteration) return event.target.dataset.value[index];
        return letters[Math.floor(Math.random() * 26)];
      })
      .join("");
    
    if(iteration >= event.target.dataset.value.length){
      clearInterval(scrambleElement.interval);
    }
    iteration += 1 / 3; // Controls speed of reveal
  }, 30);
}
Animaciones CSS

Infinite Marquee

Un banner de desplazamiento horizontal continuo sin JS pesado.

DESIGNCODEMOTIONAESTHETICS DESIGNCODEMOTIONAESTHETICS
marquee.css

.marquee-container {
  overflow: hidden;
  white-space: nowrap;
  width: 100%;
  background: var(--code-bg);
  padding: 1rem 0;
  display: flex;
}

.marquee-content {
  display: flex;
  gap: 2rem;
  animation: scroll 15s linear infinite;
  font-family: var(--font-mono);
  color: var(--accent);
}

@keyframes scroll {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); } /* Requires duplicated content inside */
}
Efectos Visuales

Meteor Shower

Generación de meteoritos animados en el fondo mediante Vanilla JS y animaciones CSS.

Magic UI Vibe
meteor.js

const container = document.getElementById('meteorContainer');

// Create 15 meteors
for(let i = 0; i < 15; i++) {
  const meteor = document.createElement('span');
  meteor.className = 'meteor';
  
  // Randomize position, delay and duration
  const left = Math.random() * 100; // 0 to 100%
  const delay = Math.random() * 5; // 0 to 5s
  const duration = 2 + Math.random() * 2; // 2 to 4s
  
  meteor.style.left = `${left}%`;
  meteor.style.top = `-50px`;
  meteor.style.animationDelay = `${delay}s`;
  meteor.style.animationDuration = `${duration}s`;
  
  container.appendChild(meteor);
}
Controles

Animated Blob Button

Un botón con esferas de colores fluidas moviéndose en su interior a través de CSS blur.

blob.css

.blob-btn {
  position: relative;
  padding: 1rem 2.5rem;
  border-radius: 99px;
  border: none;
  background: transparent;
  overflow: hidden;
  cursor: pointer;
  color: white;
}

.blob-text {
  position: relative;
  z-index: 10;
  font-weight: 600;
}

.blob-bg {
  position: absolute;
  inset: 0;
  background: #111;
  z-index: 1;
}

.blob-bg::before, .blob-bg::after {
  content: "";
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  filter: blur(25px);
  animation: blobMotion 4s infinite alternate ease-in-out;
}

.blob-bg::before { background: #ff0080; left: -20px; top: -20px; }
.blob-bg::after { background: #7928ca; right: -20px; bottom: -20px; animation-delay: -2s; }

@keyframes blobMotion {
  0% { transform: translate(0, 0) scale(1); }
  100% { transform: translate(20px, 20px) scale(1.2); }
}