| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function getTimeParts(tz, fmt, showSec) { |
| 5 |
try { |
| 6 |
var now = new Date(); |
| 7 |
var opts = { timeZone: tz, hour: 'numeric', minute: '2-digit', second: showSec ? '2-digit' : undefined, hour12: fmt === '12h' }; |
| 8 |
var optsDate = { timeZone: tz, month: 'short', day: 'numeric', weekday: 'short' }; |
| 9 |
return { |
| 10 |
time: new Intl.DateTimeFormat('en-US', opts).format(now), |
| 11 |
date: new Intl.DateTimeFormat('en-US', optsDate).format(now) |
| 12 |
}; |
| 13 |
} catch (e) { |
| 14 |
return { time: 'Invalid TZ', date: '' }; |
| 15 |
} |
| 16 |
} |
| 17 |
|
| 18 |
function getAngles(tz) { |
| 19 |
try { |
| 20 |
var now = new Date(); |
| 21 |
var s = new Intl.DateTimeFormat('en-US', { |
| 22 |
timeZone: tz, hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false |
| 23 |
}).format(now); |
| 24 |
// s may be "03:25:42" or similar |
| 25 |
var parts = s.split(':').map(Number); |
| 26 |
var h = (parts[0] || 0) % 12, m = parts[1] || 0, sec = parts[2] || 0; |
| 27 |
return { |
| 28 |
h: (h * 30) + (m * 0.5), |
| 29 |
m: m * 6 + sec * 0.1, |
| 30 |
s: sec * 6 |
| 31 |
}; |
| 32 |
} catch (e) { |
| 33 |
return { h: 0, m: 0, s: 0 }; |
| 34 |
} |
| 35 |
} |
| 36 |
|
| 37 |
function esc(str) { |
| 38 |
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); |
| 39 |
} |
| 40 |
|
| 41 |
function svgClock(tz, o) { |
| 42 |
var size = o.analogSize || 120; |
| 43 |
var r = size / 2; |
| 44 |
var ang = getAngles(tz); |
| 45 |
var bc = o.borderColor || '#e5e7eb'; |
| 46 |
var ac = o.accentColor || '#6c3fb5'; |
| 47 |
|
| 48 |
function handLine(angle, len, color, width) { |
| 49 |
var rad = (angle - 90) * Math.PI / 180; |
| 50 |
return '<line x1="' + r + '" y1="' + r + '" x2="' + (r + len * Math.cos(rad)) + '" y2="' + (r + len * Math.sin(rad)) + '" stroke="' + color + '" stroke-width="' + width + '" stroke-linecap="round"/>'; |
| 51 |
} |
| 52 |
|
| 53 |
var ticks = ''; |
| 54 |
for (var i = 0; i < 12; i++) { |
| 55 |
var rad = (i * 30 - 90) * Math.PI / 180; |
| 56 |
var rIn = r * 0.82, rOut = r * 0.92; |
| 57 |
ticks += '<line x1="' + (r + rIn * Math.cos(rad)) + '" y1="' + (r + rIn * Math.sin(rad)) + '" x2="' + (r + rOut * Math.cos(rad)) + '" y2="' + (r + rOut * Math.sin(rad)) + '" stroke="' + ac + '" stroke-width="1.5"/>'; |
| 58 |
} |
| 59 |
|
| 60 |
var svg = '<svg width="' + size + '" height="' + size + '" viewBox="0 0 ' + size + ' ' + size + '">'; |
| 61 |
svg += '<circle cx="' + r + '" cy="' + r + '" r="' + (r - 2) + '" fill="' + (o.clockFace || '#f5f3ff') + '" stroke="' + ac + '" stroke-width="2"/>'; |
| 62 |
svg += ticks; |
| 63 |
svg += handLine(ang.h, r * 0.5, o.handHour || '#1e1b4b', 3); |
| 64 |
svg += handLine(ang.m, r * 0.7, o.handMin || '#6c3fb5', 2); |
| 65 |
if (o.showSeconds !== false) svg += handLine(ang.s, r * 0.75, o.handSec || '#ef4444', 1); |
| 66 |
svg += '<circle cx="' + r + '" cy="' + r + '" r="3" fill="' + (o.handSec || '#ef4444') + '"/>'; |
| 67 |
svg += '</svg>'; |
| 68 |
return svg; |
| 69 |
} |
| 70 |
|
| 71 |
function buildCards(container, o) { |
| 72 |
var cols = o.columns || 4; |
| 73 |
var gap = (o.gap || 16) + 'px'; |
| 74 |
|
| 75 |
container.style.display = 'grid'; |
| 76 |
container.style.gridTemplateColumns = 'repeat(' + cols + ', 1fr)'; |
| 77 |
container.style.gap = gap; |
| 78 |
container.style.maxWidth = (o.maxWidth || 960) + 'px'; |
| 79 |
container.style.margin = '0 auto'; |
| 80 |
|
| 81 |
container.innerHTML = ''; |
| 82 |
(o.zones || []).forEach(function (zone) { |
| 83 |
var card = document.createElement('div'); |
| 84 |
card.className = 'bkbg-wc-card'; |
| 85 |
card.style.cssText = [ |
| 86 |
'background:' + (o.cardBg || '#fff'), |
| 87 |
'border:1px solid ' + (o.borderColor || '#e5e7eb'), |
| 88 |
'border-radius:' + (o.cardRadius || 16) + 'px', |
| 89 |
'padding:' + (o.cardPadding || 24) + 'px', |
| 90 |
'display:flex', |
| 91 |
'flex-direction:column', |
| 92 |
'align-items:center', |
| 93 |
'gap:8px', |
| 94 |
'box-sizing:border-box' |
| 95 |
].join(';'); |
| 96 |
|
| 97 |
card.dataset.tz = zone.tz; |
| 98 |
card.dataset.label = zone.label; |
| 99 |
container.appendChild(card); |
| 100 |
}); |
| 101 |
} |
| 102 |
|
| 103 |
function updateCards(container, o) { |
| 104 |
var cards = container.querySelectorAll('.bkbg-wc-card'); |
| 105 |
cards.forEach(function (card) { |
| 106 |
var tz = card.dataset.tz; |
| 107 |
var label = card.dataset.label; |
| 108 |
var parts = getTimeParts(tz, o.timeFormat, o.showSeconds !== false); |
| 109 |
|
| 110 |
var inner = ''; |
| 111 |
if (o.clockStyle === 'analog') { |
| 112 |
inner += '<div class="bkbg-wc-analog">' + svgClock(tz, o) + '</div>'; |
| 113 |
} |
| 114 |
inner += '<div class="bkbg-wc-label" style="color:' + (o.labelColor || '#6b7280') + ';text-align:center">' + esc(label) + '</div>'; |
| 115 |
if (o.clockStyle !== 'analog') { |
| 116 |
inner += '<div class="bkbg-wc-time" style="color:' + (o.timeColor || '#1e1b4b') + '">' + esc(parts.time) + '</div>'; |
| 117 |
} |
| 118 |
if (o.showDate !== false) { |
| 119 |
inner += '<div class="bkbg-wc-date" style="color:' + (o.dateColor || '#9ca3af') + ';text-align:center">' + esc(parts.date) + '</div>'; |
| 120 |
} |
| 121 |
card.innerHTML = inner; |
| 122 |
}); |
| 123 |
} |
| 124 |
|
| 125 |
function buildApp(app) { |
| 126 |
var opts; |
| 127 |
try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) { return; } |
| 128 |
var o = opts; |
| 129 |
|
| 130 |
app.style.paddingTop = (o.paddingTop || 60) + 'px'; |
| 131 |
app.style.paddingBottom = (o.paddingBottom || 60) + 'px'; |
| 132 |
if (o.sectionBg) app.style.background = o.sectionBg; |
| 133 |
|
| 134 |
/* Header */ |
| 135 |
if (o.showTitle || o.showSubtitle) { |
| 136 |
var hdr = document.createElement('div'); |
| 137 |
hdr.style.cssText = 'text-align:center;margin-bottom:32px'; |
| 138 |
if (o.showTitle && o.title) { |
| 139 |
var ht = document.createElement('h3'); |
| 140 |
ht.className = 'bkbg-wc-title'; |
| 141 |
ht.textContent = o.title; |
| 142 |
ht.style.cssText = 'color:' + (o.titleColor || '#1e1b4b') + ';margin:0 0 8px'; |
| 143 |
hdr.appendChild(ht); |
| 144 |
} |
| 145 |
if (o.showSubtitle && o.subtitle) { |
| 146 |
var hs = document.createElement('p'); |
| 147 |
hs.className = 'bkbg-wc-subtitle'; |
| 148 |
hs.textContent = o.subtitle; |
| 149 |
hs.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';margin:0'; |
| 150 |
hdr.appendChild(hs); |
| 151 |
} |
| 152 |
app.appendChild(hdr); |
| 153 |
} |
| 154 |
|
| 155 |
/* Grid container */ |
| 156 |
var grid = document.createElement('div'); |
| 157 |
grid.className = 'bkbg-wc-grid'; |
| 158 |
app.appendChild(grid); |
| 159 |
|
| 160 |
buildCards(grid, o); |
| 161 |
updateCards(grid, o); |
| 162 |
|
| 163 |
/* Tick every second */ |
| 164 |
setInterval(function () { |
| 165 |
updateCards(grid, o); |
| 166 |
}, 1000); |
| 167 |
} |
| 168 |
|
| 169 |
document.querySelectorAll('.bkbg-wc-app').forEach(buildApp); |
| 170 |
})(); |
| 171 |
|