<div>
  <p>Please Hover Below</p>
  <p>Mount Everest</p>
  <p>Kangchenjunga</p>
  <p>Lhotse</p>
  <p>Chomo Lonzo</p>
  <p>Nanga Parbat</p>
  <p>Cho Oyu</p>
</div>
@import url("https://fonts.googleapis.com/css2?family=VT323&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --black: #23232b;
  --blue: #2ecee0;
}

html,
body {
  display: grid;
  place-content: center;
  min-height: 100dvh;
  font-family: "Montserrat", "Noto Sans TC", sans-serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
  background-color: var(--black);
  color: var(--white);
}

a {
  text-decoration: none;
}

a,
a:hover,
a:visited {
  color: var(--white);
}

ul,
ol {
  list-style: none;
}

button {
  border: none;
  outline: none;
}

div {
  --blue-10: color-mix(in srgb, var(--blue), transparent 90%);
  --blue-50: color-mix(in srgb, var(--blue), transparent 50%);

  padding: 32px 64px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: linear-gradient(0deg, var(--blue-50), transparent),
    linear-gradient(var(--blue-10) 1px, var(--black) 2px) center/100% 3px;
}

p {
  font-family: "VT323", monospace;
  letter-spacing: 1px;
  font-size: 36px;
}

p {
  --color: var(--blue);
  color: var(--color);
  cursor: pointer;
}
const textEls = document.querySelectorAll("p");

textEls.forEach((textEl) => {
  splitText(textEl, { charClass: "char" });
  textEl.addEventListener("mouseenter", hoverTextAnimation);
});

/**
 * @typedef {object} SplitTextOptions
 * @property {'words' | 'chars'} type
 * @property {string?} wordClass
 * @property {string?} charClass
 *
 * @param {HTMLElement} element
 * @param {SplitTextOptions} options
 */
function splitText(element, { type = "chars", wordClass, charClass } = {}) {
  const text = element.innerText;
  let splitArray;

  if (type === "words") {
    splitArray = text
      .split(" ")
      .map((word) => `<span class="${wordClass}">${word}</span>`);
  } else if (type === "chars") {
    splitArray = text
      .split("")
      .map((char) => `<span class="${charClass}">${char}</span>`);
  } else {
    throw new Error("Invalid split type. Use 'words' or 'chars'.");
  }

  element.innerHTML = splitArray.join(type === "words" ? " " : "");
}

/**
 *
 * @typedef {object} HoverTextAnimationOptions
 * @property {number} charDuration
 * @property {number} charDelay
 * @property {number} repeatTimes
 *
 * @param {Event} e
 * @param {HoverTextAnimationOptions} options
 */
function hoverTextAnimation(
  e,
  { charDelay = 50, charFreq = 200, randomCharRepeats = 2 } = {}
) {
  const text = e.target.closest("p");
  if (text.classList.contains("is-animated")) return;

  const chars = text.querySelectorAll(".char");
  text.classList.add("is-animated");

  chars.forEach((char, index) => {
    createCharAnimation(char, index, {
      charDelay,
      charFreq,
      randomCharRepeats
    });
  });

  const totalDuration =
    charFreq * (randomCharRepeats + 1) + chars.length * charDelay;
  setTimeout(() => {
    text.classList.remove("is-animated");
  }, totalDuration);
}

/**
 * @param {HTMLElement} char
 * @param {number} index
 * @param {HoverTextAnimationOptions} options
 */
function createCharAnimation(
  char,
  index,
  { charDelay, charFreq, randomCharRepeats }
) {
  const initText = char.innerText;
  const delay = charDelay * index;

  // initial animation
  char.style.setProperty("opacity", 0);
  // first animation
  setTimeout(() => {
    char.style.setProperty("opacity", 1);
    char.style.setProperty("background", "var(--color)");
  }, charFreq - charDelay + delay);

  for (let i = 1; i <= randomCharRepeats; i++) {
    setTimeout(() => {
      i == 1 && char.style.setProperty("background", "transparent");
      char.innerText = randomLetterAndSymbol();
    }, charFreq * i + delay);
  }

  // last animation
  const totalDuration = charFreq * (randomCharRepeats + 1) + delay;
  setTimeout(() => {
    char.innerText = initText;
  }, totalDuration);
}

function randomLetterAndSymbol() {
  const chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
  return chars.charAt(Math.floor(Math.random() * chars.length));
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.