1/* ======================================== 2 SCRAMBLE TEXT â revelação embaralhada de texto 3 Port em JS vanilla do efeito "Scramble Text" (Originkit, componente Framer/ 4 React) pro hero, que não usa build step/React. Reveal sequencial esquerdaâ 5 direita: cada caractere passa por alguns frames de glitch (letra aleatória) 6 antes de travar na letra certa, com leve tremeliqueio (flicker) opcional 7 ao travar. Reexecutável via NovaFMScramble.play(el) â cada chamada cancela 8 qualquer animação anterior ainda rodando no mesmo elemento. 9 ======================================== */ 10(function () { 11 'use strict'; 12 13 var GLITCH_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 14 var GLITCH_LOWER = 'abcdefghijklmnopqrstuvwxyz'; 15 16 var DURATION_MS = 1100; 17 var SCRAMBLE_COLOR = 'var(--orange)'; 18 var FLICKER_COLOR = '#ffb37a'; 19 var FLICKER_CHANCE = 0.35; 20 21 function easeOut(t) { return 1 - (1 - t) * (1 - t); } 22 23 function nextGlitchChar(char) { 24 var isLower = char === char.toLowerCase() && char !== char.toUpperCase(); 25 var pool = isLower ? GLITCH_LOWER : GLITCH_UPPER; 26 return pool[Math.floor(Math.random() * pool.length)]; 27 } 28 29 function splitTokens(text) { 30 return text.match(/\s+|\S+/g) || []; 31 } 32 33 // Reconstrói a árvore de origem em `target`, trocando cada caractere visÃvel 34 // por um <span> oculto (revelado depois, um a um) e preservando as tags de 35 // formatação ao redor deles (ex: <span style="font-weight:700">) â sem isso 36 // qualquer HTML inline digitado no campo (tÃtulo/texto complementar) seria 37 // achatado em texto puro antes de animar. 38 function buildScrambleTree(source, target, chars) { 39 source.childNodes.forEach(function (node) { 40 if (node.nodeType === Node.TEXT_NODE) { 41 splitTokens(node.textContent).forEach(function (tok) { 42 if (/^\s+$/.test(tok)) { 43 target.appendChild(document.createTextNode(tok)); 44 return; 45 } 46 tok.split('').forEach(function (ch) { 47 var span = document.createElement('span'); 48 span.textContent = ch; 49 span.style.display = 'none'; 50 target.appendChild(span); 51 chars.push({ span: span, target: ch }); 52 }); 53 }); 54 } else if (node.nodeType === Node.ELEMENT_NODE) { 55 var clone = document.createElement(node.tagName); 56 for (var i = 0; i < node.attributes.length; i++) { 57 var attr = node.attributes[i]; 58 clone.setAttribute(attr.name, attr.value); 59 } 60 target.appendChild(clone); 61 buildScrambleTree(node, clone, chars); 62 } 63 }); 64 } 65 66 function animateChar(el, token, entry, targetEnd) { 67 return new Promise(function (resolve) { 68 var span = entry.span; 69 var ch = entry.target; 70 span.style.display = ''; 71 72 if (ch === '.') { 73 span.textContent = ch; 74 setTimeout(resolve, Math.max(0, targetEnd - performance.now())); 75 return; 76 } 77 78 var windowMs = targetEnd - performance.now(); 79 var minDelay = 15; 80 var desiredFrames = 1 + Math.floor(Math.random() * 8); 81 var maxFitFrames = Math.max(1, Math.floor((windowMs * 0.8) / minDelay)); 82 var frames = Math.min(desiredFrames, maxFitFrames); 83 var delay = frames > 0 ? Math.max(minDelay, Math.floor((windowMs * 0.8) / frames)) : minDelay; 84 85 var i = 0; 86 function frame() { 87 if (el._scrambleToken !== token) { resolve(); return; } 88 if (i < frames) { 89 span.textContent = nextGlitchChar(ch); 90 span.style.color = SCRAMBLE_COLOR; 91 i++; 92 setTimeout(frame, delay); 93 return; 94 } 95 span.textContent = ch; 96 span.style.color = ''; 97 var wait = Math.max(0, targetEnd - performance.now()); 98 setTimeout(function () { 99 if (el._scrambleToken !== token) { resolve(); return; } 100 if (Math.random() < FLICKER_CHANCE) { 101 span.style.color = FLICKER_COLOR; 102 setTimeout(function () { 103 if (el._scrambleToken === token) span.style.color = ''; 104 resolve(); 105 }, 40 + Math.random() * 70); 106 } else { 107 resolve(); 108 } 109 }, wait); 110 } 111 frame(); 112 });
113 } 114 115 function play(el) { 116 if (!el) return; 117 if (el._scrambleOriginal === undefined) { 118 el._scrambleOriginal = el.innerHTML; 119 } 120 var original = el._scrambleOriginal; 121 var token = (el._scrambleToken = (el._scrambleToken || 0) + 1); 122 123 var source = document.createElement('div'); 124 source.innerHTML = original; 125 126 el.innerHTML = ''; 127 var chars = []; 128 buildScrambleTree(source, el, chars); 129 130 var total = chars.length; 131 if (!total) return; 132 133 var start = performance.now(); 134 function targetAt(step) { 135 return start + DURATION_MS * easeOut(step / total); 136 } 137 138 (async function run() { 139 for (var idx = 0; idx < chars.length; idx++) { 140 if (el._scrambleToken !== token) return; 141 await animateChar(el, token, chars[idx], targetAt(idx + 1)); 142 } 143 })(); 144 } 145 146 window.NovaFMScramble = { play: play }; 147})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.