The function splitText(element, text, delayStep) splits the string character by character. Spaces are preserved as plain text; each letter is wrapped in a <span style="animation-delay: Ns">, with the delay equal to the character index multiplied by delayStep (0.05 s in the function signature, 0.08 s in initFadeIn()). The result is injected as the innerHTML of the target element.
CSS drives all the animation: .text-fade-in span starts at opacity: 0 and runs @keyframes fadeInLetter (opacity: 1 at 100%) over 0.5 s, easing ease, fill-mode: forwards — each letter stays visible after its animation ends. The staggered delays mechanically produce the cascade reveal.
The replayFadeIn() function clears the innerHTML, waits 50 ms (to let the browser commit removal of the frozen animation styles), then calls initFadeIn() again. That minimal delay is necessary: a forwards keyframe stays locked on its final state as long as the node exists — clearing and re-injecting the DOM is the most reliable way to restart from scratch.
The effect is 100% CSS for the animation: no requestAnimationFrame, no canvas, no external library. JS only builds the initial DOM and handles replay. The splitText() function is generic — it accepts any element and any string, and can be called multiple times to animate independent text zones.
If JS is disabled, the static HTML structure preserves the <code><span></code> elements and CSS animation still works. If CSS animations are disabled browser-side, letters appear directly at <code>opacity: 1</code> — no errors, the text remains readable.