Jump to…
snowfeat: given a new redesign and emulated terminal homepagerxkspqmsoknz1mo
Matt W1/**
Matt W2 * Terminal typewriter animation for the homepage example session.
Matt W3 *
Matt W4 * Each line in the terminal body is either a command (typed out character by
Matt W5 * character with a blinking cursor) or output (revealed instantly after the
Matt W6 * command finishes "typing"). Blank lines pause briefly before continuing.
Matt W7 *
Matt W8 * The animation respects `prefers-reduced-motion`: when the user prefers
Matt W9 * reduced motion, all lines are shown immediately with no animation.
Matt W10 *
Matt W11 * The animation starts when the terminal scrolls into view (IntersectionObserver)
Matt W12 * and only plays once.
Matt W13 */
Matt W14(function () {
Matt W15 "use strict";
Matt W16
Matt W17 var CHAR_DELAY = 32; // ms between typed characters
Matt W18 var LINE_PAUSE = 80; // ms pause after a full line before next
Matt W19 var CMD_PAUSE = 400; // ms pause after a command finishes before showing output
Matt W20 var BLANK_PAUSE = 250; // ms pause for blank lines
Matt W21 var INITIAL_DELAY = 600; // ms before animation starts after becoming visible
Matt W22
Matt W23 function initTerminalAnimation() {
Matt W24 var body = document.querySelector("[data-term-animate]");
Matt W25 if (!body) return;
Matt W26
Matt W27 // Respect prefers-reduced-motion
Matt W28 if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
Matt W29 body.classList.add("term-ready");
Matt W30 return;
Matt W31 }
Matt W32
Matt W33 var lines = body.querySelectorAll("[data-term-line]");
Matt W34 if (!lines.length) return;
Matt W35
Matt W36 // Hide all lines initially. Toggled via a class, not inline styles: the
Matt W37 // site's CSP has no `style-src 'unsafe-inline'`, so `el.style.x = y` is
Matt W38 // silently blocked.
Matt W39 body.classList.add("term-animating");
Matt W40
Matt W41 var played = false;
Matt W42
Matt W43 // Start when visible
Matt W44 var observer = new IntersectionObserver(function (entries) {
Matt W45 if (played) return;
Matt W46 for (var j = 0; j < entries.length; j++) {
Matt W47 if (entries[j].isIntersecting) {
Matt W48 played = true;
Matt W49 observer.disconnect();
Matt W50 setTimeout(function () { playAnimation(body, lines); }, INITIAL_DELAY);
Matt W51 break;
Matt W52 }
Matt W53 }
Matt W54 }, { threshold: 0.3 });
Matt W55
Matt W56 observer.observe(body);
Matt W57 }
Matt W58
Matt W59 function playAnimation(body, lines) {
Matt W60 var idx = 0;
Matt W61
Matt W62 function next() {
Matt W63 if (idx >= lines.length) {
Matt W64 body.classList.remove("term-animating");
Matt W65 body.classList.add("term-ready");
Matt W66 return;
Matt W67 }
Matt W68
Matt W69 var line = lines[idx];
Matt W70 idx++;
Matt W71
Matt W72 line.classList.add("term-line-visible");
Matt W73
Matt W74 var isCmd = line.hasAttribute("data-term-cmd");
Matt W75 var isBlank = line.hasAttribute("data-term-blank");
Matt W76
Matt W77 if (isBlank) {
Matt W78 setTimeout(next, BLANK_PAUSE);
Matt W79 } else if (isCmd) {
Matt W80 typeCommand(line, function () {
Matt W81 setTimeout(next, CMD_PAUSE);
Matt W82 });
Matt W83 } else {
Matt W84 // Output lines: reveal instantly
Matt W85 setTimeout(next, LINE_PAUSE);
Matt W86 }
Matt W87 }
Matt W88
Matt W89 next();
Matt W90 }
Matt W91
Matt W92 function typeCommand(el, callback) {
Matt W93 var text = el.getAttribute("data-term-text") || el.textContent;
Matt W94 var original = el.textContent;
Matt W95 el.textContent = "";
Matt W96 el.classList.add("term-typing");
Matt W97
Matt W98 var charIdx = 0;
Matt W99
Matt W100 function typeChar() {
Matt W101 if (charIdx >= text.length) {
Matt W102 el.textContent = original;
Matt W103 el.classList.remove("term-typing");
Matt W104 callback();
Matt W105 return;
Matt W106 }
Matt W107
Matt W108 el.textContent = text.substring(0, charIdx + 1);
Matt W109 charIdx++;
Matt W110 setTimeout(typeChar, CHAR_DELAY);
Matt W111 }
Matt W112
Matt W113 typeChar();
Matt W114 }
Matt W115
Matt W116 // Init on DOMContentLoaded or immediately if already loaded
Matt W117 if (document.readyState === "loading") {
Matt W118 document.addEventListener("DOMContentLoaded", initTerminalAnimation);
Matt W119 } else {
Matt W120 initTerminalAnimation();
Matt W121 }
Matt W122})();

122 lines · JavaScript