| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var POPULAR_ZONES = [ |
| 5 |
'America/New_York','America/Chicago','America/Denver','America/Los_Angeles', |
| 6 |
'America/Toronto','America/Mexico_City','America/Sao_Paulo','Europe/London', |
| 7 |
'Europe/Paris','Europe/Berlin','Europe/Moscow','Africa/Lagos','Asia/Dubai', |
| 8 |
'Asia/Kolkata','Asia/Shanghai','Asia/Tokyo','Asia/Singapore','Australia/Sydney', |
| 9 |
'Pacific/Auckland','Pacific/Honolulu','UTC' |
| 10 |
]; |
| 11 |
|
| 12 |
function fmtZoneName(tz) { |
| 13 |
return tz.replace(/_/g, ' ').replace('/', ' / '); |
| 14 |
} |
| 15 |
|
| 16 |
function getDisplayTime(dateObj, tz, use12, showDate) { |
| 17 |
try { |
| 18 |
var opts = { timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: !!use12 }; |
| 19 |
if (showDate) { |
| 20 |
opts.weekday = 'short'; |
| 21 |
opts.month = 'short'; |
| 22 |
opts.day = 'numeric'; |
| 23 |
} |
| 24 |
return dateObj.toLocaleString('en-US', opts); |
| 25 |
} catch (e) { return '—'; } |
| 26 |
} |
| 27 |
|
| 28 |
function getOffset(dateObj, tz) { |
| 29 |
try { |
| 30 |
var inZone = new Date(dateObj.toLocaleString('en-US', {timeZone: tz})); |
| 31 |
var inUtc = new Date(dateObj.toLocaleString('en-US', {timeZone: 'UTC'})); |
| 32 |
var diff = Math.round((inZone - inUtc) / 60000); |
| 33 |
var sign = diff >= 0 ? '+' : '-'; |
| 34 |
var abs = Math.abs(diff); |
| 35 |
return 'UTC' + sign + Math.floor(abs / 60) + (abs % 60 ? ':' + String(abs % 60).padStart(2, '0') : ''); |
| 36 |
} catch (e) { return ''; } |
| 37 |
} |
| 38 |
|
| 39 |
function getNowLocal() { |
| 40 |
var d = new Date(); d.setSeconds(0, 0); |
| 41 |
var pad = function (n) { return String(n).padStart(2, '0'); }; |
| 42 |
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes()); |
| 43 |
} |
| 44 |
|
| 45 |
function init(app) { |
| 46 |
var a; |
| 47 |
try { a = JSON.parse(app.getAttribute('data-opts')); } catch (e) { return; } |
| 48 |
|
| 49 |
var accent = a.accentColor || '#6c3fb5'; |
| 50 |
var targets = String(a.targetZones || 'Europe/London,Europe/Paris,Asia/Tokyo,America/Los_Angeles,Australia/Sydney') |
| 51 |
.split(',').map(function (s) { return s.trim(); }).filter(Boolean); |
| 52 |
|
| 53 |
app.style.paddingTop = (a.paddingTop || 60) + 'px'; |
| 54 |
app.style.paddingBottom = (a.paddingBottom || 60) + 'px'; |
| 55 |
if (a.sectionBg) app.style.background = a.sectionBg; |
| 56 |
app.innerHTML = ''; |
| 57 |
|
| 58 |
var wrap = document.createElement('div'); |
| 59 |
wrap.className = 'bkbg-tzc-wrap'; |
| 60 |
wrap.style.maxWidth = (a.maxWidth || 540) + 'px'; |
| 61 |
wrap.style.borderRadius = (a.cardRadius || 16) + 'px'; |
| 62 |
wrap.style.background = a.cardBg || '#fff'; |
| 63 |
app.appendChild(wrap); |
| 64 |
|
| 65 |
if (a.showTitle || a.showSubtitle) { |
| 66 |
var header = document.createElement('div'); |
| 67 |
header.className = 'bkbg-tzc-header'; |
| 68 |
if (a.showTitle && a.title) { |
| 69 |
var title = document.createElement('div'); title.className = 'bkbg-tzc-title'; |
| 70 |
title.textContent = a.title; |
| 71 |
if (a.titleColor) title.style.color = a.titleColor; |
| 72 |
header.appendChild(title); |
| 73 |
} |
| 74 |
if (a.showSubtitle && a.subtitle) { |
| 75 |
var sub = document.createElement('div'); sub.className = 'bkbg-tzc-subtitle'; |
| 76 |
sub.textContent = a.subtitle; if (a.subtitleColor) sub.style.color = a.subtitleColor; |
| 77 |
header.appendChild(sub); |
| 78 |
} |
| 79 |
wrap.appendChild(header); |
| 80 |
} |
| 81 |
|
| 82 |
// Source card |
| 83 |
var sourceCard = document.createElement('div'); |
| 84 |
sourceCard.className = 'bkbg-tzc-source'; |
| 85 |
sourceCard.style.background = a.sourceBg || accent; |
| 86 |
sourceCard.style.borderRadius = (a.rowRadius || 10) + 'px'; |
| 87 |
|
| 88 |
var inputsRow = document.createElement('div'); |
| 89 |
inputsRow.className = 'bkbg-tzc-source-inputs'; |
| 90 |
|
| 91 |
// Datetime input |
| 92 |
var dtGroup = document.createElement('div'); dtGroup.className = 'bkbg-tzc-source-group'; |
| 93 |
var dtLabel = document.createElement('label'); dtLabel.className = 'bkbg-tzc-source-label'; dtLabel.style.color = a.sourceColor||'#fff'; dtLabel.textContent = 'Date & Time'; |
| 94 |
var dtInput = document.createElement('input'); dtInput.style.colorScheme = 'dark'; dtInput.type = 'datetime-local'; dtInput.className = 'bkbg-tzc-dt-input'; dtInput.style.color = a.sourceColor||'#fff'; dtInput.value = getNowLocal(); |
| 95 |
dtGroup.appendChild(dtLabel); dtGroup.appendChild(dtInput); |
| 96 |
|
| 97 |
// Timezone select |
| 98 |
var tzGroup = document.createElement('div'); tzGroup.className = 'bkbg-tzc-source-group'; |
| 99 |
var tzLabel = document.createElement('label'); tzLabel.className = 'bkbg-tzc-source-label'; tzLabel.style.color = a.sourceColor||'#fff'; tzLabel.textContent = 'Source Time Zone'; |
| 100 |
var tzSel = document.createElement('select'); tzSel.className = 'bkbg-tzc-tz-select'; tzSel.style.color = a.sourceColor||'#fff'; |
| 101 |
POPULAR_ZONES.forEach(function (z) { |
| 102 |
var opt = document.createElement('option'); opt.value = z; opt.textContent = fmtZoneName(z); |
| 103 |
if (z === (a.sourceZone || 'America/New_York')) opt.selected = true; |
| 104 |
tzSel.appendChild(opt); |
| 105 |
}); |
| 106 |
tzGroup.appendChild(tzLabel); tzGroup.appendChild(tzSel); |
| 107 |
|
| 108 |
inputsRow.appendChild(dtGroup); inputsRow.appendChild(tzGroup); |
| 109 |
|
| 110 |
var srcTimeEl = document.createElement('div'); srcTimeEl.className = 'bkbg-tzc-source-time'; srcTimeEl.style.color = a.sourceColor||'#fff'; |
| 111 |
var srcOffsetEl = document.createElement('div'); srcOffsetEl.className = 'bkbg-tzc-source-offset'; srcOffsetEl.style.color = a.sourceColor||'#fff'; |
| 112 |
if (!a.showOffset) srcOffsetEl.style.display = 'none'; |
| 113 |
|
| 114 |
sourceCard.appendChild(inputsRow); |
| 115 |
sourceCard.appendChild(srcTimeEl); |
| 116 |
sourceCard.appendChild(srcOffsetEl); |
| 117 |
wrap.appendChild(sourceCard); |
| 118 |
|
| 119 |
// Target rows |
| 120 |
var rowsContainer = document.createElement('div'); rowsContainer.className = 'bkbg-tzc-rows'; |
| 121 |
var rowEls = targets.map(function (tz) { |
| 122 |
var row = document.createElement('div'); row.className = 'bkbg-tzc-row'; row.style.background = a.rowBg||'#f9fafb'; row.style.borderColor = a.rowBorder||'#e5e7eb'; row.style.borderRadius = (a.rowRadius||10)+'px'; |
| 123 |
var info = document.createElement('div'); |
| 124 |
var zoneEl = document.createElement('div'); zoneEl.className = 'bkbg-tzc-row-zone'; zoneEl.style.color = a.rowZoneColor||'#6b7280'; zoneEl.textContent = fmtZoneName(tz); |
| 125 |
var offEl = document.createElement('div'); offEl.className = 'bkbg-tzc-row-offset'; if (!a.showOffset) offEl.style.display = 'none'; |
| 126 |
var timeEl = document.createElement('div'); timeEl.className = 'bkbg-tzc-row-time'; timeEl.style.color = a.rowTimeColor||'#1f2937'; |
| 127 |
info.appendChild(zoneEl); info.appendChild(offEl); |
| 128 |
row.appendChild(info); row.appendChild(timeEl); |
| 129 |
rowsContainer.appendChild(row); |
| 130 |
return { tz: tz, timeEl: timeEl, offEl: offEl }; |
| 131 |
}); |
| 132 |
wrap.appendChild(rowsContainer); |
| 133 |
|
| 134 |
function update() { |
| 135 |
var val = dtInput.value; |
| 136 |
if (!val) return; |
| 137 |
// Build the date as if the user's typed time is in the source zone |
| 138 |
// We interpret dtInput value naively as a local Date then display via Intl |
| 139 |
var dateObj = new Date(val.replace('T', ' ')); // naive local datetime |
| 140 |
if (isNaN(dateObj.getTime())) dateObj = new Date(); |
| 141 |
|
| 142 |
var srcZone = tzSel.value; |
| 143 |
srcTimeEl.textContent = getDisplayTime(dateObj, srcZone, a.use12Hour, a.showDate); |
| 144 |
srcOffsetEl.textContent = fmtZoneName(srcZone) + ' · ' + getOffset(dateObj, srcZone); |
| 145 |
|
| 146 |
rowEls.forEach(function (r) { |
| 147 |
r.timeEl.textContent = getDisplayTime(dateObj, r.tz, a.use12Hour, a.showDate); |
| 148 |
r.offEl.textContent = getOffset(dateObj, r.tz); |
| 149 |
}); |
| 150 |
} |
| 151 |
|
| 152 |
dtInput.addEventListener('input', update); |
| 153 |
tzSel.addEventListener('change', update); |
| 154 |
update(); |
| 155 |
} |
| 156 |
|
| 157 |
document.querySelectorAll('.bkbg-tzc-app').forEach(init); |
| 158 |
})(); |
| 159 |
|