| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function calcBodyFat(gender, height, waist, neck, hip) { |
| 5 |
var h = parseFloat(height) || 0; |
| 6 |
var w = parseFloat(waist) || 0; |
| 7 |
var n = parseFloat(neck) || 0; |
| 8 |
var hi= parseFloat(hip) || 0; |
| 9 |
if (h <= 0 || w <= 0 || n <= 0) return null; |
| 10 |
var bf; |
| 11 |
if (gender === 'male') { |
| 12 |
bf = 495 / (1.0324 - 0.19077 * Math.log10(w - n) + 0.15456 * Math.log10(h)) - 450; |
| 13 |
} else { |
| 14 |
if (hi <= 0) return null; |
| 15 |
bf = 495 / (1.29579 - 0.35004 * Math.log10(w + hi - n) + 0.22100 * Math.log10(h)) - 450; |
| 16 |
} |
| 17 |
return Math.max(0, Math.min(70, +bf.toFixed(1))); |
| 18 |
} |
| 19 |
|
| 20 |
function getCategory(bf, gender) { |
| 21 |
if (gender === 'male') { |
| 22 |
if (bf < 6) return { label: 'Essential Fat', color: '#3b82f6' }; |
| 23 |
if (bf < 14) return { label: 'Athletes', color: '#10b981' }; |
| 24 |
if (bf < 18) return { label: 'Fitness', color: '#6c3fb5' }; |
| 25 |
if (bf < 25) return { label: 'Average', color: '#f59e0b' }; |
| 26 |
return { label: 'Obese', color: '#ef4444' }; |
| 27 |
} else { |
| 28 |
if (bf < 14) return { label: 'Essential Fat', color: '#3b82f6' }; |
| 29 |
if (bf < 21) return { label: 'Athletes', color: '#10b981' }; |
| 30 |
if (bf < 25) return { label: 'Fitness', color: '#6c3fb5' }; |
| 31 |
if (bf < 32) return { label: 'Average', color: '#f59e0b' }; |
| 32 |
return { label: 'Obese', color: '#ef4444' }; |
| 33 |
} |
| 34 |
} |
| 35 |
|
| 36 |
function maleRanges() { return [[0,6,'Essential','#3b82f6'],[6,14,'Athletes','#10b981'],[14,18,'Fitness','#6c3fb5'],[18,25,'Average','#f59e0b'],[25,50,'Obese','#ef4444']]; } |
| 37 |
function femaleRanges() { return [[0,14,'Essential','#3b82f6'],[14,21,'Athletes','#10b981'],[21,25,'Fitness','#6c3fb5'],[25,32,'Average','#f59e0b'],[32,50,'Obese','#ef4444']]; } |
| 38 |
|
| 39 |
function typoCssVarsForEl(typo, prefix, el) { |
| 40 |
if (!typo) return; |
| 41 |
if (typo.family) el.style.setProperty(prefix + 'font-family', "'" + typo.family + "', sans-serif"); |
| 42 |
if (typo.weight) el.style.setProperty(prefix + 'font-weight', typo.weight); |
| 43 |
if (typo.transform) el.style.setProperty(prefix + 'text-transform', typo.transform); |
| 44 |
if (typo.style) el.style.setProperty(prefix + 'font-style', typo.style); |
| 45 |
if (typo.decoration) el.style.setProperty(prefix + 'text-decoration', typo.decoration); |
| 46 |
var su = typo.sizeUnit || 'px'; |
| 47 |
if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) el.style.setProperty(prefix + 'font-size-d', typo.sizeDesktop + su); |
| 48 |
if (typo.sizeTablet !== '' && typo.sizeTablet != null) el.style.setProperty(prefix + 'font-size-t', typo.sizeTablet + su); |
| 49 |
if (typo.sizeMobile !== '' && typo.sizeMobile != null) el.style.setProperty(prefix + 'font-size-m', typo.sizeMobile + su); |
| 50 |
var lhu = typo.lineHeightUnit || ''; |
| 51 |
if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) el.style.setProperty(prefix + 'line-height-d', typo.lineHeightDesktop + lhu); |
| 52 |
if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) el.style.setProperty(prefix + 'line-height-t', typo.lineHeightTablet + lhu); |
| 53 |
if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) el.style.setProperty(prefix + 'line-height-m', typo.lineHeightMobile + lhu); |
| 54 |
var lsu = typo.letterSpacingUnit || 'px'; |
| 55 |
if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) el.style.setProperty(prefix + 'letter-spacing-d', typo.letterSpacingDesktop + lsu); |
| 56 |
if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) el.style.setProperty(prefix + 'letter-spacing-t', typo.letterSpacingTablet + lsu); |
| 57 |
if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) el.style.setProperty(prefix + 'letter-spacing-m', typo.letterSpacingMobile + lsu); |
| 58 |
var wsu = typo.wordSpacingUnit || 'px'; |
| 59 |
if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) el.style.setProperty(prefix + 'word-spacing-d', typo.wordSpacingDesktop + wsu); |
| 60 |
if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) el.style.setProperty(prefix + 'word-spacing-t', typo.wordSpacingTablet + wsu); |
| 61 |
if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) el.style.setProperty(prefix + 'word-spacing-m', typo.wordSpacingMobile + wsu); |
| 62 |
} |
| 63 |
|
| 64 |
function buildApp(app) { |
| 65 |
var opts = {}; |
| 66 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {} |
| 67 |
var o = opts; |
| 68 |
var accent = o.accentColor || '#6c3fb5'; |
| 69 |
var labelC = o.labelColor || '#374151'; |
| 70 |
var radius = (o.cardRadius || 16) + 'px'; |
| 71 |
var inpRad = (o.inputRadius || 8) + 'px'; |
| 72 |
var resultBg= o.resultBg || '#f5f3ff'; |
| 73 |
var resultBr= o.resultBorder || '#ede9fe'; |
| 74 |
|
| 75 |
app.innerHTML = ''; |
| 76 |
app.style.fontFamily = 'inherit'; |
| 77 |
|
| 78 |
var section = document.createElement('div'); |
| 79 |
section.style.cssText = 'padding-top:' + (o.paddingTop||60) + 'px;padding-bottom:' + (o.paddingBottom||60) + 'px;background:' + (o.sectionBg||'') + ';'; |
| 80 |
app.appendChild(section); |
| 81 |
|
| 82 |
var card = document.createElement('div'); |
| 83 |
card.style.cssText = 'background:' + (o.cardBg||'#fff') + ';border-radius:' + radius + ';padding:36px 32px;max-width:' + (o.maxWidth||560) + 'px;margin:0 auto;box-shadow:0 4px 24px rgba(0,0,0,.09);box-sizing:border-box;'; |
| 84 |
section.appendChild(card); |
| 85 |
|
| 86 |
// Title/subtitle |
| 87 |
if (o.showTitle || o.showSubtitle) { |
| 88 |
var hdr = document.createElement('div'); |
| 89 |
hdr.style.marginBottom = '20px'; |
| 90 |
if (o.showTitle) { var t=document.createElement('div'); t.className='bkbg-bfc-title'; t.textContent=o.title||'Body Fat Calculator'; t.style.cssText='color:'+(o.titleColor||'#1e1b4b')+';margin-bottom:6px;'; hdr.appendChild(t); } |
| 91 |
if (o.showSubtitle && o.subtitle) { var s=document.createElement('div'); s.textContent=o.subtitle; s.style.cssText='font-size:15px;color:'+(o.subtitleColor||'#6b7280')+';'; hdr.appendChild(s); } |
| 92 |
card.appendChild(hdr); |
| 93 |
} |
| 94 |
|
| 95 |
var gender = o.defaultGender || 'male'; |
| 96 |
var unit = o.defaultUnit || 'metric'; |
| 97 |
var unitLbl = unit === 'metric' ? 'cm' : 'in'; |
| 98 |
|
| 99 |
// Helper: convert to cm |
| 100 |
function toCm(v) { return unit === 'imperial' ? v * 2.54 : v; } |
| 101 |
|
| 102 |
// Gender tabs |
| 103 |
var genderRow = document.createElement('div'); |
| 104 |
genderRow.style.cssText = 'display:flex;gap:10px;margin-bottom:20px;'; |
| 105 |
var gBtns = {}; |
| 106 |
['male','female'].forEach(function(g) { |
| 107 |
var btn = document.createElement('button'); |
| 108 |
btn.type = 'button'; |
| 109 |
btn.textContent = g === 'male' ? '♂ Male' : '♀ Female'; |
| 110 |
btn.style.cssText = 'flex:1;padding:9px 16px;border-radius:8px;cursor:pointer;font-family:inherit;font-size:14px;font-weight:700;transition:all .15s;'; |
| 111 |
btn.addEventListener('click', function() { gender = g; refreshGender(); update(); }); |
| 112 |
gBtns[g] = btn; |
| 113 |
genderRow.appendChild(btn); |
| 114 |
}); |
| 115 |
card.appendChild(genderRow); |
| 116 |
|
| 117 |
// Unit tabs |
| 118 |
var unitRow = document.createElement('div'); |
| 119 |
unitRow.style.cssText = 'display:flex;gap:10px;margin-bottom:24px;'; |
| 120 |
var uBtns = {}; |
| 121 |
[['metric','Metric (cm)'],['imperial','Imperial (in)']].forEach(function(pair) { |
| 122 |
var btn = document.createElement('button'); |
| 123 |
btn.type = 'button'; btn.textContent = pair[1]; |
| 124 |
btn.style.cssText = 'flex:1;padding:9px 16px;border-radius:8px;cursor:pointer;font-family:inherit;font-size:14px;font-weight:700;transition:all .15s;'; |
| 125 |
btn.addEventListener('click', function() { unit = pair[0]; unitLbl = unit==='metric'?'cm':'in'; refreshUnit(); }); |
| 126 |
uBtns[pair[0]] = btn; |
| 127 |
unitRow.appendChild(btn); |
| 128 |
}); |
| 129 |
card.appendChild(unitRow); |
| 130 |
|
| 131 |
function styleTab(btn, active) { |
| 132 |
btn.style.border = '2px solid ' + (active ? accent : '#e5e7eb'); |
| 133 |
btn.style.background = active ? accent : 'transparent'; |
| 134 |
btn.style.color = active ? '#fff' : accent; |
| 135 |
} |
| 136 |
function refreshGender() { ['male','female'].forEach(function(g){ styleTab(gBtns[g], g===gender); }); buildHipRow(); } |
| 137 |
function refreshUnit() { |
| 138 |
['metric','imperial'].forEach(function(u){ styleTab(uBtns[u], u===unit); }); |
| 139 |
unitLbl = unit === 'metric' ? 'cm' : 'in'; |
| 140 |
['height','waist','neck','hip'].forEach(function(f) { |
| 141 |
var lEl = document.getElementById('bkbg-bfc-lbl-'+f); |
| 142 |
if (lEl) lEl.textContent = unitLbl; |
| 143 |
}); |
| 144 |
update(); |
| 145 |
} |
| 146 |
refreshGender(); refreshUnit(); |
| 147 |
|
| 148 |
// Input grid |
| 149 |
var grid = document.createElement('div'); |
| 150 |
grid.className = 'bkbg-bfc-input-grid'; |
| 151 |
grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:0 20px;margin-bottom:20px;'; |
| 152 |
card.appendChild(grid); |
| 153 |
|
| 154 |
var inputs = {}; |
| 155 |
var defaults = { metric: { height:170, waist:85, neck:37, hip:95 }, imperial: { height:67, waist:33, neck:15, hip:37 } }; |
| 156 |
|
| 157 |
function makeInputRow(field, label, container) { |
| 158 |
var row = document.createElement('div'); |
| 159 |
row.className = 'bkbg-bfc-input-row'; |
| 160 |
row.setAttribute('data-field', field); |
| 161 |
row.style.marginBottom = '14px'; |
| 162 |
var lbl = document.createElement('label'); |
| 163 |
lbl.style.cssText = 'display:flex;justify-content:space-between;font-size:13px;font-weight:600;color:'+labelC+';margin-bottom:5px;'; |
| 164 |
var nm = document.createElement('span'); nm.textContent = label; |
| 165 |
var su = document.createElement('span'); su.id = 'bkbg-bfc-lbl-'+field; su.textContent = unitLbl; su.style.cssText = 'font-weight:400;color:#9ca3af;'; |
| 166 |
lbl.appendChild(nm); lbl.appendChild(su); |
| 167 |
row.appendChild(lbl); |
| 168 |
var inp = document.createElement('input'); |
| 169 |
inp.type = 'number'; inp.min = '0'; inp.step = '0.1'; |
| 170 |
inp.value = (defaults[unit] || defaults.metric)[field]; |
| 171 |
inp.style.cssText = 'width:100%;padding:9px 12px;border-radius:'+inpRad+';border:1.5px solid #e5e7eb;font-size:16px;outline:none;box-sizing:border-box;'; |
| 172 |
inp.addEventListener('focus', function(){ inp.style.borderColor=accent; inp.style.boxShadow='0 0 0 3px rgba(108,63,181,.15)'; }); |
| 173 |
inp.addEventListener('blur', function(){ inp.style.borderColor='#e5e7eb'; inp.style.boxShadow=''; }); |
| 174 |
inp.addEventListener('input', update); |
| 175 |
row.appendChild(inp); |
| 176 |
inputs[field] = inp; |
| 177 |
container.appendChild(row); |
| 178 |
} |
| 179 |
|
| 180 |
makeInputRow('height', 'Height', grid); |
| 181 |
makeInputRow('waist', 'Waist (at navel)', grid); |
| 182 |
makeInputRow('neck', 'Neck', grid); |
| 183 |
|
| 184 |
var hipRow; |
| 185 |
function buildHipRow() { |
| 186 |
if (gender === 'female') { |
| 187 |
if (!hipRow) { |
| 188 |
hipRow = document.createElement('div'); |
| 189 |
hipRow.className = 'bkbg-bfc-input-row'; |
| 190 |
hipRow.style.marginBottom = '14px'; |
| 191 |
var lbl = document.createElement('label'); |
| 192 |
lbl.style.cssText = 'display:flex;justify-content:space-between;font-size:13px;font-weight:600;color:'+labelC+';margin-bottom:5px;'; |
| 193 |
var nm = document.createElement('span'); nm.textContent = 'Hips'; |
| 194 |
var su = document.createElement('span'); su.id = 'bkbg-bfc-lbl-hip'; su.textContent = unitLbl; su.style.cssText = 'font-weight:400;color:#9ca3af;'; |
| 195 |
lbl.appendChild(nm); lbl.appendChild(su); |
| 196 |
hipRow.appendChild(lbl); |
| 197 |
var inp = document.createElement('input'); inp.type = 'number'; inp.min = '0'; inp.step = '0.1'; inp.value = (defaults[unit]||defaults.metric).hip; |
| 198 |
inp.style.cssText = 'width:100%;padding:9px 12px;border-radius:'+inpRad+';border:1.5px solid #e5e7eb;font-size:16px;outline:none;box-sizing:border-box;'; |
| 199 |
inp.addEventListener('focus',function(){ inp.style.borderColor=accent; inp.style.boxShadow='0 0 0 3px rgba(108,63,181,.15)'; }); |
| 200 |
inp.addEventListener('blur', function(){ inp.style.borderColor='#e5e7eb'; inp.style.boxShadow=''; }); |
| 201 |
inp.addEventListener('input', update); |
| 202 |
hipRow.appendChild(inp); |
| 203 |
inputs.hip = inp; |
| 204 |
} |
| 205 |
grid.appendChild(hipRow); |
| 206 |
} else if (hipRow && hipRow.parentNode) { |
| 207 |
hipRow.parentNode.removeChild(hipRow); |
| 208 |
} |
| 209 |
} |
| 210 |
|
| 211 |
// Result |
| 212 |
var resultBox = document.createElement('div'); |
| 213 |
resultBox.style.cssText = 'background:'+resultBg+';border:2px solid '+resultBr+';border-radius:'+radius+';padding:24px 28px;text-align:center;margin-bottom:16px;'; |
| 214 |
card.appendChild(resultBox); |
| 215 |
|
| 216 |
// Category bar |
| 217 |
var catSection = document.createElement('div'); |
| 218 |
if (o.showChart) card.appendChild(catSection); |
| 219 |
|
| 220 |
function update() { |
| 221 |
var h = toCm(parseFloat(inputs.height.value)||0); |
| 222 |
var w = toCm(parseFloat(inputs.waist.value)||0); |
| 223 |
var n = toCm(parseFloat(inputs.neck.value)||0); |
| 224 |
var hi= gender==='female' && inputs.hip ? toCm(parseFloat(inputs.hip.value)||0) : 0; |
| 225 |
var bf = calcBodyFat(gender, h, w, n, hi); |
| 226 |
var cat = bf !== null ? getCategory(bf, gender) : null; |
| 227 |
|
| 228 |
resultBox.innerHTML = ''; |
| 229 |
if (bf !== null && cat) { |
| 230 |
var valEl = document.createElement('div'); valEl.className='bkbg-bfc-bf-value'; valEl.textContent = bf+'%'; valEl.style.cssText = 'color:'+cat.color+';'; |
| 231 |
var catEl = document.createElement('div'); catEl.textContent = cat.label; catEl.style.cssText = 'margin-top:6px;font-weight:700;font-size:16px;color:'+cat.color+';'; |
| 232 |
var lblEl = document.createElement('div'); lblEl.textContent = 'Body Fat Percentage'; lblEl.style.cssText = 'margin-top:4px;font-size:13px;color:'+labelC+';'; |
| 233 |
resultBox.appendChild(valEl); resultBox.appendChild(catEl); resultBox.appendChild(lblEl); |
| 234 |
} else { |
| 235 |
var ph = document.createElement('div'); ph.textContent = 'Enter measurements above'; ph.style.cssText = 'color:'+labelC+';font-size:15px;'; |
| 236 |
resultBox.appendChild(ph); |
| 237 |
} |
| 238 |
|
| 239 |
if (o.showChart) { |
| 240 |
catSection.innerHTML = ''; |
| 241 |
var ranges = gender === 'male' ? maleRanges() : femaleRanges(); |
| 242 |
var track = document.createElement('div'); track.style.cssText = 'display:flex;height:14px;border-radius:100px;overflow:hidden;margin-bottom:8px;'; |
| 243 |
ranges.forEach(function(r) { var seg=document.createElement('div'); seg.style.cssText='flex:'+(r[1]-r[0])+';background:'+r[3]+';'; track.appendChild(seg); }); |
| 244 |
catSection.appendChild(track); |
| 245 |
if (o.showCategories) { |
| 246 |
var leg = document.createElement('div'); leg.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;'; |
| 247 |
ranges.forEach(function(r) { |
| 248 |
var chip=document.createElement('span'); chip.textContent=r[2]+' ('+r[0]+(r[1]<50?'-'+r[1]:'+')+' %)'; chip.style.cssText='font-size:11px;font-weight:700;background:'+r[3]+'22;color:'+r[3]+';padding:2px 8px;border-radius:100px;'; |
| 249 |
leg.appendChild(chip); |
| 250 |
}); |
| 251 |
catSection.appendChild(leg); |
| 252 |
} |
| 253 |
} |
| 254 |
} |
| 255 |
|
| 256 |
update(); |
| 257 |
typoCssVarsForEl(o.typoTitle, '--bkbg-bfc-title-', app); |
| 258 |
typoCssVarsForEl(o.typoResult, '--bkbg-bfc-result-', app); |
| 259 |
} |
| 260 |
|
| 261 |
document.querySelectorAll('.bkbg-bfc-app').forEach(buildApp); |
| 262 |
})(); |
| 263 |
|