Wordle en JavaScript: clon completo en español

Clon del juego Wordle en JavaScript puro con palabras en español. Tablero 6×5, teclado virtual, colores verde/amarillo/gris, animación de flip y detección de victoria y derrota. Todo en un único fichero HTML sin dependencias externas.
				<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wordle en Español</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #121213;
    color: #fff;
    font-family: 'Helvetica Neue', Arial, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
  }
  header {
    width: 100%;
    max-width: 500px;
    padding: 14px;
    text-align: center;
    border-bottom: 1px solid #3a3a3c;
    margin-bottom: 20px;
  }
  h1 { font-size: 1.8rem; letter-spacing: 4px; text-transform: uppercase; }
  #mensaje {
    height: 28px;
    font-size: 0.9rem;
    font-weight: bold;
    color: #fff;
    background: rgba(255,255,255,0.1);
    border-radius: 4px;
    padding: 4px 14px;
    margin-bottom: 16px;
    transition: opacity 0.3s;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 200px;
  }
  /* ?? Tablero ?? */
  #tablero {
    display: grid;
    grid-template-rows: repeat(6, 62px);
    gap: 5px;
    margin-bottom: 20px;
  }
  .fila {
    display: grid;
    grid-template-columns: repeat(5, 62px);
    gap: 5px;
  }
  .celda {
    border: 2px solid #3a3a3c;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    font-weight: bold;
    text-transform: uppercase;
    transition: border-color 0.1s;
    user-select: none;
  }
  .celda.activa { border-color: #565758; }
  .celda.pop { animation: pop 0.1s ease; }
  @keyframes pop { 0%{transform:scale(1)} 50%{transform:scale(1.12)} 100%{transform:scale(1)} }
  .celda.flip { animation: flip 0.5s ease forwards; }
  @keyframes flip {
    0%   { transform: rotateX(0);    }
    49%  { transform: rotateX(90deg);}
    50%  { transform: rotateX(90deg);}
    100% { transform: rotateX(0);    }
  }
  .correcta  { background: #538d4e; border-color: #538d4e; }
  .presente  { background: #b59f3b; border-color: #b59f3b; }
  .ausente   { background: #3a3a3c; border-color: #3a3a3c; }
  /* ?? Teclado ?? */
  #teclado { display: flex; flex-direction: column; gap: 8px; align-items: center; }
  .fila-teclado { display: flex; gap: 6px; }
  .tecla {
    height: 58px;
    min-width: 43px;
    padding: 0 6px;
    background: #818384;
    border: none;
    border-radius: 4px;
    color: #fff;
    font-size: 0.85rem;
    font-weight: bold;
    text-transform: uppercase;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.3s;
  }
  .tecla.wide { min-width: 65px; font-size: 0.75rem; }
  .tecla:hover { filter: brightness(1.15); }
  .tecla.correcta  { background: #538d4e; }
  .tecla.presente  { background: #b59f3b; }
  .tecla.ausente   { background: #3a3a3c; }
  /* ?? Modal victoria/derrota ?? */
  #modal-overlay {
    display: none;
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.7);
    align-items: center;
    justify-content: center;
    z-index: 10;
  }
  #modal {
    background: #1a1a1b;
    border: 1px solid #3a3a3c;
    border-radius: 10px;
    padding: 32px 40px;
    text-align: center;
    max-width: 320px;
    width: 90%;
  }
  #modal h2 { font-size: 1.4rem; margin-bottom: 10px; }
  #modal p  { color: #aaa; margin-bottom: 6px; font-size: 0.9rem; }
  #modal .palabra-reveal {
    font-size: 1.6rem;
    font-weight: bold;
    letter-spacing: 4px;
    color: #538d4e;
    margin: 10px 0 20px;
  }
  #btn-nuevo {
    background: #538d4e;
    color: #fff;
    border: none;
    border-radius: 4px;
    padding: 12px 28px;
    font-size: 1rem;
    font-weight: bold;
    cursor: pointer;
    width: 100%;
  }
  #btn-nuevo:hover { background: #6aaf64; }
</style>
</head>
<body>
<header><h1>Wordle</h1></header>
<div id="mensaje"></div>

<div id="tablero"></div>

<div id="teclado">
  <div class="fila-teclado" id="fila-q"></div>
  <div class="fila-teclado" id="fila-a"></div>
  <div class="fila-teclado" id="fila-z"></div>
</div>

<div id="modal-overlay">
  <div id="modal">
    <h2 id="modal-titulo"></h2>
    <p id="modal-subtitulo"></p>
    <div class="palabra-reveal" id="modal-palabra"></div>
    <button id="btn-nuevo">Nueva partida</button>
  </div>
</div>

<script>
// ?? Lista de palabras en español de 5 letras ??????????????????
const PALABRAS = [
  'ACERO','AGUJA','AIREN','ALCEN','ALDEA','ALELO','ALERO','ALIFE','ALISO',
  'ALMUD','ALOJA','ALOSA','ALUDA','ALURA','ALZAN','AMADA','AMAGO','AMARA',
  'AMARO','AMIBA','AMIGO','AMINA','AMOCO','AMOLA','AMONA','AMPLA','ANGEL',
  'ANIMA','ANION','ANISO','ANITO','ANODE','ANOLA','ANONA','BALDE','BANCO',
  'BANDA','BELGA','BELLO','BESAR','BIELA','BIRRA','BOLSA','BOMBA','BORDE',
  'BRISA','BURRO','CACAO','CACHO','CALOR','CAMPO','CANAL','CANAS','CANTO',
  'CARNE','CARTA','CASCO','CAUSA','CAZAR','CELOS','CERDO','CERIO','CIELO',
  'CINCO','CIRCO','CLASE','CLAVE','CLAVO','COBRO','COCER','COCIO','COFIA',
  'COIMA','COJAN','COJOS','COLON','COLOR','COLZA','COMER','COMIC','COMUN',
  'CORAL','CORTE','COSMO','COSTE','CREAR','CREDO','CREMA','CRIME','CRIOS',
  'CRUCE','CRUDA','CRUEL','CUERO','CULPA','DANZA','DEBUT','DECIR','DELTA',
  'DENSE','DEPEN','DESDE','DICHA','DISCO','DOLOR','DOMBO','DONAR','DUDAR',
  'DUELO','DULCE','EBRIO','EDEMA','EGYPT','EJIDO','ELEVA','ELIXIR','ENERO',
  'ENLACE','ENOJO','ENTRE','ENVIO','EPOCA','ESCALA','ESTRO','ETAPA','ETNIA',
  'FACIL','FALDA','FALSO','FANGO','FARDA','FERIA','FEROZ','FINCA','FLOTA',
  'FOBIA','FOGON','FORMA','FOTON','FRENO','FRESA','FUEGO','FUERZA','FUMAR',
  'GALLO','GARZA','GASTO','GEMIR','GENIO','GENOMA','GLOBO','GLOSA','GORDO',
  'GOTAS','GRAMO','GRANO','GRASA','GRAVE','GRITA','GRUTA','GUAPA','GUION',
  'GUSTO','HEROE','HERTZ','HIELO','HIGOS','HONOR','HOTEL','HULLA','HUMUS',
  'IDEAL','IDOLO','INDIO','INODO','JAMON','JARDIN','JAULA','JUGAR','JUNTO',
  'KARMA','KILOS','LASER','LATIR','LECHE','LEDGE','LENTE','LEONA','LIBRE',
  'LIMON','LINEA','LIRIO','LITRO','LLANO','LLENO','LLORO','LONJA','LUNES',
  'LUCHA','LUGAR','LUMBA','MADRE','MALVA','MANGO','MANOR','MARCO','MARES',
  'MAGIA','MARSH','MATAR','MEDIA','MEDIO','MELON','MENOS','MENTA','MENUS',
  'METAL','MIEDO','MIMAR','MINGO','MIRAR','MIXTO','MODELO','MODIO','MOJAR',
  'MOLDE','MONTE','MORAL','MOTOR','MOVER','MUCHO','MUNDO','NEGRO','NIEVE',
  'NIVEL','NOBLE','NOCHE','NORMA','NOVIO','NUBLA','NUEVO','OBESO','OBRAR',
  'ODIAR','OLEAJE','OLIVO','OLIVE','OPACO','OPERA','ORDEN','OREIA','ORION',
  'PAGAR','PALMA','PANDA','PANEL','PAPEL','PARED','PARIR','PARVO','PASTA',
  'PAUSA','PECHO','PEDAL','PEDIR','PENAL','PERLA','PESAR','PIANO','PICAR',
  'PILOTO','PIOJO','PISTA','PIXEL','PIZZA','PLANO','PLATA','PLAZA','PLENA',
  'PLUMA','PODER','POEMA','POLLO','POMPA','PONER','PORTA','PRISA','PROBAR',
  'PROSA','PUBIS','PUNTA','PUNTO','QUESO','RADIO','RAION','RAJAS','RANGO',
  'RASGO','RATON','REBUS','REGLA','REINA','REINO','RELOJ','REPAS','REUMA',
  'RIGOR','RINDE','RITMO','RIVAL','ROBLE','ROBOT','RONDA','ROPER','ROSTO',
  'ROTAR','RUBIO','RUEDA','RUINA','SABIO','SAGAZ','SALIR','SALON','SALUD',
  'SALVO','SAUCE','SECTA','SERIO','SIGLO','SIGMA','SIRVA','SOBRE','SOLAR',
  'SOMBA','SONAR','SOPLA','SORDO','SUAVE','SUCIO','SUELO','SUMAR','SUPER',
  'TABACO','TABLA','TACTO','TALON','TARDE','TAREA','TECLA','TEXTO','TIBIA',
  'TIRAR','TITAN','TOCAR','TOMAR','TONER','TORNO','TORPE','TOTAL','TRAMA',
  'TRAMO','TRIBU','TRIGO','TRONO','TROZO','TURBO','TUTOR','UMBRA','UNION',
  'UNTAR','URANO','URBAN','USADA','USTED','VALOR','VAPOR','VARIAR','VASCO',
  'VECINO','VELOZ','VENDA','VENIR','VENTA','VERDE','VERJA','VERSO','VIAJE',
  'VIDEO','VIGOR','VIRAL','VISOR','VISTA','VIVIR','VOTAR','VUELO','YERBA',
  'YERMO','YOGUR','ZURDO'
].filter(w => w.length === 5);

// ?? Estado global ??????????????????????????????????????????????
let palabraActual, intentoActual, letraActual, terminado;
let filas, estadoTeclado;

function elegirPalabra() {
  return PALABRAS[Math.floor(Math.random() * PALABRAS.length)];
}

function iniciar() {
  palabraActual = elegirPalabra();
  intentoActual = 0;
  letraActual   = 0;
  terminado     = false;
  estadoTeclado = {};

  construirTablero();
  construirTeclado();
  document.getElementById('modal-overlay').style.display = 'none';
  mostrarMensaje('');
}

function construirTablero() {
  const tablero = document.getElementById('tablero');
  tablero.innerHTML = '';
  filas = [];
  for (let i = 0; i < 6; i++) {
    const fila = document.createElement('div');
    fila.className = 'fila';
    const celdas = [];
    for (let j = 0; j < 5; j++) {
      const c = document.createElement('div');
      c.className = 'celda';
      fila.appendChild(c);
      celdas.push(c);
    }
    tablero.appendChild(fila);
    filas.push(celdas);
  }
}

const FILAS_TECLADO = [
  ['Q','W','E','R','T','Y','U','I','O','P'],
  ['A','S','D','F','G','H','J','K','L','Ñ'],
  ['?','Z','X','C','V','B','N','M','?']
];
const IDS_FILAS = ['fila-q','fila-a','fila-z'];

function construirTeclado() {
  FILAS_TECLADO.forEach((letras, i) => {
    const fila = document.getElementById(IDS_FILAS[i]);
    fila.innerHTML = '';
    letras.forEach(l => {
      const btn = document.createElement('button');
      btn.className = 'tecla' + (l === '?' || l === '?' ? ' wide' : '');
      btn.textContent = l;
      btn.dataset.letra = l;
      btn.addEventListener('click', () => manejarLetra(l));
      fila.appendChild(btn);
    });
  });
}

function manejarLetra(l) {
  if (terminado) return;
  if (l === '?') {
    borrar(); return;
  }
  if (l === '?') {
    confirmar(); return;
  }
  if (letraActual < 5) {
    const celda = filas[intentoActual][letraActual];
    celda.textContent = l;
    celda.classList.add('activa', 'pop');
    setTimeout(() => celda.classList.remove('pop'), 150);
    letraActual++;
  }
}

function borrar() {
  if (letraActual > 0) {
    letraActual--;
    filas[intentoActual][letraActual].textContent = '';
    filas[intentoActual][letraActual].classList.remove('activa');
  }
}

function confirmar() {
  if (letraActual < 5) {
    mostrarMensaje('Faltan letras'); return;
  }
  const intento = filas[intentoActual].map(c => c.textContent).join('');
  const resultado = evaluar(intento, palabraActual);

  // Animar con delay por celda
  resultado.forEach((estado, i) => {
    setTimeout(() => {
      const celda = filas[intentoActual][i];
      celda.classList.add('flip');
      setTimeout(() => {
        celda.classList.remove('activa');
        celda.classList.add(estado);
      }, 250);
    }, i * 100);
  });

  // Actualizar teclado después de la animación
  setTimeout(() => {
    resultado.forEach((estado, i) => {
      const letra = intento[i];
      const prioridad = {correcta:3, presente:2, ausente:1};
      if ((prioridad[estado] || 0) > (prioridad[estadoTeclado[letra]] || 0)) {
        estadoTeclado[letra] = estado;
        const btn = document.querySelector(`.tecla[data-letra="${letra}"]`);
        if (btn) { btn.classList.remove('correcta','presente','ausente'); btn.classList.add(estado); }
      }
    });

    intentoActual++;
    letraActual = 0;

    if (intento === palabraActual) {
      terminado = true;
      const mensajes = ['¡Perfecto!','¡Brillante!','¡Excelente!','¡Muy bien!','¡Bien!','¡Uf, por poco!'];
      setTimeout(() => mostrarModal(true, mensajes[intentoActual - 1] || '¡Conseguido!'), 300);
    } else if (intentoActual === 6) {
      terminado = true;
      setTimeout(() => mostrarModal(false), 300);
    }
  }, 600);
}

function evaluar(intento, objetivo) {
  const resultado = Array(5).fill('ausente');
  const conteo = {};
  // Contar letras del objetivo
  for (const l of objetivo) conteo[l] = (conteo[l] || 0) + 1;
  // Primero las correctas
  for (let i = 0; i < 5; i++) {
    if (intento[i] === objetivo[i]) {
      resultado[i] = 'correcta';
      conteo[intento[i]]--;
    }
  }
  // Luego las presentes
  for (let i = 0; i < 5; i++) {
    if (resultado[i] === 'correcta') continue;
    if (conteo[intento[i]] > 0) {
      resultado[i] = 'presente';
      conteo[intento[i]]--;
    }
  }
  return resultado;
}

function mostrarMensaje(msg) {
  const el = document.getElementById('mensaje');
  el.textContent = msg;
  el.style.opacity = msg ? '1' : '0';
  if (msg) setTimeout(() => { el.style.opacity = '0'; }, 2000);
}

function mostrarModal(victoria, titulo = '') {
  document.getElementById('modal-titulo').textContent = victoria ? titulo : '¡Oh no!';
  document.getElementById('modal-subtitulo').textContent = victoria
    ? `Lo resolviste en ${intentoActual} ${intentoActual === 1 ? 'intento' : 'intentos'}`
    : 'La palabra era:';
  document.getElementById('modal-palabra').textContent = palabraActual;
  document.getElementById('modal-overlay').style.display = 'flex';
}

// Teclado físico
document.addEventListener('keydown', e => {
  if (e.ctrlKey || e.altKey || e.metaKey) return;
  if (e.key === 'Enter')     { manejarLetra('?'); return; }
  if (e.key === 'Backspace') { manejarLetra('?'); return; }
  const l = e.key.toUpperCase();
  if (/^[A-ZÁÉÍÓÚÑ]$/.test(l)) manejarLetra(l);
});

document.getElementById('btn-nuevo').addEventListener('click', iniciar);

iniciar();
</script>
</body>
</html>

			
Descargar adjuntos
COMPARTE ESTE TUTORIAL

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP