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

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

364 lines 16.7 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 var KM_PER_MILE = 1.60934;
26
27 function pad2(n) { return String(Math.floor(Math.max(0, n))).padStart(2, '0'); }
28
29 function secsToHMS(s) {
30 s = Math.round(Math.max(0, s));
31 var h = Math.floor(s / 3600);
32 var m = Math.floor((s % 3600) / 60);
33 var sec = s % 60;
34 return (h > 0 ? h + ':' + pad2(m) : m) + ':' + pad2(sec);
35 }
36
37 function secsToPace(s) {
38 s = Math.max(0, s);
39 var m = Math.floor(s / 60);
40 var sec = Math.round(s % 60);
41 if (sec === 60) { m++; sec = 0; }
42 return m + ':' + pad2(sec);
43 }
44
45 function paceToSecs(str) {
46 str = String(str || '').trim();
47 var parts = str.split(':');
48 if (parts.length >= 2) return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
49 return parseFloat(str) * 60 || 0;
50 }
51
52 function timeToSecs(str) {
53 str = String(str || '').trim();
54 var parts = str.split(':');
55 if (parts.length === 3) return parseInt(parts[0],10)*3600 + parseInt(parts[1],10)*60 + parseInt(parts[2],10);
56 if (parts.length === 2) return parseInt(parts[0],10)*60 + parseInt(parts[1],10);
57 return parseFloat(str) * 60 || 0;
58 }
59
60 function initPaceCalc(app) {
61 var opts = {};
62 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {}
63
64 var accent = opts.accentColor || '#6c3fb5';
65 var cardBg = opts.cardBg || '#ffffff';
66 var resultBg = opts.resultBg || '#f5f3ff';
67 var resultBorder= opts.resultBorder || '#ede9fe';
68 var resultColor = opts.resultColor || '#6c3fb5';
69 var presetBg = opts.presetBg || '#f3f4f6';
70 var presetColor = opts.presetColor || '#374151';
71 var convBg = opts.convBg || '#f9fafb';
72 var labelColor = opts.labelColor || '#374151';
73 var titleColor = opts.titleColor || '#1f2937';
74 var subtitleColor = opts.subtitleColor || '#6b7280';
75 var sectionBg = opts.sectionBg || '';
76 var maxWidth = opts.maxWidth || 560;
77 var cardRadius = opts.cardRadius || 16;
78 var inputRadius = opts.inputRadius || 8;
79 var titleSize = opts.titleSize || 28;
80 var resultSize = opts.resultSize || 52;
81 var padTop = opts.paddingTop || 60;
82 var padBot = opts.paddingBottom || 60;
83 var showTitle = opts.showTitle !== false;
84 var showSub = opts.showSubtitle !== false;
85 var showPresets = opts.showPresets !== false;
86 var showSpeedConv = opts.showSpeedConv !== false;
87 var defMode = opts.defaultMode || 'find_pace';
88 var defUnit = opts.defaultUnit || 'km';
89 var presets = opts.presets || [{ label:'5K', dist:5 }, { label:'10K', dist:10 }, { label:'Half', dist:21.0975 }, { label:'Marathon', dist:42.195 }];
90
91 var mode = defMode;
92 var unit = defUnit;
93 var distVal = '10';
94 var timeVal = '1:00:00';
95 var paceVal = '6:00';
96
97 // Build DOM
98 app.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
99 if (sectionBg) app.style.background = sectionBg;
100 app.style.paddingTop = padTop + 'px';
101 app.style.paddingBottom = padBot + 'px';
102
103 typoCssVarsForEl(app, opts.titleTypo, '--bkbg-pace-tt-');
104 typoCssVarsForEl(app, opts.resultTypo, '--bkbg-pace-rs-');
105
106 var card = document.createElement('div');
107 card.className = 'bkbg-pace-card';
108 card.style.cssText = 'background:' + cardBg + ';border-radius:' + cardRadius + 'px;padding:36px 32px;max-width:' + maxWidth + 'px;margin:0 auto;';
109 app.appendChild(card);
110
111 // Title
112 if (showTitle && opts.title) {
113 var titleEl = document.createElement('div');
114 titleEl.className = 'bkbg-pace-title';
115 titleEl.style.color = titleColor;
116 titleEl.style.marginBottom = '6px';
117 titleEl.textContent = opts.title;
118 card.appendChild(titleEl);
119 }
120 if (showSub && opts.subtitle) {
121 var subEl = document.createElement('div');
122 subEl.style.cssText = 'font-size:15px;color:' + subtitleColor + ';margin-bottom:20px;';
123 subEl.textContent = opts.subtitle;
124 card.appendChild(subEl);
125 }
126
127 var modes = [
128 { key: 'find_pace', label: 'Find Pace' },
129 { key: 'find_time', label: 'Find Time' },
130 { key: 'find_dist', label: 'Find Distance' }
131 ];
132 var units = [{ key: 'km', label: 'Kilometers' }, { key: 'mi', label: 'Miles' }];
133
134 // Mode tabs
135 var modeTabWrap = document.createElement('div');
136 modeTabWrap.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;';
137 card.appendChild(modeTabWrap);
138
139 var modeTabBtns = {};
140 modes.forEach(function(m) {
141 var btn = document.createElement('button');
142 btn.className = 'bkbg-pace-tab-btn';
143 btn.textContent = m.label;
144 btn.dataset.key = m.key;
145 modeTabBtns[m.key] = btn;
146 btn.addEventListener('click', function() { mode = m.key; refreshMode(); refreshResult(); });
147 modeTabWrap.appendChild(btn);
148 });
149
150 // Unit tabs
151 var unitTabWrap = document.createElement('div');
152 unitTabWrap.style.cssText = 'display:flex;gap:8px;margin-bottom:18px;';
153 card.appendChild(unitTabWrap);
154
155 var unitTabBtns = {};
156 units.forEach(function(u) {
157 var btn = document.createElement('button');
158 btn.className = 'bkbg-pace-tab-btn';
159 btn.textContent = u.label;
160 btn.dataset.key = u.key;
161 unitTabBtns[u.key] = btn;
162 btn.addEventListener('click', function() { unit = u.key; refreshUnit(); refreshResult(); });
163 unitTabWrap.appendChild(btn);
164 });
165
166 // Presets
167 var presetWrap = document.createElement('div');
168 presetWrap.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px;';
169 card.appendChild(presetWrap);
170
171 function buildPresets() {
172 presetWrap.innerHTML = '';
173 if (!showPresets || mode === 'find_dist') { presetWrap.style.display = 'none'; return; }
174 presetWrap.style.display = 'flex';
175 presets.forEach(function(p) {
176 var d = unit === 'km' ? p.dist : p.dist / KM_PER_MILE;
177 var btn = document.createElement('button');
178 btn.className = 'bkbg-pace-preset-btn';
179 btn.style.background = presetBg;
180 btn.style.color = presetColor;
181 btn.textContent = p.label;
182 btn.addEventListener('click', function() {
183 distVal = String(parseFloat(d.toFixed(3)));
184 distInput.value = distVal;
185 refreshResult();
186 });
187 presetWrap.appendChild(btn);
188 });
189 }
190
191 // Input grid
192 var inputGrid = document.createElement('div');
193 inputGrid.className = 'bkbg-pace-input-grid';
194 card.appendChild(inputGrid);
195
196 function makeInputRow(label, hint, initVal, onChange) {
197 var wrap = document.createElement('div');
198 wrap.className = 'bkbg-pace-input-row';
199 var lbl = document.createElement('label');
200 lbl.className = 'bkbg-pace-label';
201 lbl.style.color = labelColor;
202 var lspan = document.createElement('span'); lspan.textContent = label;
203 var hspan = document.createElement('span'); hspan.className = 'bkbg-pace-label-hint'; hspan.textContent = hint;
204 lbl.appendChild(lspan); lbl.appendChild(hspan);
205 var inp = document.createElement('input');
206 inp.className = 'bkbg-pace-input';
207 inp.type = 'text';
208 inp.value = initVal;
209 inp.style.borderRadius = inputRadius + 'px';
210 inp.addEventListener('input', function() { onChange(inp.value); refreshResult(); });
211 wrap.appendChild(lbl); wrap.appendChild(inp);
212 return { el: wrap, inp: inp, hspan: hspan, lspan: lspan };
213 }
214
215 var distRow = makeInputRow('Distance', unit === 'km' ? 'km' : 'mi', distVal, function(v) { distVal = v; });
216 var timeRow = makeInputRow('Time', 'H:MM:SS', timeVal, function(v) { timeVal = v; });
217 var paceRow = makeInputRow('Pace', 'M:SS/' + (unit==='km'?'km':'mi'), paceVal, function(v) { paceVal = v; });
218
219 var distInput = distRow.inp;
220 var timeInput = timeRow.inp;
221 var paceInput = paceRow.inp;
222
223 inputGrid.appendChild(distRow.el);
224 inputGrid.appendChild(timeRow.el);
225 inputGrid.appendChild(paceRow.el);
226
227 // Result area
228 var resultBox = document.createElement('div');
229 resultBox.className = 'bkbg-pace-result';
230 resultBox.style.cssText = 'background:' + resultBg + ';border:2px solid ' + resultBorder + ';border-radius:' + cardRadius + 'px;padding:24px 28px;';
231 card.appendChild(resultBox);
232
233 // Speed conversion
234 var convBox = document.createElement('div');
235 convBox.className = 'bkbg-pace-conv';
236 convBox.style.background = convBg;
237 card.appendChild(convBox);
238
239 function refreshMode() {
240 Object.keys(modeTabBtns).forEach(function(k) {
241 var btn = modeTabBtns[k];
242 var active = k === mode;
243 btn.style.background = active ? accent : 'transparent';
244 btn.style.color = active ? '#fff' : accent;
245 btn.style.border = '2px solid ' + (active ? accent : '#e5e7eb');
246 });
247 distRow.el.style.display = 'block';
248 timeRow.el.style.display = (mode === 'find_dist' || mode === 'find_pace') ? 'block' : 'block';
249 paceRow.el.style.display = 'block';
250
251 // update grid: for find_dist remove dist, show time+pace
252 if (mode === 'find_dist') {
253 distRow.el.style.display = 'none';
254 timeRow.el.style.display = 'block';
255 paceRow.el.style.display = 'block';
256 timeRow.hspan.textContent = 'H:MM:SS';
257 } else if (mode === 'find_pace') {
258 distRow.el.style.display = 'block';
259 timeRow.el.style.display = 'block';
260 paceRow.el.style.display = 'none';
261 distRow.hspan.textContent = unit === 'km' ? 'km' : 'mi';
262 timeRow.hspan.textContent = 'H:MM:SS';
263 } else { // find_time
264 distRow.el.style.display = 'block';
265 timeRow.el.style.display = 'none';
266 paceRow.el.style.display = 'block';
267 distRow.hspan.textContent = unit === 'km' ? 'km' : 'mi';
268 paceRow.hspan.textContent = 'M:SS/' + (unit === 'km' ? 'km' : 'mi');
269 }
270 buildPresets();
271 }
272
273 function refreshUnit() {
274 Object.keys(unitTabBtns).forEach(function(k) {
275 var btn = unitTabBtns[k];
276 var active = k === unit;
277 btn.style.background = active ? accent : 'transparent';
278 btn.style.color = active ? '#fff' : accent;
279 btn.style.border = '2px solid ' + (active ? accent : '#e5e7eb');
280 });
281 distRow.hspan.textContent = unit === 'km' ? 'km' : 'mi';
282 paceRow.hspan.textContent = 'M:SS/' + (unit === 'km' ? 'km' : 'mi');
283 buildPresets();
284 }
285
286 function refreshResult() {
287 var distN = parseFloat(distVal) || 0;
288 var timeSec = timeToSecs(timeVal);
289 var paceSec = paceToSecs(paceVal);
290 var pu = unit === 'km' ? 'km' : 'mi';
291
292 var labelTxt, mainTxt, subTxt = '', extraTxt = '';
293 var speedKmh, speedMph, speedMs;
294
295 if (mode === 'find_pace') {
296 if (distN <= 0 || timeSec <= 0) { resultBox.innerHTML = '<div style="color:' + labelColor + '">Enter distance and time to calculate</div>'; convBox.innerHTML = ''; return; }
297 var ps = timeSec / distN;
298 speedKmh = 3600 / (unit === 'km' ? ps : ps * KM_PER_MILE);
299 labelTxt = 'Your Pace'; mainTxt = secsToPace(ps); subTxt = '/' + pu;
300 } else if (mode === 'find_time') {
301 if (distN <= 0 || paceSec <= 0) { resultBox.innerHTML = '<div style="color:' + labelColor + '">Enter distance and pace to calculate</div>'; convBox.innerHTML = ''; return; }
302 var ts = distN * paceSec;
303 speedKmh = 3600 / (unit === 'km' ? paceSec : paceSec * KM_PER_MILE);
304 labelTxt = 'Finish Time'; mainTxt = secsToHMS(ts);
305 } else {
306 // find_dist
307 if (timeSec <= 0 || paceSec <= 0) { resultBox.innerHTML = '<div style="color:' + labelColor + '">Enter time and pace to calculate</div>'; convBox.innerHTML = ''; return; }
308 var d = timeSec / paceSec;
309 speedKmh = 3600 / (unit === 'km' ? paceSec : paceSec * KM_PER_MILE);
310 labelTxt = 'Distance Covered'; mainTxt = d.toFixed(2); subTxt = pu;
311 }
312
313 resultBox.innerHTML = '';
314 var lbl = document.createElement('div');
315 lbl.style.cssText = 'font-size:13px;font-weight:600;color:' + labelColor + ';margin-bottom:6px;';
316 lbl.textContent = labelTxt;
317 resultBox.appendChild(lbl);
318 var mainRow = document.createElement('div');
319 mainRow.style.cssText = 'display:flex;align-items:baseline;justify-content:center;gap:4px;';
320 var mainSpan = document.createElement('span');
321 mainSpan.className = 'bkbg-pace-result-value';
322 mainSpan.style.color = resultColor;
323 mainSpan.textContent = mainTxt;
324 mainRow.appendChild(mainSpan);
325 if (subTxt) {
326 var unitSpan = document.createElement('span');
327 unitSpan.style.cssText = 'font-size:22px;font-weight:600;color:' + labelColor + ';';
328 unitSpan.textContent = ' ' + subTxt;
329 mainRow.appendChild(unitSpan);
330 }
331 resultBox.appendChild(mainRow);
332
333 // Speed conversions
334 if (showSpeedConv && speedKmh) {
335 speedMph = speedKmh / KM_PER_MILE;
336 speedMs = speedKmh / 3.6;
337 convBox.innerHTML = '';
338 convBox.style.display = 'flex';
339 [
340 { val: speedKmh.toFixed(2), lbl: 'km/h' },
341 { val: speedMph.toFixed(2), lbl: 'mph' },
342 { val: speedMs.toFixed(2), lbl: 'm/s' }
343 ].forEach(function(item) {
344 var div = document.createElement('div');
345 div.className = 'bkbg-pace-conv-item';
346 div.style.color = labelColor;
347 div.innerHTML = '<span class="bkbg-pace-conv-val">' + item.val + '</span><span class="bkbg-pace-conv-lbl">' + item.lbl + '</span>';
348 convBox.appendChild(div);
349 });
350 } else {
351 convBox.innerHTML = '';
352 convBox.style.display = 'none';
353 }
354 }
355
356 // Init
357 refreshMode();
358 refreshUnit();
359 refreshResult();
360 }
361
362 document.querySelectorAll('.bkbg-pace-app').forEach(initPaceCalc);
363 })();
364