| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function pad2(n) { return String(Math.abs(Math.floor(n))).padStart(2,'0'); } |
| 5 |
|
| 6 |
function fmtHMS(totalSec) { |
| 7 |
var neg = totalSec < 0; |
| 8 |
var s = Math.abs(totalSec); |
| 9 |
var h = Math.floor(s / 3600); s -= h * 3600; |
| 10 |
var m = Math.floor(s / 60); s -= m * 60; |
| 11 |
return (neg ? '−' : '') + pad2(h) + ':' + pad2(m) + ':' + pad2(s); |
| 12 |
} |
| 13 |
|
| 14 |
function toSec(h, m, s) { |
| 15 |
return (parseInt(h) || 0) * 3600 + (parseInt(m) || 0) * 60 + (parseInt(s) || 0); |
| 16 |
} |
| 17 |
|
| 18 |
function initBlock(app) { |
| 19 |
var opts = {}; |
| 20 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {} |
| 21 |
|
| 22 |
var accent = opts.accentColor || '#6c3fb5'; |
| 23 |
var resultBg = opts.resultBg || accent; |
| 24 |
var resultColor = opts.resultColor || '#fff'; |
| 25 |
var statBg = opts.statBg || '#f3f4f6'; |
| 26 |
var statBorder = opts.statBorder || '#e5e7eb'; |
| 27 |
var statValue = opts.statValue || '#111827'; |
| 28 |
var statLabel = opts.statLabel || '#6b7280'; |
| 29 |
var opActiveBg = opts.opActiveBg || accent; |
| 30 |
var opActiveClr = opts.opActiveColor || '#fff'; |
| 31 |
var opInactiveBg = opts.opInactiveBg || '#f3f4f6'; |
| 32 |
var opInactiveClr= opts.opInactiveColor|| '#374151'; |
| 33 |
var inputBorder = opts.inputBorder || '#e5e7eb'; |
| 34 |
var labelColor = opts.labelColor || '#374151'; |
| 35 |
var cardRadius = (opts.cardRadius !== undefined ? opts.cardRadius : 16) + 'px'; |
| 36 |
var inputRadius = (opts.inputRadius !== undefined ? opts.inputRadius : 8) + 'px'; |
| 37 |
var showSec = opts.showSeconds !== false; |
| 38 |
var showTotalSec = opts.showTotalSec !== false; |
| 39 |
var showDays = opts.showDaysBreakdown !== false; |
| 40 |
|
| 41 |
var state = { op: opts.defaultOp || 'add' }; |
| 42 |
|
| 43 |
var wrap = document.createElement('div'); |
| 44 |
wrap.className = 'bkbg-tdc-wrap'; |
| 45 |
wrap.style.borderRadius = cardRadius; |
| 46 |
if (opts.cardBg) wrap.style.background = opts.cardBg; |
| 47 |
app.appendChild(wrap); |
| 48 |
|
| 49 |
// Header |
| 50 |
if (opts.showTitle || opts.showSubtitle) { |
| 51 |
var hdr = document.createElement('div'); hdr.className = 'bkbg-tdc-header'; |
| 52 |
if (opts.showTitle && opts.title) { |
| 53 |
var ti = document.createElement('div'); ti.className = 'bkbg-tdc-title'; |
| 54 |
if (opts.titleColor) ti.style.color = opts.titleColor; |
| 55 |
ti.textContent = opts.title; hdr.appendChild(ti); |
| 56 |
} |
| 57 |
if (opts.showSubtitle && opts.subtitle) { |
| 58 |
var su = document.createElement('div'); su.className = 'bkbg-tdc-subtitle'; |
| 59 |
if (opts.subtitleColor) su.style.color = opts.subtitleColor; |
| 60 |
su.textContent = opts.subtitle; hdr.appendChild(su); |
| 61 |
} |
| 62 |
wrap.appendChild(hdr); |
| 63 |
} |
| 64 |
|
| 65 |
// Op buttons |
| 66 |
var opsRow = document.createElement('div'); opsRow.className = 'bkbg-tdc-ops'; |
| 67 |
var btnAdd = document.createElement('button'); btnAdd.className = 'bkbg-tdc-op'; btnAdd.type = 'button'; btnAdd.textContent = '+ Add'; |
| 68 |
var btnSub = document.createElement('button'); btnSub.className = 'bkbg-tdc-op'; btnSub.type = 'button'; btnSub.textContent = '− Subtract'; |
| 69 |
opsRow.appendChild(btnAdd); opsRow.appendChild(btnSub); wrap.appendChild(opsRow); |
| 70 |
|
| 71 |
function styleOp() { |
| 72 |
[btnAdd, btnSub].forEach(function(btn, i) { |
| 73 |
var active = (i === 0 && state.op === 'add') || (i === 1 && state.op === 'sub'); |
| 74 |
btn.style.background = active ? opActiveBg : opInactiveBg; |
| 75 |
btn.style.color = active ? opActiveClr : opInactiveClr; |
| 76 |
btn.classList.toggle('active', active); |
| 77 |
}); |
| 78 |
opSymEl.textContent = state.op === 'add' ? '+' : '−'; |
| 79 |
resultLabelEl.textContent = state.op === 'add' ? 'Total Duration' : 'Difference'; |
| 80 |
calc(); |
| 81 |
} |
| 82 |
|
| 83 |
btnAdd.addEventListener('click', function(){ state.op = 'add'; styleOp(); }); |
| 84 |
btnSub.addEventListener('click', function(){ state.op = 'sub'; styleOp(); }); |
| 85 |
|
| 86 |
// Duration builder helper |
| 87 |
function makeDurBlock(label, defH, defM, defS) { |
| 88 |
var block = document.createElement('div'); block.className = 'bkbg-tdc-dur-block'; |
| 89 |
var lbl = document.createElement('div'); lbl.className = 'bkbg-tdc-dur-lbl'; lbl.style.color = labelColor; lbl.textContent = label; |
| 90 |
var grid = document.createElement('div'); grid.className = 'bkbg-tdc-dur-grid' + (showSec ? '' : ' no-sec'); |
| 91 |
block.appendChild(lbl); block.appendChild(grid); |
| 92 |
|
| 93 |
var inputs = {}; |
| 94 |
[['h', 'Hours', defH], ['m', 'Minutes', defM], showSec && ['s', 'Seconds', defS]].forEach(function(f) { |
| 95 |
if (!f) return; |
| 96 |
var cell = document.createElement('div'); |
| 97 |
var fl = document.createElement('label'); fl.className = 'bkbg-tdc-field-label'; fl.style.color = labelColor; fl.textContent = f[1]; |
| 98 |
var fi = document.createElement('input'); fi.type = 'number'; fi.className = 'bkbg-tdc-input'; fi.min = 0; fi.value = f[2]; |
| 99 |
fi.style.borderColor = inputBorder; fi.style.borderRadius = inputRadius; |
| 100 |
fi.addEventListener('input', calc); |
| 101 |
cell.appendChild(fl); cell.appendChild(fi); grid.appendChild(cell); |
| 102 |
inputs[f[0]] = fi; |
| 103 |
}); |
| 104 |
if (!showSec) inputs['s'] = { value: '0' }; |
| 105 |
return { block: block, inputs: inputs }; |
| 106 |
} |
| 107 |
|
| 108 |
var dur1 = makeDurBlock('Duration A', opts.defaultH1 || 1, opts.defaultM1 || 30, opts.defaultS1 || 0); |
| 109 |
wrap.appendChild(dur1.block); |
| 110 |
|
| 111 |
var opSymEl = document.createElement('div'); opSymEl.className = 'bkbg-tdc-operator'; opSymEl.style.color = accent; opSymEl.textContent = '+'; |
| 112 |
wrap.appendChild(opSymEl); |
| 113 |
|
| 114 |
var dur2 = makeDurBlock('Duration B', opts.defaultH2 || 0, opts.defaultM2 || 45, opts.defaultS2 || 0); |
| 115 |
wrap.appendChild(dur2.block); |
| 116 |
|
| 117 |
// Spacer |
| 118 |
var spacer = document.createElement('div'); spacer.style.height = '16px'; wrap.appendChild(spacer); |
| 119 |
|
| 120 |
// Result card |
| 121 |
var resultCard = document.createElement('div'); resultCard.className = 'bkbg-tdc-result'; |
| 122 |
resultCard.style.background = resultBg; resultCard.style.color = resultColor; resultCard.style.borderRadius = '12px'; |
| 123 |
var resultLabelEl = document.createElement('div'); resultLabelEl.className = 'bkbg-tdc-result-label'; resultLabelEl.textContent = 'Total Duration'; |
| 124 |
var resultTimeEl = document.createElement('div'); resultTimeEl.className = 'bkbg-tdc-result-time'; |
| 125 |
resultCard.appendChild(resultLabelEl); resultCard.appendChild(resultTimeEl); wrap.appendChild(resultCard); |
| 126 |
|
| 127 |
// Stats |
| 128 |
var statsRow = document.createElement('div'); statsRow.className = 'bkbg-tdc-stats'; wrap.appendChild(statsRow); |
| 129 |
var statEls = {}; |
| 130 |
function makeStat(key, label) { |
| 131 |
var s = document.createElement('div'); s.className = 'bkbg-tdc-stat'; s.style.background = statBg; s.style.borderColor = statBorder; |
| 132 |
var v = document.createElement('div'); v.className = 'bkbg-tdc-stat-val'; v.style.color = statValue; |
| 133 |
var l = document.createElement('div'); l.className = 'bkbg-tdc-stat-lbl'; l.style.color = statLabel; l.textContent = label; |
| 134 |
s.appendChild(v); s.appendChild(l); statsRow.appendChild(s); |
| 135 |
statEls[key] = v; |
| 136 |
return s; |
| 137 |
} |
| 138 |
var daysStat = showDays ? makeStat('days','Days') : null; |
| 139 |
makeStat('hours','Hours'); makeStat('mins','Minutes'); |
| 140 |
var secsStat = showSec ? makeStat('secs','Seconds') : null; |
| 141 |
var totalSecSt = showTotalSec ? makeStat('tsec','Total Seconds') : null; |
| 142 |
|
| 143 |
function calc() { |
| 144 |
var t1 = toSec(dur1.inputs.h.value, dur1.inputs.m.value, dur1.inputs.s.value); |
| 145 |
var t2 = toSec(dur2.inputs.h.value, dur2.inputs.m.value, dur2.inputs.s.value); |
| 146 |
var total = state.op === 'add' ? t1 + t2 : t1 - t2; |
| 147 |
var abs = Math.abs(total); |
| 148 |
var days = Math.floor(abs / 86400); |
| 149 |
var hours = Math.floor((abs % 86400) / 3600); |
| 150 |
var mins = Math.floor((abs % 3600) / 60); |
| 151 |
var secs = abs % 60; |
| 152 |
|
| 153 |
resultTimeEl.textContent = fmtHMS(total); |
| 154 |
if (statEls.days) statEls.days.textContent = days; |
| 155 |
if (statEls.hours) statEls.hours.textContent = hours; |
| 156 |
if (statEls.mins) statEls.mins.textContent = mins; |
| 157 |
if (statEls.secs) statEls.secs.textContent = secs; |
| 158 |
if (statEls.tsec) statEls.tsec.textContent = abs.toLocaleString(); |
| 159 |
} |
| 160 |
|
| 161 |
styleOp(); |
| 162 |
} |
| 163 |
|
| 164 |
function init() { document.querySelectorAll('.bkbg-tdc-app').forEach(initBlock); } |
| 165 |
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } |
| 166 |
})(); |
| 167 |
|