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

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

321 lines 17.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 var ACTIVITY_MULT = {sedentary:1.2, light:1.375, moderate:1.55, active:1.725, very_active:1.9};
26 var GOAL_MULT = {lose_fast:0.75, lose_slow:0.85, maintain:1, gain_slow:1.1, gain_fast:1.2};
27 var MACRO_RATIOS = {
28 lose_fast:[0.40,0.30,0.30], lose_slow:[0.35,0.35,0.30], maintain:[0.30,0.40,0.30],
29 gain_slow:[0.30,0.45,0.25], gain_fast:[0.30,0.50,0.20]
30 };
31 var ACTIVITY_OPTS = [
32 {label:'Sedentary (little/no exercise)', value:'sedentary'},
33 {label:'Light (1-3 days/week)', value:'light'},
34 {label:'Moderate (3-5 days/week)', value:'moderate'},
35 {label:'Active (6-7 days/week)', value:'active'},
36 {label:'Very Active (twice/day)', value:'very_active'}
37 ];
38 var GOAL_OPTS = [
39 {label:'Lose weight fast (–25%)', value:'lose_fast'},
40 {label:'Lose weight slow (–15%)', value:'lose_slow'},
41 {label:'Maintain weight', value:'maintain'},
42 {label:'Gain muscle slow (+10%)', value:'gain_slow'},
43 {label:'Gain muscle fast (+20%)', value:'gain_fast'}
44 ];
45
46 function calcMacros(gender, age, weightKg, heightCm, activity, goal) {
47 var bmr = gender === 'female'
48 ? 10*weightKg + 6.25*heightCm - 5*age - 161
49 : 10*weightKg + 6.25*heightCm - 5*age + 5;
50 var tdee = bmr * (ACTIVITY_MULT[activity] || 1.55);
51 var calories = Math.round(tdee * (GOAL_MULT[goal] || 1));
52 var ratio = MACRO_RATIOS[goal] || MACRO_RATIOS['maintain'];
53 return {
54 calories: calories,
55 protein: Math.round((calories * ratio[0]) / 4),
56 carbs: Math.round((calories * ratio[1]) / 4),
57 fat: Math.round((calories * ratio[2]) / 9),
58 bmr: Math.round(bmr),
59 tdee: Math.round(tdee)
60 };
61 }
62
63 function buildDonut(r, protClr, carbClr, fatClr) {
64 var ns = 'http://www.w3.org/2000/svg';
65 var svg = document.createElementNS(ns, 'svg');
66 svg.setAttribute('viewBox','0 0 100 100');
67 svg.className = 'bkbg-mcr-donut-svg';
68 svg.style.cssText = 'width:110px;height:110px;display:block';
69
70 var total = r.protein*4 + r.carbs*4 + r.fat*9;
71 var p1 = total > 0 ? r.protein*4/total : 0.33;
72 var p2 = total > 0 ? r.carbs*4/total : 0.34;
73
74 function makeArc(pct, offset, color) {
75 var c = 2 * Math.PI * 40;
76 var sd = c * (1 - pct);
77 var el = document.createElementNS(ns, 'circle');
78 el.setAttribute('cx','50'); el.setAttribute('cy','50'); el.setAttribute('r','40');
79 el.setAttribute('fill','none'); el.setAttribute('stroke', color); el.setAttribute('stroke-width','18');
80 el.setAttribute('stroke-dasharray', c); el.setAttribute('stroke-dashoffset', sd);
81 el.setAttribute('transform','rotate('+(offset*360-90)+' 50 50)');
82 return el;
83 }
84 svg.appendChild(makeArc(p1, 0, protClr));
85 svg.appendChild(makeArc(p2, p1, carbClr));
86 svg.appendChild(makeArc(1-p1-p2, p1+p2, fatClr));
87
88 var tVal = document.createElementNS(ns,'text');
89 tVal.setAttribute('x','50'); tVal.setAttribute('y','48'); tVal.setAttribute('text-anchor','middle');
90 tVal.setAttribute('font-size','12'); tVal.setAttribute('fill','#374151'); tVal.setAttribute('font-weight','700');
91 tVal.textContent = r.calories;
92 var tLbl = document.createElementNS(ns,'text');
93 tLbl.setAttribute('x','50'); tLbl.setAttribute('y','60'); tLbl.setAttribute('text-anchor','middle');
94 tLbl.setAttribute('font-size','7'); tLbl.setAttribute('fill','#6b7280');
95 tLbl.textContent = 'kcal/day';
96 svg.appendChild(tVal); svg.appendChild(tLbl);
97 return svg;
98 }
99
100 function initMacro(app) {
101 var opts;
102 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) { opts = {}; }
103
104 var accent = opts.accentColor || '#6c3fb5';
105 var cardBg = opts.cardBg || '#ffffff';
106 var calorieBg = opts.calorieBg || accent;
107 var calorieColor = opts.calorieColor || '#ffffff';
108 var protClr = opts.proteinColor || '#3b82f6';
109 var carbClr = opts.carbColor || '#f59e0b';
110 var fatClr = opts.fatColor || '#ef4444';
111 var statBg = opts.statBg || '#f3f4f6';
112 var statBorder = opts.statBorder || '#e5e7eb';
113 var inputBorder = opts.inputBorder || '#e5e7eb';
114 var labelColor = opts.labelColor || '#374151';
115 var sectionBg = opts.sectionBg || '';
116
117 var cardRadius = (opts.cardRadius || 16) + 'px';
118 var inputRadius = (opts.inputRadius || 8) + 'px';
119 var maxWidth = (opts.maxWidth || 580) + 'px';
120 var calorieSize = (opts.calorieSize || 52) + 'px';
121 var titleSize = (opts.titleSize || 28) + 'px';
122 var paddingTop = (opts.paddingTop != null ? opts.paddingTop : 60) + 'px';
123 var paddingBottom = (opts.paddingBottom != null ? opts.paddingBottom : 60) + 'px';
124
125 var showPieChart = opts.showPieChart !== false;
126
127 app.style.setProperty('--bkbg-mcr-accent', accent);
128 typoCssVarsForEl(app, opts.titleTypo, '--bkbg-mcr-tt-');
129 typoCssVarsForEl(app, opts.subtitleTypo, '--bkbg-mcr-st-');
130 app.style.paddingTop = paddingTop;
131 app.style.paddingBottom = paddingBottom;
132 if (sectionBg) app.style.background = sectionBg;
133
134 var wrap = document.createElement('div');
135 wrap.className = 'bkbg-mcr-wrap';
136 wrap.style.cssText = 'background:' + cardBg + ';border-radius:' + cardRadius + ';max-width:' + maxWidth;
137 app.appendChild(wrap);
138
139 // Header
140 if ((opts.showTitle && opts.title) || (opts.showSubtitle && opts.subtitle)) {
141 var hdr = document.createElement('div'); hdr.className = 'bkbg-mcr-header';
142 if (opts.showTitle && opts.title) {
143 var t = document.createElement('div'); t.className = 'bkbg-mcr-title';
144 if (opts.titleColor) t.style.color = opts.titleColor;
145 t.textContent = opts.title; hdr.appendChild(t);
146 }
147 if (opts.showSubtitle && opts.subtitle) {
148 var s = document.createElement('div'); s.className = 'bkbg-mcr-subtitle';
149 if (opts.subtitleColor) s.style.color = opts.subtitleColor;
150 s.textContent = opts.subtitle; hdr.appendChild(s);
151 }
152 wrap.appendChild(hdr);
153 }
154
155 // State
156 var state = {
157 unit: opts.defaultUnit || 'metric',
158 gender: opts.defaultGender || 'male',
159 age: opts.defaultAge || 30,
160 weight: opts.defaultWeight || 75,
161 height: opts.defaultHeight || 175,
162 activity: opts.defaultActivity || 'moderate',
163 goal: opts.defaultGoal || 'maintain'
164 };
165
166 function inputStyle() { return 'border:1.5px solid '+inputBorder+';border-radius:'+inputRadius; }
167 function makeLabel(text) {
168 var l = document.createElement('label');
169 l.className = 'bkbg-mcr-label'; l.style.color = labelColor;
170 l.textContent = text; return l;
171 }
172
173 // Unit toggle row
174 var toggleRow = document.createElement('div'); toggleRow.className = 'bkbg-mcr-toggle-row';
175
176 function makeToggle(options, current, onChange, labelText) {
177 var grp = document.createElement('div'); grp.className = 'bkbg-mcr-toggle-group';
178 var lbl = makeLabel(labelText); grp.appendChild(lbl);
179 var btns = document.createElement('div');
180 btns.className = 'bkbg-mcr-toggle-btns';
181 btns.style.cssText = 'border:1.5px solid '+inputBorder+';border-radius:8px;overflow:hidden';
182 var btnEls = [];
183 options.forEach(function(o) {
184 var b = document.createElement('button');
185 b.className = 'bkbg-mcr-toggle-btn'; b.dataset.val = o.value;
186 b.textContent = o.label;
187 b.style.cssText = 'background:' + (current===o.value?accent:'#fff') + ';color:' + (current===o.value?'#fff':'#374151');
188 b.addEventListener('click', function() {
189 current = o.value;
190 btnEls.forEach(function(be) {
191 be.style.background = be.dataset.val===current ? accent : '#fff';
192 be.style.color = be.dataset.val===current ? '#fff' : '#374151';
193 });
194 onChange(o.value);
195 refresh();
196 });
197 btnEls.push(b); btns.appendChild(b);
198 });
199 grp.appendChild(btns); return grp;
200 }
201
202 var unitToggle = makeToggle([{label:'Metric',value:'metric'},{label:'Imperial',value:'imperial'}], state.unit, function(v){ state.unit=v; updateLabels(); }, 'Unit');
203 var genderToggle = makeToggle([{label:'Male',value:'male'},{label:'Female',value:'female'}], state.gender, function(v){ state.gender=v; }, 'Gender');
204 toggleRow.appendChild(unitToggle); toggleRow.appendChild(genderToggle);
205 wrap.appendChild(toggleRow);
206
207 // Age / Weight / Height grid
208 var grid3 = document.createElement('div'); grid3.className = 'bkbg-mcr-grid-3';
209
210 function makeNumberField(labelText, val, min, onChange) {
211 var field = document.createElement('div');
212 var lbl = makeLabel(labelText); field.appendChild(lbl);
213 var inp = document.createElement('input');
214 inp.type = 'number'; inp.value = val; inp.min = min;
215 inp.className = 'bkbg-mcr-input'; inp.style.cssText = inputStyle();
216 inp.addEventListener('input', function() { onChange(parseFloat(inp.value) || 0); refresh(); });
217 field.appendChild(inp); return { field:field, inp:inp };
218 }
219
220 var ageF = makeNumberField('Age', state.age, 10, function(v){state.age=v;});
221 var weightF = makeNumberField('Weight (kg)', state.weight, 30, function(v){state.weight=v;});
222 var heightF = makeNumberField('Height (cm)', state.height, 50, function(v){state.height=v;});
223
224 function updateLabels() {
225 weightF.inp.previousSibling.textContent = state.unit==='imperial' ? 'Weight (lbs)' : 'Weight (kg)';
226 heightF.inp.previousSibling.textContent = state.unit==='imperial' ? 'Height (in)' : 'Height (cm)';
227 }
228
229 grid3.appendChild(ageF.field); grid3.appendChild(weightF.field); grid3.appendChild(heightF.field);
230 wrap.appendChild(grid3);
231
232 // Activity + Goal selects
233 var grid2 = document.createElement('div'); grid2.className = 'bkbg-mcr-grid-2';
234 function makeSelect(labelText, options, val, onChange) {
235 var field = document.createElement('div');
236 var lbl = makeLabel(labelText); field.appendChild(lbl);
237 var sel = document.createElement('select');
238 sel.className = 'bkbg-mcr-select'; sel.style.cssText = inputStyle();
239 options.forEach(function(o) { var op=document.createElement('option'); op.value=o.value; op.textContent=o.label; if(o.value===val) op.selected=true; sel.appendChild(op); });
240 sel.addEventListener('change', function() { onChange(sel.value); refresh(); });
241 field.appendChild(sel); return field;
242 }
243 grid2.appendChild(makeSelect('Activity Level', ACTIVITY_OPTS, state.activity, function(v){state.activity=v;}));
244 grid2.appendChild(makeSelect('Goal', GOAL_OPTS, state.goal, function(v){state.goal=v;}));
245 wrap.appendChild(grid2);
246
247 // Calorie result card
248 var calorieCard = document.createElement('div'); calorieCard.className = 'bkbg-mcr-calorie-card';
249 calorieCard.style.cssText = 'background:' + calorieBg + ';color:' + calorieColor;
250 var cLbl = document.createElement('div'); cLbl.className = 'bkbg-mcr-calorie-lbl'; cLbl.textContent = 'Daily Calories';
251 var cVal = document.createElement('div'); cVal.className = 'bkbg-mcr-calorie-val'; cVal.style.fontSize = calorieSize;
252 var cSub = document.createElement('div'); cSub.className = 'bkbg-mcr-calorie-sub';
253 calorieCard.appendChild(cLbl); calorieCard.appendChild(cVal); calorieCard.appendChild(cSub);
254 wrap.appendChild(calorieCard);
255
256 // Macros row
257 var macrosRow = document.createElement('div'); macrosRow.className = 'bkbg-mcr-macros-row';
258
259 var donutWrap;
260 if (showPieChart) { donutWrap = document.createElement('div'); donutWrap.className = 'bkbg-mcr-donut'; macrosRow.appendChild(donutWrap); }
261
262 var macroList = document.createElement('div'); macroList.className = 'bkbg-mcr-macro-list';
263 var macroItems = [
264 {key:'protein', label:'Protein', unit:'g', note:'(4 kcal/g)', color:protClr},
265 {key:'carbs', label:'Carbohydrates', unit:'g', note:'(4 kcal/g)', color:carbClr},
266 {key:'fat', label:'Fat', unit:'g', note:'(9 kcal/g)', color:fatClr}
267 ];
268 var macroValEls = {};
269 macroItems.forEach(function(m) {
270 var item = document.createElement('div'); item.className = 'bkbg-mcr-macro-item';
271 item.style.cssText = 'background:' + statBg + ';border:1px solid ' + statBorder;
272 var left = document.createElement('div'); left.className = 'bkbg-mcr-macro-left';
273 var dot = document.createElement('div'); dot.className = 'bkbg-mcr-macro-dot'; dot.style.background = m.color;
274 var name = document.createElement('span'); name.className = 'bkbg-mcr-macro-name'; name.style.color = labelColor; name.textContent = m.label;
275 var note = document.createElement('span'); note.className = 'bkbg-mcr-macro-note'; note.textContent = m.note;
276 left.appendChild(dot); left.appendChild(name); left.appendChild(note);
277 var val = document.createElement('span'); val.className = 'bkbg-mcr-macro-val'; val.style.color = m.color;
278 macroValEls[m.key] = val;
279 item.appendChild(left); item.appendChild(val); macroList.appendChild(item);
280 });
281 macrosRow.appendChild(macroList);
282 wrap.appendChild(macrosRow);
283
284 // BMR / TDEE stats
285 var statRow = document.createElement('div'); statRow.className = 'bkbg-mcr-stat-row';
286 var bmrStat = document.createElement('div'); bmrStat.className = 'bkbg-mcr-stat'; bmrStat.style.cssText = 'background:'+statBg+';border:1px solid '+statBorder;
287 var tdeeStat = document.createElement('div'); tdeeStat.className = 'bkbg-mcr-stat'; tdeeStat.style.cssText = 'background:'+statBg+';border:1px solid '+statBorder;
288 var bmrV = document.createElement('div'); bmrV.className = 'bkbg-mcr-stat-val'; bmrV.style.color = '#111827';
289 var bmrL = document.createElement('div'); bmrL.className = 'bkbg-mcr-stat-lbl'; bmrL.style.color = '#6b7280'; bmrL.textContent = 'BMR (base metabolic rate)';
290 var tdeeV = document.createElement('div'); tdeeV.className = 'bkbg-mcr-stat-val'; tdeeV.style.color = '#111827';
291 var tdeeL = document.createElement('div'); tdeeL.className = 'bkbg-mcr-stat-lbl'; tdeeL.style.color = '#6b7280'; tdeeL.textContent = 'TDEE (maintenance calories)';
292 bmrStat.appendChild(bmrV); bmrStat.appendChild(bmrL);
293 tdeeStat.appendChild(tdeeV); tdeeStat.appendChild(tdeeL);
294 statRow.appendChild(bmrStat); statRow.appendChild(tdeeStat);
295 wrap.appendChild(statRow);
296
297 function refresh() {
298 var weightKg = state.unit === 'imperial' ? state.weight * 0.453592 : state.weight;
299 var heightCm = state.unit === 'imperial' ? state.height * 2.54 : state.height;
300 var r = calcMacros(state.gender, state.age, weightKg, heightCm, state.activity, state.goal);
301
302 cVal.textContent = r.calories;
303 cSub.textContent = 'BMR: ' + r.bmr + ' · TDEE: ' + r.tdee + ' kcal';
304 macroValEls.protein.textContent = r.protein + 'g';
305 macroValEls.carbs.textContent = r.carbs + 'g';
306 macroValEls.fat.textContent = r.fat + 'g';
307 bmrV.textContent = r.bmr;
308 tdeeV.textContent = r.tdee;
309
310 if (showPieChart && donutWrap) {
311 donutWrap.innerHTML = '';
312 donutWrap.appendChild(buildDonut(r, protClr, carbClr, fatClr));
313 }
314 }
315
316 refresh();
317 }
318
319 document.querySelectorAll('.bkbg-mcr-app').forEach(initMacro);
320 })();
321