| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function splitGraphemes(str) { |
| 5 |
try { |
| 6 |
if (window.Intl && Intl.Segmenter) { |
| 7 |
var seg = new Intl.Segmenter(undefined, { granularity: 'grapheme' }); |
| 8 |
return Array.from(seg.segment(str), function (s) { return s.segment; }); |
| 9 |
} |
| 10 |
} catch (e) {} |
| 11 |
return Array.from(str); |
| 12 |
} |
| 13 |
|
| 14 |
// WordPress's emoji script (twemoji) may replace some symbols (e.g. ✔) with <img>. |
| 15 |
// For Terminal Mockup we want to render text as text. U+FE0E forces text presentation. |
| 16 |
function forceTextPresentation(str) { |
| 17 |
if (!str) return str; |
| 18 |
return String(str).replace(/\u2714(?![\uFE0E\uFE0F])/g, '\u2714\uFE0E'); |
| 19 |
} |
| 20 |
|
| 21 |
function initTerminal(el) { |
| 22 |
var typewriterOn = el.dataset.typewriter === '1'; |
| 23 |
if (!typewriterOn) return; |
| 24 |
|
| 25 |
var speed = parseInt(el.dataset.speed, 10) || 35; |
| 26 |
var showCursor = el.dataset.cursor === '1'; |
| 27 |
var lines = Array.from(el.querySelectorAll('.bkbg-term-line:not(.bkbg-term-line--blank), .bkbg-terminal-line:not(.bkbg-terminal-line--blank)')); |
| 28 |
var blanks = Array.from(el.querySelectorAll('.bkbg-term-line--blank, .bkbg-terminal-line--blank')); |
| 29 |
|
| 30 |
/* Hide all lines initially */ |
| 31 |
lines.forEach(function (line) { line.classList.add('bkbg-tw-hidden'); }); |
| 32 |
blanks.forEach(function (b) { b.style.visibility = 'hidden'; }); |
| 33 |
|
| 34 |
var allNodes = Array.from(el.querySelectorAll('.bkbg-term-line, .bkbg-terminal-line')); |
| 35 |
var cursor = showCursor ? (el.querySelector('.bkbg-term-cursor') || el.querySelector('.bkbg-terminal-cursor')) : null; |
| 36 |
if (showCursor && !cursor) { |
| 37 |
cursor = document.createElement('span'); |
| 38 |
cursor.className = 'bkbg-term-cursor'; |
| 39 |
} |
| 40 |
|
| 41 |
function typeLine(lineEl, cb) { |
| 42 |
lineEl.classList.remove('bkbg-tw-hidden'); |
| 43 |
lineEl.classList.add('bkbg-tw-typing'); |
| 44 |
var textSpan = lineEl.querySelector('.bkbg-term-text') || lineEl.querySelector('.bkbg-terminal-text'); |
| 45 |
if (!textSpan) { |
| 46 |
lineEl.classList.remove('bkbg-tw-typing'); |
| 47 |
cb(); |
| 48 |
return; |
| 49 |
} |
| 50 |
|
| 51 |
var fullText = textSpan.getAttribute('data-bkbg-fulltext'); |
| 52 |
if (fullText === null) { |
| 53 |
fullText = textSpan.textContent; |
| 54 |
textSpan.setAttribute('data-bkbg-fulltext', fullText); |
| 55 |
} |
| 56 |
|
| 57 |
// Prevent ✔ -> <img class="emoji"> conversion. |
| 58 |
fullText = forceTextPresentation(fullText); |
| 59 |
|
| 60 |
var chars = splitGraphemes(fullText); |
| 61 |
|
| 62 |
textSpan.textContent = ''; |
| 63 |
if (cursor) { |
| 64 |
if (cursor.parentNode) cursor.parentNode.removeChild(cursor); |
| 65 |
try { |
| 66 |
var col = window.getComputedStyle(textSpan).color; |
| 67 |
cursor.style.background = col; |
| 68 |
cursor.style.color = col; |
| 69 |
} catch (e) {} |
| 70 |
lineEl.appendChild(cursor); |
| 71 |
} |
| 72 |
|
| 73 |
var i = 0; |
| 74 |
function tick() { |
| 75 |
if (i < chars.length) { |
| 76 |
textSpan.textContent += chars[i]; |
| 77 |
i++; |
| 78 |
setTimeout(tick, speed + Math.random() * 12); |
| 79 |
} else { |
| 80 |
lineEl.classList.remove('bkbg-tw-typing'); |
| 81 |
// Ensure the final rendered text is exact (no missing glyphs). |
| 82 |
textSpan.textContent = fullText; |
| 83 |
if (cursor && lineEl !== allNodes[allNodes.length - 1]) lineEl.removeChild(cursor); |
| 84 |
cb(); |
| 85 |
} |
| 86 |
} |
| 87 |
tick(); |
| 88 |
} |
| 89 |
|
| 90 |
function playNode(idx) { |
| 91 |
if (idx >= allNodes.length) return; |
| 92 |
var node = allNodes[idx]; |
| 93 |
if (node.classList.contains('bkbg-term-line--blank') || node.classList.contains('bkbg-terminal-line--blank')) { |
| 94 |
node.style.visibility = ''; |
| 95 |
setTimeout(function () { playNode(idx + 1); }, speed * 2); |
| 96 |
return; |
| 97 |
} |
| 98 |
typeLine(node, function () { |
| 99 |
setTimeout(function () { playNode(idx + 1); }, speed * 3); |
| 100 |
}); |
| 101 |
} |
| 102 |
|
| 103 |
playNode(0); |
| 104 |
} |
| 105 |
|
| 106 |
function observe(el) { |
| 107 |
if ('IntersectionObserver' in window) { |
| 108 |
var io = new IntersectionObserver(function (entries) { |
| 109 |
entries.forEach(function (entry) { |
| 110 |
if (entry.isIntersecting) { |
| 111 |
io.unobserve(entry.target); |
| 112 |
initTerminal(entry.target); |
| 113 |
} |
| 114 |
}); |
| 115 |
}, { threshold: 0.2 }); |
| 116 |
io.observe(el); |
| 117 |
} else { |
| 118 |
initTerminal(el); |
| 119 |
} |
| 120 |
} |
| 121 |
|
| 122 |
document.querySelectorAll('.bkbg-terminal, .bkbg-terminal-window').forEach(observe); |
| 123 |
}()); |
| 124 |
|