| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function calcSalesTax(amount, rate) { |
| 5 |
var tax = amount * (rate / 100); |
| 6 |
return { net: amount, tax: tax, total: amount + tax }; |
| 7 |
} |
| 8 |
function calcReverse(total, rate) { |
| 9 |
var net = total / (1 + rate / 100); |
| 10 |
return { net: net, tax: total - net, total: total }; |
| 11 |
} |
| 12 |
function fmt(val, cur, pos) { |
| 13 |
return pos === 'after' ? val.toFixed(2) + cur : cur + val.toFixed(2); |
| 14 |
} |
| 15 |
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); } |
| 16 |
|
| 17 |
function buildApp(app) { |
| 18 |
var opts; |
| 19 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) { return; } |
| 20 |
var o = opts; |
| 21 |
var accent = o.accentColor || '#6c3fb5'; |
| 22 |
var cur = o.currency || '$'; |
| 23 |
var pos = o.currencyPos || 'before'; |
| 24 |
var maxW = (o.maxWidth || 580) + 'px'; |
| 25 |
var iRad = (o.inputRadius || 8) + 'px'; |
| 26 |
var cRad = (o.cardRadius || 16) + 'px'; |
| 27 |
var taxLbl = o.taxLabel || 'Sales Tax'; |
| 28 |
|
| 29 |
app.style.paddingTop = (o.paddingTop || 60) + 'px'; |
| 30 |
app.style.paddingBottom = (o.paddingBottom || 60) + 'px'; |
| 31 |
if (o.sectionBg) app.style.background = o.sectionBg; |
| 32 |
|
| 33 |
/* Header */ |
| 34 |
if (o.showTitle || o.showSubtitle) { |
| 35 |
var hdr = document.createElement('div'); |
| 36 |
hdr.style.cssText = 'text-align:center;margin-bottom:32px;max-width:' + maxW + ';margin-left:auto;margin-right:auto'; |
| 37 |
if (o.showTitle && o.title) { |
| 38 |
var ht = document.createElement('h3'); |
| 39 |
ht.className = 'bkbg-tax-title'; |
| 40 |
ht.textContent = o.title; |
| 41 |
ht.style.cssText = 'color:' + (o.titleColor || '#1e1b4b') + ';margin:0 0 8px'; |
| 42 |
hdr.appendChild(ht); |
| 43 |
} |
| 44 |
if (o.showSubtitle && o.subtitle) { |
| 45 |
var hs = document.createElement('p'); |
| 46 |
hs.textContent = o.subtitle; |
| 47 |
hs.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';margin:0'; |
| 48 |
hdr.appendChild(hs); |
| 49 |
} |
| 50 |
app.appendChild(hdr); |
| 51 |
} |
| 52 |
|
| 53 |
/* Card */ |
| 54 |
var card = document.createElement('div'); |
| 55 |
card.style.cssText = 'background:' + (o.cardBg || '#fff') + ';border-radius:' + cRad + ';padding:32px;max-width:' + maxW + ';margin:0 auto;box-shadow:0 4px 24px rgba(0,0,0,0.06)'; |
| 56 |
app.appendChild(card); |
| 57 |
|
| 58 |
/* State */ |
| 59 |
var mode = o.mode || 'sales'; |
| 60 |
var amount = parseFloat(o.defaultAmount) || 100; |
| 61 |
var rate = parseFloat(o.defaultRate) || 8.5; |
| 62 |
|
| 63 |
/* Mode tabs */ |
| 64 |
var modeRow = document.createElement('div'); |
| 65 |
modeRow.style.cssText = 'display:flex;gap:8px;margin-bottom:24px'; |
| 66 |
var modeBtns = {}; |
| 67 |
[['sales','Add Tax →'],['reverse','← Remove Tax']].forEach(function (pair) { |
| 68 |
var btn = document.createElement('button'); |
| 69 |
btn.className = 'bkbg-tax-mode-btn'; |
| 70 |
btn.textContent = pair[1]; |
| 71 |
btn.style.cssText = 'flex:1;padding:9px;border:none;border-radius:' + iRad + ';font-weight:600;font-size:13px;cursor:pointer'; |
| 72 |
btn.addEventListener('click', function () { mode = pair[0]; refreshMode(); update(); }); |
| 73 |
modeBtns[pair[0]] = btn; |
| 74 |
modeRow.appendChild(btn); |
| 75 |
}); |
| 76 |
card.appendChild(modeRow); |
| 77 |
|
| 78 |
function refreshMode() { |
| 79 |
Object.keys(modeBtns).forEach(function (k) { |
| 80 |
modeBtns[k].style.background = k === mode ? accent : (o.statCardBg || '#f9fafb'); |
| 81 |
modeBtns[k].style.color = k === mode ? '#fff' : (o.labelColor || '#374151'); |
| 82 |
amtLabel.textContent = mode === 'reverse' ? 'Total Price (with tax)' : 'Original Amount'; |
| 83 |
}); |
| 84 |
} |
| 85 |
|
| 86 |
/* Preset chips */ |
| 87 |
if (o.showPresets !== false && (o.presetRates || []).length) { |
| 88 |
var chipRow = document.createElement('div'); |
| 89 |
chipRow.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px'; |
| 90 |
(o.presetRates || []).forEach(function (p) { |
| 91 |
var chip = document.createElement('button'); |
| 92 |
chip.className = 'bkbg-tax-preset-chip'; |
| 93 |
chip.textContent = p.label; |
| 94 |
chip.style.cssText = 'padding:4px 12px;border:1px solid ' + accent + ';border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;background:transparent;color:' + accent; |
| 95 |
chip.addEventListener('click', function () { |
| 96 |
rate = p.rate; |
| 97 |
rateInput.value = rate; |
| 98 |
update(); |
| 99 |
}); |
| 100 |
chipRow.appendChild(chip); |
| 101 |
}); |
| 102 |
card.appendChild(chipRow); |
| 103 |
} |
| 104 |
|
| 105 |
/* Amount row */ |
| 106 |
var amtLabel, amtInput; |
| 107 |
(function () { |
| 108 |
var row = document.createElement('div'); |
| 109 |
row.className = 'bkbg-tax-input-row'; |
| 110 |
amtLabel = document.createElement('label'); |
| 111 |
amtLabel.style.cssText = 'font-size:14px;color:' + (o.labelColor || '#374151'); |
| 112 |
var right = document.createElement('div'); |
| 113 |
right.style.cssText = 'display:flex;align-items:center;gap:6px'; |
| 114 |
var pfx = document.createElement('span'); |
| 115 |
pfx.textContent = cur; |
| 116 |
pfx.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';font-size:13px'; |
| 117 |
amtInput = document.createElement('input'); |
| 118 |
amtInput.type = 'number'; amtInput.value = amount; amtInput.min = '0'; amtInput.step = '0.01'; |
| 119 |
amtInput.style.cssText = 'width:110px;text-align:right;border:1px solid #d1d5db;border-radius:' + iRad + ';padding:5px 8px;font-size:14px;box-sizing:border-box'; |
| 120 |
amtInput.addEventListener('input', function () { amount = parseFloat(amtInput.value) || 0; update(); }); |
| 121 |
if (pos === 'before') right.appendChild(pfx); |
| 122 |
right.appendChild(amtInput); |
| 123 |
if (pos === 'after') right.appendChild(pfx); |
| 124 |
row.appendChild(amtLabel); |
| 125 |
row.appendChild(right); |
| 126 |
card.appendChild(row); |
| 127 |
})(); |
| 128 |
|
| 129 |
/* Rate row */ |
| 130 |
var rateInput; |
| 131 |
(function () { |
| 132 |
var row = document.createElement('div'); |
| 133 |
row.className = 'bkbg-tax-input-row'; |
| 134 |
var lbl = document.createElement('label'); |
| 135 |
lbl.textContent = 'Tax Rate (' + taxLbl + ')'; |
| 136 |
lbl.style.cssText = 'font-size:14px;color:' + (o.labelColor || '#374151'); |
| 137 |
var right = document.createElement('div'); |
| 138 |
right.style.cssText = 'display:flex;align-items:center;gap:6px'; |
| 139 |
rateInput = document.createElement('input'); |
| 140 |
rateInput.type = 'number'; rateInput.value = rate; rateInput.min = '0'; rateInput.step = '0.001'; |
| 141 |
rateInput.style.cssText = 'width:110px;text-align:right;border:1px solid #d1d5db;border-radius:' + iRad + ';padding:5px 8px;font-size:14px;box-sizing:border-box'; |
| 142 |
rateInput.addEventListener('input', function () { rate = parseFloat(rateInput.value) || 0; update(); }); |
| 143 |
var sfx = document.createElement('span'); |
| 144 |
sfx.textContent = '%'; |
| 145 |
sfx.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';font-size:13px'; |
| 146 |
right.appendChild(rateInput); |
| 147 |
right.appendChild(sfx); |
| 148 |
row.appendChild(lbl); |
| 149 |
row.appendChild(right); |
| 150 |
card.appendChild(row); |
| 151 |
})(); |
| 152 |
|
| 153 |
/* Result box */ |
| 154 |
var resultBox = document.createElement('div'); |
| 155 |
resultBox.style.cssText = 'background:' + (o.resultBg || '#f5f3ff') + ';border:2px solid ' + (o.resultBorder || '#ede9fe') + ';border-radius:' + cRad + ';padding:20px 24px;margin-top:24px'; |
| 156 |
card.appendChild(resultBox); |
| 157 |
|
| 158 |
function update() { |
| 159 |
var res = mode === 'reverse' ? calcReverse(amount, rate) : calcSalesTax(amount, rate); |
| 160 |
var rs = o.resultSize || 48; |
| 161 |
resultBox.innerHTML = |
| 162 |
'<div class="bkbg-tax-result-grid" style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px">' + |
| 163 |
'<div class="bkbg-tax-result-col" style="flex:1;text-align:center;min-width:100px">' + |
| 164 |
'<div class="bkbg-tax-result-label" style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:' + (o.subtitleColor || '#6b7280') + ';margin-bottom:4px">Net Price</div>' + |
| 165 |
'<div class="bkbg-tax-result-val" style="font-size:' + (rs * 0.55) + 'px;font-weight:800;color:' + (o.netColor || '#10b981') + '">' + esc(fmt(res.net, cur, pos)) + '</div>' + |
| 166 |
'</div>' + |
| 167 |
'<div class="bkbg-tax-result-col" style="flex:1;text-align:center;min-width:100px">' + |
| 168 |
'<div class="bkbg-tax-result-label" style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:' + (o.subtitleColor || '#6b7280') + ';margin-bottom:4px">' + esc(taxLbl) + '</div>' + |
| 169 |
'<div class="bkbg-tax-result-val" style="font-size:' + (rs * 0.55) + 'px;font-weight:800;color:' + (o.taxColor || '#ef4444') + '">' + esc(fmt(res.tax, cur, pos)) + '</div>' + |
| 170 |
'</div>' + |
| 171 |
'<div class="bkbg-tax-result-col" style="flex:1;text-align:center;min-width:100px">' + |
| 172 |
'<div class="bkbg-tax-result-label" style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:' + (o.subtitleColor || '#6b7280') + ';margin-bottom:4px">Total Price</div>' + |
| 173 |
'<div class="bkbg-tax-result-val" style="font-size:' + (rs * 0.7) + 'px;font-weight:900;color:' + (o.totalColor || accent) + '">' + esc(fmt(res.total, cur, pos)) + '</div>' + |
| 174 |
'</div>' + |
| 175 |
'</div>'; |
| 176 |
} |
| 177 |
|
| 178 |
refreshMode(); |
| 179 |
update(); |
| 180 |
} |
| 181 |
|
| 182 |
document.querySelectorAll('.bkbg-tax-app').forEach(buildApp); |
| 183 |
})(); |
| 184 |
|