Why is Animationend firing after a delay with timing function as linear?

I simply want the animationend event to fire as soon as the animation completes visually.

The most similar answer I could find for this was this, but unfortunately using linear in my cases did not solve the issue.

document.querySelector('h1.ani').addEventListener('animationend', (e) => {
  console.log("invoked");
});
* {
  margin: 0px;
  padding: 0px;
  box-sizing: border-box;
  font-family: Arial;
}

body {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

h1 {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background-color: green;
  letter-spacing: -500px;
  position: relative;
}

h1.ani {
  animation: morph 4s linear forwards;
}

h1 span {
  transition: opacity .5s ease;
}

h1 span.show {
  opacity: 1 !important;
}

h1.hide span {
  opacity: 0;
  transform: scale(0) translateY(100%);
  position: absolute;
}

@keyframes morph {
  0% {
    letter-spacing: -500px;
  }
  40% {
    letter-spacing: 0px;
  }
  60% {
    letter-spacing: 0px;
  }
  100% {
    letter-spacing: -500px;
  }
}
<h1 class="ani">
  <span>W</span>
  <span>e</span>
  <span>b</span>
  <span>&nbsp;</span>
  <span>D</span>
  <span>e</span>
  <span>v</span>
  <span>e</span>
  <span>l</span>
  <span>o</span>
  <span>p</span>
  <span>e</span>
  <span>r</span>
</h1>