PageSourceSearch

https://novafm103.com.br/wp-content/themes/novafm-theme/assets/js/scramble-text.js?ver=1.17.96

js novafm103.com.br collected 2026-10-04 07:03:40 UTC 5,257 bytes, 147 lines download raw bytes

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.