PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / typing-speed-test / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/typing-speed-test/frontend.js

384 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var TEXTS = {
5 easy: [
6 'The sun is bright and the sky is blue. Birds fly over the trees and flowers bloom in the field. A gentle breeze blows through the leaves.',
7 'She walked to the store to buy some fruit. The apples were red and the bananas were yellow. She also picked up some bread and milk.',
8 'The cat sat on the mat and looked at the dog. The dog wagged its tail and ran around the yard. They were good friends who loved to play.'
9 ],
10 medium: [
11 'The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. How vexingly quick daft zebras jump in the fog.',
12 'Technology is rapidly changing the way we live and work. Smartphones and computers have become essential tools for communication and productivity in modern society.',
13 'Scientists have discovered that regular exercise improves both physical and mental health. Even a short daily walk can significantly reduce stress and boost mood levels.'
14 ],
15 hard: [
16 'Cryptography encompasses the techniques used to secure communication in the presence of adversarial behavior. Modern cryptographic systems rely on mathematical problems believed to be computationally intractable.',
17 'Quantum mechanics describes the behavior of matter and energy at the subatomic scale, where classical physics breaks down entirely. Particles can exhibit wave-particle duality and exist in superposition states.',
18 'The Byzantine Generals Problem illustrates the challenge of achieving consensus in distributed systems where components may fail or act maliciously. Practical solutions form the basis of blockchain consensus algorithms.'
19 ]
20 };
21
22 function getRank(wpm) {
23 if (wpm >= 100) return { label: 'Stenographer', emoji: '🏆', color: '#f59e0b' };
24 if (wpm >= 80) return { label: 'Professional', emoji: '🥇', color: '#6366f1' };
25 if (wpm >= 60) return { label: 'Advanced', emoji: '🥈', color: '#0ea5e9' };
26 if (wpm >= 40) return { label: 'Average', emoji: '🥉', color: '#22c55e' };
27 if (wpm >= 20) return { label: 'Beginner', emoji: '📝', color: '#f97316' };
28 return { label: 'Novice', emoji: '🌱', color: '#ef4444' };
29 }
30
31 function randomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
32
33 function initBlock(root) {
34 var opts;
35 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
36
37 var a = opts;
38 var totalSeconds = parseInt(a.duration) || 60;
39 var difficulty = a.defaultDifficulty || 'medium';
40 var targetText = '';
41 var typed = '';
42 var started = false;
43 var finished = false;
44 var timerInterval = null;
45 var timeLeft = totalSeconds;
46 var errors = 0;
47 var totalTyped = 0;
48
49 root.innerHTML = '';
50
51 var wrap = document.createElement('div');
52 wrap.className = 'bkbg-tst-wrap';
53 wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
54 root.appendChild(wrap);
55
56 if (a.showTitle) {
57 var h = document.createElement('div');
58 h.className = 'bkbg-tst-title';
59 h.style.color = a.titleColor;
60 h.textContent = a.title;
61 wrap.appendChild(h);
62 }
63 if (a.showSubtitle) {
64 var s = document.createElement('div');
65 s.className = 'bkbg-tst-subtitle';
66 s.style.color = a.subtitleColor;
67 s.textContent = a.subtitle;
68 wrap.appendChild(s);
69 }
70
71 /* Stats bar */
72 var statsBar = document.createElement('div');
73 statsBar.className = 'bkbg-tst-stats';
74 statsBar.style.background = a.cardBg;
75 wrap.appendChild(statsBar);
76
77 function makeStat(id, label) {
78 var box = document.createElement('div');
79 box.className = 'bkbg-tst-stat';
80 var val = document.createElement('div');
81 val.className = 'bkbg-tst-stat-val';
82 val.id = 'bkbg-tst-' + id + '-' + Math.random().toString(36).slice(2);
83 val.style.color = a.accentColor;
84 val.textContent = id === 'time' ? totalSeconds + 's' : '';
85 var lbl = document.createElement('div');
86 lbl.className = 'bkbg-tst-stat-lbl';
87 lbl.style.color = a.subtitleColor;
88 lbl.textContent = label;
89 box.appendChild(val);
90 box.appendChild(lbl);
91 statsBar.appendChild(box);
92 return val;
93 }
94
95 var timeEl = makeStat('time', 'Time');
96 var wpmEl = a.showLiveWpm ? makeStat('wpm', 'WPM') : null;
97 var accEl = a.showAccuracy ? makeStat('acc', 'Accuracy') : null;
98 var errEl = a.showErrors ? makeStat('err', 'Errors') : null;
99
100 /* Time bar */
101 var timeBarWrap = document.createElement('div');
102 timeBarWrap.className = 'bkbg-tst-time-bar-wrap';
103 wrap.appendChild(timeBarWrap);
104 var timeBar = document.createElement('div');
105 timeBar.className = 'bkbg-tst-time-bar';
106 timeBar.style.cssText = 'width:100%;background:' + a.accentColor + ';';
107 timeBarWrap.appendChild(timeBar);
108
109 /* Difficulty buttons */
110 var diffRow = null;
111 if (a.showDifficultyBtn) {
112 diffRow = document.createElement('div');
113 diffRow.className = 'bkbg-tst-diff-btns';
114 wrap.appendChild(diffRow);
115 ['easy', 'medium', 'hard'].forEach(function (d) {
116 var btn = document.createElement('button');
117 btn.className = 'bkbg-tst-diff-btn';
118 btn.textContent = d.charAt(0).toUpperCase() + d.slice(1);
119 setDiffStyle(btn, d === difficulty);
120 btn.addEventListener('click', function () {
121 if (started) return;
122 difficulty = d;
123 diffRow.querySelectorAll('.bkbg-tst-diff-btn').forEach(function (b, i) {
124 setDiffStyle(b, ['easy','medium','hard'][i] === difficulty);
125 });
126 resetTest();
127 });
128 diffRow.appendChild(btn);
129 });
130 }
131
132 function setDiffStyle(btn, active) {
133 btn.style.cssText = 'background:' + (active ? a.accentColor : '#e5e7eb') + ';color:' + (active ? '#fff' : '#374151') + ';';
134 }
135
136 /* Text box */
137 var textBox = document.createElement('div');
138 textBox.className = 'bkbg-tst-text-box';
139 textBox.style.cssText = 'background:' + a.textAreaBg + ';color:' + a.textAreaColor + ';';
140 wrap.appendChild(textBox);
141
142 /* Input */
143 var inputEl = document.createElement('textarea');
144 inputEl.className = 'bkbg-tst-input';
145 inputEl.placeholder = 'Click here and start typing to begin the test…';
146 inputEl.style.cssText = 'background:' + a.cardBg + ';color:' + a.labelColor + ';border-color:' + a.accentColor + ';';
147 inputEl.disabled = false;
148 wrap.appendChild(inputEl);
149
150 /* Actions */
151 var actionsRow = document.createElement('div');
152 actionsRow.className = 'bkbg-tst-actions';
153 wrap.appendChild(actionsRow);
154
155 var startBtn = document.createElement('button');
156 startBtn.className = 'bkbg-tst-btn';
157 startBtn.style.background = a.accentColor;
158 startBtn.textContent = '▶ Start Test';
159 actionsRow.appendChild(startBtn);
160
161 var resetBtn = document.createElement('button');
162 resetBtn.className = 'bkbg-tst-btn bkbg-tst-btn-outline';
163 resetBtn.style.cssText = 'color:' + a.accentColor + ';border-color:' + a.accentColor + ';display:none;';
164 resetBtn.textContent = '↺ Try Again';
165 actionsRow.appendChild(resetBtn);
166
167 /* Results */
168 var resultsEl = document.createElement('div');
169 resultsEl.className = 'bkbg-tst-results';
170 resultsEl.style.background = a.cardBg;
171 wrap.appendChild(resultsEl);
172
173 function renderTextBox() {
174 textBox.innerHTML = '';
175 for (var i = 0; i < targetText.length; i++) {
176 var span = document.createElement('span');
177 span.className = 'bkbg-tst-char';
178 span.textContent = targetText[i] === ' ' ? '\u00a0' : targetText[i];
179
180 if (i < typed.length) {
181 if (typed[i] === targetText[i]) {
182 span.className += ' bkbg-tst-char-correct';
183 span.style.color = a.correctColor;
184 } else {
185 span.className += ' bkbg-tst-char-error';
186 span.style.cssText = 'color:' + a.errorColor + ';background:rgba(239,68,68,0.15);';
187 }
188 } else if (i === typed.length) {
189 span.className += ' bkbg-tst-char-cursor';
190 span.style.color = a.textAreaColor;
191 span.querySelector = null;
192 var cursorStyle = document.createElement('style');
193 var uid = 'cur' + i;
194 span.style.position = 'relative';
195 var cursor = document.createElement('span');
196 cursor.style.cssText = 'position:absolute;left:0;top:2px;bottom:2px;width:2px;background:' + a.accentColor + ';border-radius:1px;animation:bkbg-tst-blink 1s step-end infinite;';
197 span.insertBefore(cursor, span.firstChild);
198 } else {
199 span.style.color = a.textAreaColor;
200 }
201 textBox.appendChild(span);
202 }
203 }
204
205 function calcWpm() {
206 var correctChars = 0;
207 for (var i = 0; i < typed.length && i < targetText.length; i++) {
208 if (typed[i] === targetText[i]) correctChars++;
209 }
210 var elapsed = totalSeconds - timeLeft;
211 if (elapsed === 0) return 0;
212 return Math.round((correctChars / 5) / (elapsed / 60));
213 }
214
215 function calcAccuracy() {
216 if (totalTyped === 0) return 100;
217 var correct = totalTyped - errors;
218 return Math.round((correct / totalTyped) * 100);
219 }
220
221 function updateStats() {
222 if (wpmEl) wpmEl.textContent = calcWpm();
223 if (accEl) accEl.textContent = calcAccuracy() + '%';
224 if (errEl) errEl.textContent = errors;
225 }
226
227 function startTimer() {
228 timerInterval = setInterval(function () {
229 timeLeft--;
230 var pct = (timeLeft / totalSeconds) * 100;
231 timeBar.style.width = pct + '%';
232 timeEl.textContent = timeLeft + 's';
233 if (timeLeft <= 10) timeEl.style.color = a.errorColor;
234 updateStats();
235 if (timeLeft <= 0) {
236 clearInterval(timerInterval);
237 endTest();
238 }
239 }, 1000);
240 }
241
242 function endTest() {
243 finished = true;
244 started = false;
245 inputEl.disabled = true;
246 startBtn.style.display = 'none';
247 resetBtn.style.display = '';
248 showResults();
249 }
250
251 function showResults() {
252 var wpm = calcWpm();
253 var acc = calcAccuracy();
254 var rank = getRank(wpm);
255
256 resultsEl.innerHTML = '';
257 resultsEl.classList.add('bkbg-tst-show');
258
259 var icon = document.createElement('div');
260 icon.className = 'bkbg-tst-results-icon';
261 icon.textContent = rank.emoji;
262 resultsEl.appendChild(icon);
263
264 var htitle = document.createElement('h3');
265 htitle.style.cssText = 'margin:0 0 6px;color:' + a.titleColor + ';font-size:20px;';
266 htitle.textContent = 'Test Complete!';
267 resultsEl.appendChild(htitle);
268
269 var grid = document.createElement('div');
270 grid.className = 'bkbg-tst-results-grid';
271
272 [
273 { val: wpm, lbl: 'WPM', color: a.accentColor },
274 { val: acc + '%', lbl: 'Accuracy', color: a.correctColor },
275 { val: errors, lbl: 'Errors', color: errors > 5 ? a.errorColor : a.correctColor }
276 ].forEach(function (item) {
277 var box = document.createElement('div');
278 box.className = 'bkbg-tst-result-item';
279 box.style.background = a.sectionBg;
280 var v = document.createElement('div');
281 v.className = 'bkbg-tst-result-val';
282 v.style.color = item.color;
283 v.textContent = item.val;
284 var l = document.createElement('div');
285 l.className = 'bkbg-tst-result-lbl';
286 l.style.color = a.subtitleColor;
287 l.textContent = item.lbl;
288 box.appendChild(v);
289 box.appendChild(l);
290 grid.appendChild(box);
291 });
292
293 resultsEl.appendChild(grid);
294
295 var rankBadge = document.createElement('div');
296 rankBadge.className = 'bkbg-tst-rank';
297 rankBadge.style.background = rank.color;
298 rankBadge.textContent = 'Rank: ' + rank.label;
299 resultsEl.appendChild(rankBadge);
300 }
301
302 function resetTest() {
303 clearInterval(timerInterval);
304 started = false;
305 finished = false;
306 timeLeft = totalSeconds;
307 typed = '';
308 errors = 0;
309 totalTyped = 0;
310 inputEl.value = '';
311 inputEl.disabled = false;
312 startBtn.style.display = '';
313 startBtn.textContent = '▶ Start Test';
314 resetBtn.style.display = 'none';
315 timeEl.textContent = totalSeconds + 's';
316 timeEl.style.color = a.accentColor;
317 if (wpmEl) wpmEl.textContent = '';
318 if (accEl) accEl.textContent = '';
319 if (errEl) errEl.textContent = '';
320 timeBar.style.transition = 'none';
321 timeBar.style.width = '100%';
322 setTimeout(function () { timeBar.style.transition = 'width 1s linear'; }, 50);
323 resultsEl.classList.remove('bkbg-tst-show');
324 targetText = randomItem(TEXTS[difficulty] || TEXTS.medium);
325 renderTextBox();
326 }
327
328 inputEl.addEventListener('focus', function () {
329 if (!started && !finished) {
330 started = true;
331 startBtn.textContent = '■ Stop';
332 startTimer();
333 }
334 });
335
336 inputEl.addEventListener('input', function () {
337 if (!started || finished) return;
338 var newVal = inputEl.value;
339 if (newVal.length > targetText.length) {
340 inputEl.value = newVal.slice(0, targetText.length);
341 return;
342 }
343 var delta = newVal.length - typed.length;
344 if (delta > 0) {
345 totalTyped += delta;
346 var newChar = newVal[newVal.length - 1];
347 var expected = targetText[newVal.length - 1];
348 if (newChar !== expected) errors++;
349 }
350 typed = newVal;
351 renderTextBox();
352 updateStats();
353
354 if (typed === targetText) {
355 clearInterval(timerInterval);
356 endTest();
357 }
358 });
359
360 startBtn.addEventListener('click', function () {
361 if (started) {
362 clearInterval(timerInterval);
363 endTest();
364 } else {
365 inputEl.focus();
366 }
367 });
368
369 resetBtn.addEventListener('click', function () { resetTest(); });
370
371 resetTest();
372 }
373
374 function init() {
375 document.querySelectorAll('.bkbg-tst-app').forEach(initBlock);
376 }
377
378 if (document.readyState === 'loading') {
379 document.addEventListener('DOMContentLoaded', init);
380 } else {
381 init();
382 }
383 })();
384