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

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

389 lines 17.5 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 = [['family','font-family'],['weight','font-weight'],['style','font-style'],['decoration','text-decoration'],['transform','text-transform'],['sizeDesktop','font-size-d'],['sizeTablet','font-size-t'],['sizeMobile','font-size-m'],['sizeUnit','*unit'],['lineHeightDesktop','line-height-d'],['lineHeightTablet','line-height-t'],['lineHeightMobile','line-height-m'],['letterSpacingDesktop','letter-spacing-d'],['letterSpacingTablet','letter-spacing-t'],['letterSpacingMobile','letter-spacing-m'],['wordSpacingDesktop','word-spacing-d'],['wordSpacingTablet','word-spacing-t'],['wordSpacingMobile','word-spacing-m']];
5 function typoCssVarsForEl(el, obj, prefix) {
6 if (!obj || typeof obj !== 'object') return;
7 var unit = obj.sizeUnit || 'px';
8 for (var i = 0; i < _typoKeys.length; i++) {
9 var k = _typoKeys[i][0], p = _typoKeys[i][1];
10 if (p === '*unit' || obj[k] == null || obj[k] === '') continue;
11 var v = obj[k];
12 if (p === 'font-size-d' || p === 'font-size-t' || p === 'font-size-m') v = v + unit;
13 el.style.setProperty(prefix + p, '' + v);
14 }
15 }
16
17 // ── Unit conversion tables ──────────────────────────────────────────────
18 var DIST_TO_M = {
19 'm': 1,
20 'km': 1000,
21 'miles': 1609.344,
22 'feet': 0.3048,
23 'nautical miles': 1852
24 };
25
26 var TIME_TO_S = {
27 'seconds': 1,
28 'minutes': 60,
29 'hours': 3600,
30 'days': 86400
31 };
32
33 var MPS_TO_SPEED = {
34 'm/s': 1,
35 'km/h': 3.6,
36 'mph': 2.23694,
37 'knots': 1.94384
38 };
39
40 var SPEED_UNITS = ['km/h', 'mph', 'm/s', 'knots'];
41 var DIST_UNITS = ['km', 'miles', 'm', 'feet', 'nautical miles'];
42 var TIME_UNITS = ['seconds', 'minutes', 'hours', 'days'];
43
44 var REAL_WORLD = [
45 { label:'Walking pace', speed:'5 km/h', dist:'', time:'' },
46 { label:'City cycling', speed:'20 km/h', dist:'', time:'' },
47 { label:'Highway car', speed:'110 km/h', dist:'', time:'' },
48 { label:'Commercial aircraft', speed:'900 km/h', dist:'', time:'' },
49 { label:'Speed of sound', speed:'1235 km/h',dist:'', time:'' },
50 { label:'Earth orbit speed', speed:'28000 km/h',dist:'', time:'' }
51 ];
52
53 // ── Helper: format number neatly ────────────────────────────────────────
54 function fmt(n) {
55 if (!isFinite(n)) return '';
56 if (Math.abs(n) >= 1000) return n.toLocaleString(undefined, { maximumFractionDigits: 2 });
57 if (Math.abs(n) >= 1) return parseFloat(n.toFixed(4)).toString();
58 return parseFloat(n.toFixed(6)).toString();
59 }
60
61 // ── Helper: format seconds as human readable duration ──────────────────
62 function fmtDuration(secs) {
63 if (!isFinite(secs) || secs < 0) return '';
64 var d = Math.floor(secs / 86400);
65 var h = Math.floor((secs % 86400) / 3600);
66 var m = Math.floor((secs % 3600) / 60);
67 var s = secs % 60;
68 var parts = [];
69 if (d > 0) parts.push(d + 'd');
70 if (h > 0) parts.push(h + 'h');
71 if (m > 0) parts.push(m + 'min');
72 if (s > 0 || parts.length === 0) parts.push(parseFloat(s.toFixed(2)) + 's');
73 return parts.join(' ');
74 }
75
76 // ── Helper: create DOM element ──────────────────────────────────────────
77 function el(tag, attrs, children) {
78 var node = document.createElement(tag);
79 if (attrs) {
80 Object.keys(attrs).forEach(function (k) {
81 if (k === 'style') {
82 Object.assign(node.style, attrs[k]);
83 } else if (k === 'class') {
84 node.className = attrs[k];
85 } else {
86 node.setAttribute(k, attrs[k]);
87 }
88 });
89 }
90 if (children) {
91 if (typeof children === 'string') {
92 node.textContent = children;
93 } else if (Array.isArray(children)) {
94 children.forEach(function (c) { if (c) node.appendChild(c); });
95 } else {
96 node.appendChild(children);
97 }
98 }
99 return node;
100 }
101
102 function text(str) { return document.createTextNode(str); }
103
104 function makeSelect(options, selected) {
105 var sel = document.createElement('select');
106 sel.className = 'bkbg-spd-unit-select';
107 options.forEach(function (opt) {
108 var o = document.createElement('option');
109 o.value = opt; o.textContent = opt;
110 if (opt === selected) o.selected = true;
111 sel.appendChild(o);
112 });
113 return sel;
114 }
115
116 function makeInput(placeholder, value) {
117 var inp = document.createElement('input');
118 inp.type = 'number';
119 inp.className = 'bkbg-spd-input';
120 inp.placeholder = placeholder || 'Enter value…';
121 inp.min = '0';
122 inp.step = 'any';
123 if (value !== undefined && value !== '') inp.value = value;
124 return inp;
125 }
126
127 // ── Build block ─────────────────────────────────────────────────────────
128 function buildBlock(root) {
129 var o;
130 try { o = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
131
132 // CSS variables
133 root.style.setProperty('--spd-accent', o.accentColor || '#3b82f6');
134 root.style.setProperty('--spd-speed-color', o.speedColor || '#8b5cf6');
135 root.style.setProperty('--spd-dist-color', o.distColor || '#0ea5e9');
136 root.style.setProperty('--spd-time-color', o.timeColor || '#22c55e');
137 root.style.setProperty('--spd-title-color', o.titleColor || '#111827');
138 root.style.setProperty('--spd-subtitle-color',o.subtitleColor|| '#6b7280');
139 root.style.setProperty('--spd-label-color', o.labelColor || '#374151');
140 root.style.setProperty('--spd-max-width', (o.contentMaxWidth || 680) + 'px');
141 root.style.setProperty('--spd-card-bg', o.cardBg || '#ffffff');
142 root.style.setProperty('--spd-input-bg', o.inputBg || '#f8fafc');
143 root.style.setProperty('--spd-result-bg', o.resultBg || '#dbeafe');
144 typoCssVarsForEl(root, o.titleTypo, '--bkspd-tt-');
145 typoCssVarsForEl(root, o.subtitleTypo, '--bkspd-st-');
146
147 // State
148 var solveFor = o.defaultSolveFor || 'speed';
149 var speedUnit = o.defaultSpeedUnit || 'km/h';
150 var distUnit = o.defaultDistUnit || 'km';
151 var timeUnit = o.defaultTimeUnit || 'hours';
152
153 var FIELDS = {
154 speed: { label:'⚡ Speed', color:o.speedColor||'#8b5cf6', units:SPEED_UNITS, getUnit:function(){ return speedUnit; }, setUnit:function(u){ speedUnit=u; } },
155 dist: { label:'📏 Distance', color:o.distColor ||'#0ea5e9', units:DIST_UNITS, getUnit:function(){ return distUnit; }, setUnit:function(u){ distUnit=u; } },
156 time: { label:'⏱️ Time', color:o.timeColor ||'#22c55e', units:TIME_UNITS, getUnit:function(){ return timeUnit; }, setUnit:function(u){ timeUnit=u; } }
157 };
158
159 // Wrap
160 var wrap = el('div', { class:'bkbg-spd-wrap', style:{ background: o.sectionBg || '#eff6ff' } });
161 root.appendChild(wrap);
162
163 // Title + subtitle
164 if (o.title) {
165 var titleEl = el('h3', { class:'bkbg-spd-title' }, o.title);
166 wrap.appendChild(titleEl);
167 }
168 if (o.subtitle) {
169 var sub = el('p', { class:'bkbg-spd-subtitle' }, o.subtitle);
170 wrap.appendChild(sub);
171 }
172
173 // Solve-for tabs
174 wrap.appendChild(el('div', { class:'bkbg-spd-tab-label' }, 'Solve for…'));
175 var tabsRow = el('div', { class:'bkbg-spd-tabs' });
176 wrap.appendChild(tabsRow);
177
178 var tabBtns = {};
179 ['speed','dist','time'].forEach(function (key) {
180 var f = FIELDS[key];
181 var btn = el('button', { class:'bkbg-spd-tab' + (key === solveFor ? ' active' : '') }, f.label);
182 tabBtns[key] = btn;
183 tabsRow.appendChild(btn);
184 });
185
186 // Input fields
187 var inputsGrid = el('div', { class:'bkbg-spd-inputs' });
188 wrap.appendChild(inputsGrid);
189
190 var inputEls = {}; // key → input element
191 var selectEls = {}; // key → select element
192 var fieldDivs = {}; // key → field wrapper div
193
194 ['speed','dist','time'].forEach(function (key) {
195 var f = FIELDS[key];
196 var isRes = key === solveFor;
197
198 var fieldDiv = el('div', {
199 class: 'bkbg-spd-field' + (isRes ? ' is-result' : ''),
200 style: { background: isRes ? '' : (o.cardBg||'#fff') }
201 });
202 fieldDivs[key] = fieldDiv;
203
204 var lbl = el('div', { class:'bkbg-spd-field-label', style:{ color: f.color } },
205 f.label + (isRes ? ' (Result)' : ''));
206 fieldDiv.appendChild(lbl);
207
208 var inp = makeInput('Enter value…');
209 inp.disabled = isRes;
210 if (isRes) { inp.value = ''; inp.placeholder = ''; }
211 inputEls[key] = inp;
212 fieldDiv.appendChild(inp);
213
214 var sel = makeSelect(f.units, f.getUnit());
215 selectEls[key] = sel;
216 fieldDiv.appendChild(sel);
217
218 inputsGrid.appendChild(fieldDiv);
219 });
220
221 // Calculate button
222 var calcBtn = el('button', { class:'bkbg-spd-calc-btn' }, '⚡ Calculate');
223 wrap.appendChild(calcBtn);
224
225 // Result zone (conversions + formula)
226 var resultZone = el('div', {});
227 wrap.appendChild(resultZone);
228
229 // ── Core calculation ──
230 function solve() {
231 resultZone.innerHTML = '';
232
233 var speedVal = parseFloat(inputEls.speed.value);
234 var distVal = parseFloat(inputEls.dist.value);
235 var timeVal = parseFloat(inputEls.time.value);
236
237 // Convert inputs to SI base units
238 var speedMPS = speedVal * (1 / (MPS_TO_SPEED[speedUnit] || 1));
239 var distM = distVal * (DIST_TO_M[distUnit] || 1);
240 var timeS = timeVal * (TIME_TO_S[timeUnit] || 1);
241
242 var resultStr = '';
243 var resultUnit = '';
244
245 if (solveFor === 'speed') {
246 var mps = distM / timeS;
247 var out = mps * (MPS_TO_SPEED[speedUnit] || 1);
248 inputEls.speed.value = isFinite(out) ? fmt(out) : '';
249 inputEls.speed.placeholder = isFinite(out) ? '' : 'Need valid inputs';
250 resultStr = isFinite(out) ? fmt(out) + ' ' + speedUnit : '';
251 resultUnit = 'speed';
252
253 if (isFinite(mps) && o.showUnitConversions) {
254 appendConversions(resultZone, 'Speed', o.speedColor||'#8b5cf6', SPEED_UNITS, function(u){
255 return fmt(mps * (MPS_TO_SPEED[u]||1)) + ' ' + u;
256 });
257 }
258 } else if (solveFor === 'dist') {
259 var dm = speedMPS * timeS;
260 var outD = dm / (DIST_TO_M[distUnit] || 1);
261 inputEls.dist.value = isFinite(outD) ? fmt(outD) : '';
262 inputEls.dist.placeholder = isFinite(outD) ? '' : 'Need valid inputs';
263 resultStr = isFinite(outD) ? fmt(outD) + ' ' + distUnit : '';
264 resultUnit = 'dist';
265
266 if (isFinite(dm) && o.showUnitConversions) {
267 appendConversions(resultZone, 'Distance', o.distColor||'#0ea5e9', DIST_UNITS, function(u){
268 return fmt(dm / (DIST_TO_M[u]||1)) + ' ' + u;
269 });
270 }
271 } else {
272 var ts = distM / speedMPS;
273 var outT = ts / (TIME_TO_S[timeUnit] || 1);
274 inputEls.time.value = isFinite(outT) ? fmt(outT) : '';
275 inputEls.time.placeholder = isFinite(outT) ? '' : 'Need valid inputs';
276 resultStr = isFinite(ts) ? fmtDuration(ts) : '';
277 resultUnit = 'time';
278
279 if (isFinite(ts) && o.showUnitConversions) {
280 appendConversions(resultZone, 'Time', o.timeColor||'#22c55e', TIME_UNITS, function(u){
281 return fmt(ts / (TIME_TO_S[u]||1)) + ' ' + u;
282 });
283 }
284 }
285
286 if (o.showFormula) appendFormula(resultZone);
287 if (o.showExamples) appendExamples(resultZone);
288 }
289
290 function appendConversions(zone, heading, color, units, fmtFn) {
291 var conv = el('div', { class:'bkbg-spd-conversions', style:{ background: o.cardBg||'#fff' } });
292 var h = el('div', { class:'bkbg-spd-conv-heading' }, '🔁 ' + heading + ' in all units');
293 conv.appendChild(h);
294
295 var grid = el('div', { class:'bkbg-spd-conv-grid' });
296 units.forEach(function (u) {
297 var item = el('div', { class:'bkbg-spd-conv-item' });
298 var uLbl = el('div', { class:'bkbg-spd-conv-unit' }, u);
299 var uVal = el('div', { class:'bkbg-spd-conv-val', style:{ color: color } }, fmtFn(u));
300 item.appendChild(uLbl);
301 item.appendChild(uVal);
302 grid.appendChild(item);
303 });
304 conv.appendChild(grid);
305 zone.appendChild(conv);
306 }
307
308 function appendFormula(zone) {
309 var box = el('div', { class:'bkbg-spd-formula', style:{ background: o.cardBg||'#fff' } });
310 [
311 { label:'⚡ Speed = Distance ÷ Time', color: o.speedColor||'#8b5cf6' },
312 { label:'📏 Distance = Speed × Time', color: o.distColor ||'#0ea5e9' },
313 { label:'⏱️ Time = Distance ÷ Speed', color: o.timeColor ||'#22c55e' }
314 ].forEach(function (f) {
315 box.appendChild(el('span', { class:'bkbg-spd-formula-item', style:{ color: f.color } }, f.label));
316 });
317 zone.appendChild(box);
318 }
319
320 function appendExamples(zone) {
321 var box = el('div', { class:'bkbg-spd-examples', style:{ background: o.cardBg||'#fff' } });
322 box.appendChild(el('div', { class:'bkbg-spd-examples-heading' }, '🌍 Real-world speeds'));
323 REAL_WORLD.forEach(function (ex) {
324 var row = el('div', { class:'bkbg-spd-example-row' });
325 row.appendChild(text(ex.label));
326 row.appendChild(el('strong', { style:{ color: o.accentColor||'#3b82f6' } }, ex.speed));
327 box.appendChild(row);
328 });
329 zone.appendChild(box);
330 }
331
332 // ── Apply solve-for mode ──
333 function applySolveFor(key) {
334 solveFor = key;
335
336 // Update tab buttons
337 ['speed','dist','time'].forEach(function (k) {
338 tabBtns[k].className = 'bkbg-spd-tab' + (k === key ? ' active' : '');
339 });
340
341 // Update field classes + input disabled state + label
342 var f = FIELDS[key];
343 ['speed','dist','time'].forEach(function (k) {
344 var isRes = k === key;
345 var fd = fieldDivs[k];
346 fd.className = 'bkbg-spd-field' + (isRes ? ' is-result' : '');
347 fd.style.background = isRes ? '' : (o.cardBg||'#fff');
348 inputEls[k].disabled = isRes;
349 if (isRes) { inputEls[k].value = ''; inputEls[k].placeholder = ''; }
350 // Update label text
351 fd.querySelector('.bkbg-spd-field-label').textContent = FIELDS[k].label + (isRes ? ' (Result)' : '');
352 });
353
354 resultZone.innerHTML = '';
355 if (o.showFormula) appendFormula(resultZone);
356 if (o.showExamples) appendExamples(resultZone);
357 }
358
359 // ── Wire events ──
360 ['speed','dist','time'].forEach(function (key) {
361 tabBtns[key].addEventListener('click', function () { applySolveFor(key); });
362
363 selectEls[key].addEventListener('change', function () {
364 FIELDS[key].setUnit(selectEls[key].value);
365 if (key === 'speed') speedUnit = selectEls[key].value;
366 if (key === 'dist') distUnit = selectEls[key].value;
367 if (key === 'time') timeUnit = selectEls[key].value;
368 });
369
370 inputEls[key].addEventListener('input', function () {
371 // auto-calculate only when 2 non-result inputs are filled
372 var keys = ['speed','dist','time'].filter(function(k){ return k !== solveFor; });
373 var bothFilled = keys.every(function(k){ return inputEls[k].value.trim() !== '' && !isNaN(parseFloat(inputEls[k].value)); });
374 if (bothFilled) solve();
375 });
376 });
377
378 calcBtn.addEventListener('click', solve);
379
380 // ── Initial state ──
381 if (o.showFormula) appendFormula(resultZone);
382 if (o.showExamples) appendExamples(resultZone);
383 }
384
385 // ── Init all blocks on page ─────────────────────────────────────────────
386 document.querySelectorAll('.bkbg-spd-app').forEach(buildBlock);
387
388 })();
389