| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function typoCssVarsForEl(typo, prefix, el) { |
| 5 |
if (!typo || typeof typo !== 'object') return; |
| 6 |
var map = { |
| 7 |
family: 'font-family', weight: 'font-weight', style: 'font-style', |
| 8 |
decoration: 'text-decoration', transform: 'text-transform', |
| 9 |
sizeDesktop: 'font-size-d', sizeTablet: 'font-size-t', sizeMobile: 'font-size-m', |
| 10 |
lineHeightDesktop: 'line-height-d', lineHeightTablet: 'line-height-t', lineHeightMobile: 'line-height-m', |
| 11 |
letterSpacingDesktop: 'letter-spacing-d', letterSpacingTablet: 'letter-spacing-t', letterSpacingMobile: 'letter-spacing-m', |
| 12 |
wordSpacingDesktop: 'word-spacing-d', wordSpacingTablet: 'word-spacing-t', wordSpacingMobile: 'word-spacing-m' |
| 13 |
}; |
| 14 |
Object.keys(map).forEach(function (k) { |
| 15 |
if (typo[k] !== undefined && typo[k] !== '') { |
| 16 |
var v = typo[k]; |
| 17 |
if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) { |
| 18 |
v = v + (typo.sizeUnit || 'px'); |
| 19 |
} else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) { |
| 20 |
v = v + (typo.lineHeightUnit || ''); |
| 21 |
} else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) { |
| 22 |
v = v + (typo.letterSpacingUnit || 'px'); |
| 23 |
} else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) { |
| 24 |
v = v + (typo.wordSpacingUnit || 'px'); |
| 25 |
} |
| 26 |
el.style.setProperty(prefix + map[k], String(v)); |
| 27 |
} |
| 28 |
}); |
| 29 |
} |
| 30 |
|
| 31 |
var UNITS = { |
| 32 |
mpg: { distLabel: 'Distance', effLabel: 'Fuel Economy', priceLabel: 'Fuel Price', volUnit: 'gal', effUnit: 'mpg', distUnit: 'mi' }, |
| 33 |
lper100:{ distLabel: 'Distance', effLabel: 'Fuel Economy', priceLabel: 'Fuel Price', volUnit: 'L', effUnit: 'L/100km', distUnit: 'km' }, |
| 34 |
kmpl: { distLabel: 'Distance', effLabel: 'Fuel Economy', priceLabel: 'Fuel Price', volUnit: 'L', effUnit: 'km/L', distUnit: 'km' } |
| 35 |
}; |
| 36 |
|
| 37 |
function calcFuel(dist, eff, price, unit) { |
| 38 |
if (unit === 'mpg') { |
| 39 |
var gallons = eff > 0 ? dist / eff : 0; |
| 40 |
return { cost: gallons * price, litres: gallons * 3.78541, gallons: gallons }; |
| 41 |
} else if (unit === 'lper100') { |
| 42 |
var l = eff > 0 ? (dist / 100) * eff : 0; |
| 43 |
return { cost: l * price, litres: l, gallons: l / 3.78541 }; |
| 44 |
} else { |
| 45 |
var l2 = eff > 0 ? dist / eff : 0; |
| 46 |
return { cost: l2 * price, litres: l2, gallons: l2 / 3.78541 }; |
| 47 |
} |
| 48 |
} |
| 49 |
|
| 50 |
function co2Kg(litres) { return litres * 2.31; } |
| 51 |
|
| 52 |
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); } |
| 53 |
|
| 54 |
function buildApp(app) { |
| 55 |
var opts; |
| 56 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) { return; } |
| 57 |
var o = opts; |
| 58 |
var accent = o.accentColor || '#6c3fb5'; |
| 59 |
var cur = o.currency || '$'; |
| 60 |
|
| 61 |
app.style.paddingTop = (o.paddingTop || 60) + 'px'; |
| 62 |
app.style.paddingBottom = (o.paddingBottom || 60) + 'px'; |
| 63 |
if (o.sectionBg) app.style.background = o.sectionBg; |
| 64 |
|
| 65 |
typoCssVarsForEl(o.typoTitle, '--bkbg-fuel-tt-', app); |
| 66 |
typoCssVarsForEl(o.typoSubtitle, '--bkbg-fuel-st-', app); |
| 67 |
typoCssVarsForEl(o.typoResult, '--bkbg-fuel-rs-', app); |
| 68 |
|
| 69 |
var maxW = (o.maxWidth || 620) + 'px'; |
| 70 |
|
| 71 |
/* Header */ |
| 72 |
if (o.showTitle || o.showSubtitle) { |
| 73 |
var hdr = document.createElement('div'); |
| 74 |
hdr.style.cssText = 'text-align:center;margin-bottom:32px;max-width:' + maxW + ';margin-left:auto;margin-right:auto'; |
| 75 |
if (o.showTitle && o.title) { |
| 76 |
var ht = document.createElement('h3'); |
| 77 |
ht.className = 'bkbg-fuel-title'; |
| 78 |
ht.textContent = o.title; |
| 79 |
ht.style.cssText = 'color:' + (o.titleColor || '#1e1b4b') + ';margin:0 0 8px'; |
| 80 |
hdr.appendChild(ht); |
| 81 |
} |
| 82 |
if (o.showSubtitle && o.subtitle) { |
| 83 |
var hs = document.createElement('p'); |
| 84 |
hs.className = 'bkbg-fuel-subtitle'; |
| 85 |
hs.textContent = o.subtitle; |
| 86 |
hs.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';margin:0'; |
| 87 |
hdr.appendChild(hs); |
| 88 |
} |
| 89 |
app.appendChild(hdr); |
| 90 |
} |
| 91 |
|
| 92 |
/* Card */ |
| 93 |
var card = document.createElement('div'); |
| 94 |
card.style.cssText = 'background:' + (o.cardBg || '#fff') + ';border-radius:' + (o.cardRadius || 16) + 'px;padding:32px;max-width:' + maxW + ';margin:0 auto;box-shadow:0 4px 24px rgba(0,0,0,0.06)'; |
| 95 |
app.appendChild(card); |
| 96 |
|
| 97 |
/* State */ |
| 98 |
var unitKey = o.unit || 'mpg'; |
| 99 |
var dist = o.defaultDist || 300; |
| 100 |
var eff = o.defaultEff || 30; |
| 101 |
var price = o.defaultPrice || 3.50; |
| 102 |
var pax = o.defaultPassengers || 2; |
| 103 |
|
| 104 |
/* Unit toggle */ |
| 105 |
var unitRow = document.createElement('div'); |
| 106 |
unitRow.style.cssText = 'display:flex;gap:8px;margin-bottom:24px'; |
| 107 |
var unitBtns = {}; |
| 108 |
['mpg','lper100','kmpl'].forEach(function (k) { |
| 109 |
var btn = document.createElement('button'); |
| 110 |
btn.textContent = k === 'lper100' ? 'L/100km' : k.toUpperCase(); |
| 111 |
btn.className = 'bkbg-fuel-unit-btn'; |
| 112 |
btn.style.cssText = 'flex:1;padding:8px;border:none;border-radius:' + (o.inputRadius || 8) + 'px;font-weight:600;font-size:13px;cursor:pointer;transition:background 0.15s'; |
| 113 |
btn.addEventListener('click', function () { unitKey = k; refreshUnitBtns(); buildInputSection(); update(); }); |
| 114 |
unitBtns[k] = btn; |
| 115 |
unitRow.appendChild(btn); |
| 116 |
}); |
| 117 |
card.appendChild(unitRow); |
| 118 |
|
| 119 |
function refreshUnitBtns() { |
| 120 |
Object.keys(unitBtns).forEach(function (k) { |
| 121 |
unitBtns[k].style.background = k === unitKey ? accent : (o.statCardBg || '#f9fafb'); |
| 122 |
unitBtns[k].style.color = k === unitKey ? '#fff' : (o.labelColor || '#374151'); |
| 123 |
}); |
| 124 |
} |
| 125 |
refreshUnitBtns(); |
| 126 |
|
| 127 |
/* Input section */ |
| 128 |
var inputSection = document.createElement('div'); |
| 129 |
card.appendChild(inputSection); |
| 130 |
|
| 131 |
var distInput, effInput, priceInput, paxInput; |
| 132 |
|
| 133 |
function makeRow(labelText, suffix) { |
| 134 |
var row = document.createElement('div'); |
| 135 |
row.className = 'bkbg-fuel-input-row'; |
| 136 |
var lbl = document.createElement('label'); |
| 137 |
lbl.textContent = labelText; |
| 138 |
lbl.style.color = o.labelColor || '#374151'; |
| 139 |
var right = document.createElement('div'); |
| 140 |
right.style.cssText = 'display:flex;align-items:center;gap:6px'; |
| 141 |
var inp = document.createElement('input'); |
| 142 |
inp.type = 'number'; inp.min = '0'; inp.step = '0.01'; |
| 143 |
inp.style.cssText = 'width:90px;text-align:right;border:1px solid #d1d5db;border-radius:' + (o.inputRadius || 8) + 'px;padding:5px 8px;font-size:14px;box-sizing:border-box'; |
| 144 |
var sfx = document.createElement('span'); |
| 145 |
sfx.textContent = suffix; |
| 146 |
sfx.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';font-size:13px;min-width:56px'; |
| 147 |
right.appendChild(inp); |
| 148 |
right.appendChild(sfx); |
| 149 |
row.appendChild(lbl); |
| 150 |
row.appendChild(right); |
| 151 |
return { row: row, input: inp, suffix: sfx }; |
| 152 |
} |
| 153 |
|
| 154 |
function buildInputSection() { |
| 155 |
inputSection.innerHTML = ''; |
| 156 |
var u = UNITS[unitKey]; |
| 157 |
var dr = makeRow(u.distLabel + ' (' + u.distUnit + ')', u.distUnit); |
| 158 |
dr.input.value = dist; |
| 159 |
dr.input.addEventListener('input', function () { dist = parseFloat(dr.input.value) || 0; update(); }); |
| 160 |
distInput = dr.input; |
| 161 |
|
| 162 |
var er = makeRow(u.effLabel, u.effUnit); |
| 163 |
er.input.value = eff; |
| 164 |
er.input.addEventListener('input', function () { eff = parseFloat(er.input.value) || 0; update(); }); |
| 165 |
effInput = er.input; |
| 166 |
|
| 167 |
var pr = makeRow(u.priceLabel + ' (' + cur + '/' + u.volUnit + ')', cur + '/' + u.volUnit); |
| 168 |
pr.input.value = price; |
| 169 |
pr.input.step = '0.01'; |
| 170 |
pr.input.addEventListener('input', function () { price = parseFloat(pr.input.value) || 0; update(); }); |
| 171 |
priceInput = pr.input; |
| 172 |
|
| 173 |
if (o.showPerPerson !== false) { |
| 174 |
var par = makeRow('Passengers', 'people'); |
| 175 |
par.input.value = pax; |
| 176 |
par.input.min = '1'; |
| 177 |
par.input.step = '1'; |
| 178 |
par.input.addEventListener('input', function () { pax = parseInt(par.input.value) || 1; update(); }); |
| 179 |
paxInput = par.input; |
| 180 |
inputSection.appendChild(dr.row); |
| 181 |
inputSection.appendChild(er.row); |
| 182 |
inputSection.appendChild(pr.row); |
| 183 |
inputSection.appendChild(par.row); |
| 184 |
} else { |
| 185 |
inputSection.appendChild(dr.row); |
| 186 |
inputSection.appendChild(er.row); |
| 187 |
inputSection.appendChild(pr.row); |
| 188 |
} |
| 189 |
} |
| 190 |
buildInputSection(); |
| 191 |
|
| 192 |
/* Result area */ |
| 193 |
var resultArea = document.createElement('div'); |
| 194 |
resultArea.style.cssText = 'background:' + (o.resultBg || '#f5f3ff') + ';border:2px solid ' + (o.resultBorder || '#ede9fe') + ';border-radius:' + (o.cardRadius || 16) + 'px;padding:24px;text-align:center;margin:24px 0'; |
| 195 |
card.appendChild(resultArea); |
| 196 |
|
| 197 |
/* Stat cards */ |
| 198 |
var statRow = document.createElement('div'); |
| 199 |
statRow.className = 'bkbg-fuel-stat-cards'; |
| 200 |
card.appendChild(statRow); |
| 201 |
|
| 202 |
function update() { |
| 203 |
var res = calcFuel(dist, eff, price, unitKey); |
| 204 |
var totalCost = res.cost; |
| 205 |
var perPerson = pax > 0 ? totalCost / pax : totalCost; |
| 206 |
var co2 = co2Kg(res.litres); |
| 207 |
|
| 208 |
resultArea.innerHTML = |
| 209 |
'<div style="font-size:13px;color:' + (o.subtitleColor || '#6b7280') + ';margin-bottom:4px">Total Fuel Cost</div>' + |
| 210 |
'<div class="bkbg-fuel-result-value" style="color:' + (o.totalColor || accent) + '">' + esc(cur + totalCost.toFixed(2)) + '</div>' + |
| 211 |
(o.showRoundTrip !== false ? '<div style="font-size:12px;color:' + (o.subtitleColor || '#6b7280') + ';margin-top:6px">one way · ' + esc(cur + (totalCost * 2).toFixed(2)) + ' round trip</div>' : ''); |
| 212 |
|
| 213 |
statRow.innerHTML = ''; |
| 214 |
var stats = []; |
| 215 |
if (o.showLitres !== false) stats.push({ val: res.litres.toFixed(1) + 'L / ' + res.gallons.toFixed(1) + 'gal', lbl: 'Fuel Used', color: o.labelColor || '#374151' }); |
| 216 |
if (o.showPerPerson !== false) stats.push({ val: cur + perPerson.toFixed(2), lbl: 'Per Person', color: accent }); |
| 217 |
if (o.showCO2 !== false) stats.push({ val: co2.toFixed(1) + ' kg', lbl: 'CO₂ Emitted', color: o.co2Color || '#10b981' }); |
| 218 |
|
| 219 |
stats.forEach(function (st) { |
| 220 |
var sc = document.createElement('div'); |
| 221 |
sc.className = 'bkbg-fuel-stat-card'; |
| 222 |
sc.style.cssText = 'background:' + (o.statCardBg || '#f9fafb') + ';border-radius:12px;padding:16px;text-align:center;flex:1;min-width:100px'; |
| 223 |
sc.innerHTML = '<div class="bkbg-fuel-stat-val" style="color:' + st.color + '">' + esc(st.val) + '</div><div class="bkbg-fuel-stat-lbl" style="color:' + (o.subtitleColor || '#6b7280') + '">' + esc(st.lbl) + '</div>'; |
| 224 |
statRow.appendChild(sc); |
| 225 |
}); |
| 226 |
} |
| 227 |
|
| 228 |
update(); |
| 229 |
} |
| 230 |
|
| 231 |
document.querySelectorAll('.bkbg-fuel-app').forEach(buildApp); |
| 232 |
})(); |
| 233 |
|