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 / stopwatch / frontend.js

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

216 lines 8.8 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 _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 decoration:'text-decoration', transform:'text-transform',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 Object.keys(_typoKeys).forEach(function (k) {
15 var v = obj[k];
16 if (v === undefined || v === '' || v === null) return;
17 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
18 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
19 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
20 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
21 el.style.setProperty(prefix + _typoKeys[k], String(v));
22 });
23 }
24
25 function buildBlock(root) {
26 var o;
27 try { o = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
28
29 // CSS vars
30 root.style.setProperty('--sw-accent', o.accentColor || '#6366f1');
31 root.style.setProperty('--sw-clock-bg', o.clockBg || '#1e1b4b');
32 root.style.setProperty('--sw-clock-color', o.clockColor || '#e0e7ff');
33 root.style.setProperty('--sw-title-color', o.titleColor || '#111827');
34 root.style.setProperty('--sw-subtitle-color',o.subtitleColor || '#6b7280');
35 root.style.setProperty('--sw-label-color', o.labelColor || '#374151');
36 root.style.setProperty('--sw-card-bg', o.cardBg || '#ffffff');
37 root.style.setProperty('--sw-max-width', (o.contentMaxWidth||560) + 'px');
38 root.style.setProperty('--sw-title-size', (o.titleFontSize ||26) + 'px');
39 root.style.setProperty('--sw-clock-size', (o.clockFontSize ||64) + 'px');
40
41 // Typography vars
42 typoCssVarsForEl(root, o.titleTypo, '--bksw-tt-');
43 typoCssVarsForEl(root, o.subtitleTypo, '--bksw-st-');
44
45 // State
46 var elapsed = 0; // ms total elapsed
47 var lapStart = 0; // ms at last lap press
48 var running = false;
49 var rafId = null;
50 var startedAt = 0; // performance.now() snapshot
51 var laps = [];
52
53 var maxLaps = o.maxLaps || 20;
54 var showMs = o.showMilliseconds !== false;
55
56 // Build DOM
57 var wrap = document.createElement('div');
58 wrap.className = 'bkbg-sw-wrap';
59 wrap.style.background = o.sectionBg || '#f5f3ff';
60 root.appendChild(wrap);
61
62 if (o.showTitle && o.title) {
63 var titleEl = document.createElement('h3');
64 titleEl.className = 'bkbg-sw-title';
65 titleEl.textContent = o.title;
66 wrap.appendChild(titleEl);
67 }
68 if (o.showSubtitle && o.subtitle) {
69 var subEl = document.createElement('p');
70 subEl.className = 'bkbg-sw-subtitle';
71 subEl.textContent = o.subtitle;
72 wrap.appendChild(subEl);
73 }
74
75 // Clock
76 var clockEl = document.createElement('div');
77 clockEl.className = 'bkbg-sw-clock';
78 wrap.appendChild(clockEl);
79
80 // Buttons
81 var btnRow = document.createElement('div');
82 btnRow.className = 'bkbg-sw-btns';
83 wrap.appendChild(btnRow);
84
85 function makeBtn(label, bg, handler) {
86 var btn = document.createElement('button');
87 btn.className = 'bkbg-sw-btn';
88 btn.textContent = label;
89 btn.style.background = bg;
90 btn.addEventListener('click', handler);
91 btnRow.appendChild(btn);
92 return btn;
93 }
94
95 var startBtn = makeBtn('▶ Start', o.startColor || '#22c55e', onStart);
96 var lapBtn = o.showLaps ? makeBtn('⏱ Lap', o.lapColor || '#f59e0b', onLap) : null;
97 var resetBtn = makeBtn('↺ Reset', o.resetColor || '#6b7280', onReset);
98
99 // Laps section
100 var lapSection = null, lapHead = null, lapBody = null;
101 if (o.showLaps) {
102 lapSection = document.createElement('div');
103 lapSection.className = 'bkbg-sw-laps';
104
105 lapHead = document.createElement('div');
106 lapHead.className = 'bkbg-sw-laps-head';
107 lapHead.innerHTML = '<span style="width:32px">#</span><span style="flex:1">Split</span><span style="flex:1;text-align:right">Total</span>';
108 lapSection.appendChild(lapHead);
109
110 lapBody = document.createElement('div');
111 lapSection.appendChild(lapBody);
112 wrap.appendChild(lapSection);
113 }
114
115 // ── Format ms as MM:SS.mm or MM:SS ──
116 function fmtTime(ms, withMs) {
117 var totalS = Math.floor(ms / 1000);
118 var minutes = Math.floor(totalS / 60);
119 var seconds = totalS % 60;
120 var centis = Math.floor((ms % 1000) / 10);
121 var mm = String(minutes).padStart(2, '0');
122 var ss = String(seconds).padStart(2, '0');
123 if (withMs !== false) {
124 var cs = String(centis).padStart(2, '0');
125 return mm + ':' + ss + '.' + cs;
126 }
127 return mm + ':' + ss;
128 }
129
130 // ── Render clock ──
131 function renderClock() {
132 clockEl.textContent = fmtTime(elapsed, showMs);
133 }
134
135 // ── RAF loop ──
136 function tick() {
137 elapsed = (o._elapsed || 0) + (performance.now() - startedAt);
138 renderClock();
139 rafId = requestAnimationFrame(tick);
140 }
141
142 // ── Start / Pause toggle ──
143 function onStart() {
144 if (!running) {
145 running = true;
146 o._elapsed = elapsed;
147 startedAt = performance.now();
148 rafId = requestAnimationFrame(tick);
149 startBtn.textContent = '⏸ Pause';
150 startBtn.style.background = o.stopColor || '#ef4444';
151 if (lapBtn) lapBtn.disabled = false;
152 } else {
153 running = false;
154 cancelAnimationFrame(rafId);
155 o._elapsed = elapsed;
156 startBtn.textContent = '▶ Resume';
157 startBtn.style.background = o.startColor || '#22c55e';
158 }
159 }
160
161 // ── Lap ──
162 function onLap() {
163 if (!running) return;
164 if (laps.length >= maxLaps) return;
165 var split = elapsed - lapStart;
166 lapStart = elapsed;
167 laps.unshift({ num: laps.length + 1, split: split, total: elapsed });
168 renderLaps();
169 }
170
171 // ── Reset ──
172 function onReset() {
173 running = false;
174 cancelAnimationFrame(rafId);
175 elapsed = 0;
176 lapStart = 0;
177 o._elapsed = 0;
178 laps = [];
179 startBtn.textContent = '▶ Start';
180 startBtn.style.background = o.startColor || '#22c55e';
181 if (lapBtn) lapBtn.disabled = true;
182 renderClock();
183 if (lapBody) renderLaps();
184 }
185
186 // ── Render laps list ──
187 function renderLaps() {
188 if (!lapBody) return;
189 lapBody.innerHTML = '';
190 if (laps.length === 0) {
191 var empty = document.createElement('div');
192 empty.className = 'bkbg-sw-laps-empty';
193 empty.textContent = 'No laps recorded yet';
194 lapBody.appendChild(empty);
195 return;
196 }
197 laps.forEach(function (lap) {
198 var row = document.createElement('div');
199 row.className = 'bkbg-sw-lap-row';
200 row.innerHTML =
201 '<span class="bkbg-sw-lap-num">' + lap.num + '</span>' +
202 '<span class="bkbg-sw-lap-split">' + fmtTime(lap.split, showMs) + '</span>' +
203 '<span class="bkbg-sw-lap-total">' + fmtTime(lap.total, showMs) + '</span>';
204 lapBody.appendChild(row);
205 });
206 }
207
208 // Init
209 if (lapBtn) lapBtn.disabled = true;
210 renderClock();
211 if (lapBody) renderLaps();
212 }
213
214 document.querySelectorAll('.bkbg-sw-app').forEach(buildBlock);
215 })();
216