| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
function getTimeInZone(timezone) { |
| 5 |
if (!timezone || timezone === 'local') { |
| 6 |
var d = new Date(); |
| 7 |
return { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() }; |
| 8 |
} |
| 9 |
try { |
| 10 |
var parts = new Intl.DateTimeFormat('en-US', { |
| 11 |
timeZone: timezone, |
| 12 |
hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false |
| 13 |
}).formatToParts(new Date()); |
| 14 |
var vals = {}; |
| 15 |
parts.forEach(function (p) { vals[p.type] = parseInt(p.value, 10); }); |
| 16 |
return { h: vals.hour % 24, m: vals.minute, s: vals.second }; |
| 17 |
} catch (e) { |
| 18 |
var d2 = new Date(); |
| 19 |
return { h: d2.getHours(), m: d2.getMinutes(), s: d2.getSeconds() }; |
| 20 |
} |
| 21 |
} |
| 22 |
|
| 23 |
function getDateInZone(timezone) { |
| 24 |
if (!timezone || timezone === 'local') { |
| 25 |
return new Date().toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); |
| 26 |
} |
| 27 |
try { |
| 28 |
return new Intl.DateTimeFormat(undefined, { |
| 29 |
timeZone: timezone, weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' |
| 30 |
}).format(new Date()); |
| 31 |
} catch (e) { |
| 32 |
return new Date().toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); |
| 33 |
} |
| 34 |
} |
| 35 |
|
| 36 |
function getDigitalInZone(timezone) { |
| 37 |
if (!timezone || timezone === 'local') { |
| 38 |
return new Date().toLocaleTimeString(); |
| 39 |
} |
| 40 |
try { |
| 41 |
return new Intl.DateTimeFormat(undefined, { timeZone: timezone, hour: '2-digit', minute: '2-digit', second: '2-digit' }).format(new Date()); |
| 42 |
} catch (e) { |
| 43 |
return new Date().toLocaleTimeString(); |
| 44 |
} |
| 45 |
} |
| 46 |
|
| 47 |
function toRad(deg) { return (deg - 90) * Math.PI / 180; } |
| 48 |
|
| 49 |
var ns = 'http://www.w3.org/2000/svg'; |
| 50 |
|
| 51 |
function createEl(tag, attrs, children) { |
| 52 |
var el = document.createElementNS(ns, tag); |
| 53 |
if (attrs) { |
| 54 |
Object.keys(attrs).forEach(function (k) { |
| 55 |
el.setAttribute(k, attrs[k]); |
| 56 |
}); |
| 57 |
} |
| 58 |
if (children) { |
| 59 |
children.forEach(function (c) { if (c) el.appendChild(c); }); |
| 60 |
} |
| 61 |
return el; |
| 62 |
} |
| 63 |
|
| 64 |
function buildClock(opts) { |
| 65 |
var sz = opts.clockSize || 240; |
| 66 |
var r = 46; |
| 67 |
var cx = 50, cy = 50; |
| 68 |
var bw = opts.clockStyle === 'minimal' ? 2 : opts.clockStyle === 'bold' ? 9 : 4; |
| 69 |
|
| 70 |
var svg = createEl('svg', { |
| 71 |
viewBox: '0 0 100 100', |
| 72 |
width: sz, height: sz, |
| 73 |
'class': 'bkbg-clk-svg', |
| 74 |
'aria-label': 'Analog clock' |
| 75 |
}); |
| 76 |
|
| 77 |
// Border circle |
| 78 |
svg.appendChild(createEl('circle', { cx: cx, cy: cy, r: 49, fill: opts.clockBorder || '#1e1b4b' })); |
| 79 |
// Face |
| 80 |
svg.appendChild(createEl('circle', { cx: cx, cy: cy, r: 49 - bw, fill: opts.clockFace || '#ffffff' })); |
| 81 |
|
| 82 |
// Ticks |
| 83 |
if (opts.showTicks !== false) { |
| 84 |
for (var i = 0; i < 60; i++) { |
| 85 |
var isBig = i % 5 === 0; |
| 86 |
var ang = (i * 6 - 90) * Math.PI / 180; |
| 87 |
var innerR = isBig ? r - 7 : r - 4; |
| 88 |
svg.appendChild(createEl('line', { |
| 89 |
x1: cx + innerR * Math.cos(ang), y1: cy + innerR * Math.sin(ang), |
| 90 |
x2: cx + r * Math.cos(ang), y2: cy + r * Math.sin(ang), |
| 91 |
stroke: opts.tickColor || '#374151', |
| 92 |
'stroke-width': isBig ? 1.8 : 0.8, |
| 93 |
'stroke-linecap': 'round' |
| 94 |
})); |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
// Numbers |
| 99 |
if (opts.showNumbers !== false) { |
| 100 |
for (var j = 1; j <= 12; j++) { |
| 101 |
var nAng = (j * 30 - 90) * Math.PI / 180; |
| 102 |
var txt = createEl('text', { |
| 103 |
x: cx + (r - 14) * Math.cos(nAng), |
| 104 |
y: cy + (r - 14) * Math.sin(nAng), |
| 105 |
'text-anchor': 'middle', |
| 106 |
'dominant-baseline': 'middle', |
| 107 |
'font-size': 7, 'font-weight': 700, |
| 108 |
fill: opts.numberColor || '#1e1b4b', |
| 109 |
'font-family': 'system-ui, sans-serif' |
| 110 |
}); |
| 111 |
txt.textContent = j; |
| 112 |
svg.appendChild(txt); |
| 113 |
} |
| 114 |
} |
| 115 |
|
| 116 |
// Hands (created and stored for updates) |
| 117 |
var hourHand = createEl('line', { |
| 118 |
x1: cx, y1: cy, x2: cx, y2: cy - 28, |
| 119 |
stroke: opts.hourHandColor || '#1e1b4b', |
| 120 |
'stroke-width': 3.5, 'stroke-linecap': 'round' |
| 121 |
}); |
| 122 |
|
| 123 |
var minuteHand = createEl('line', { |
| 124 |
x1: cx, y1: cy, x2: cx, y2: cy - 38, |
| 125 |
stroke: opts.minuteHandColor || '#374151', |
| 126 |
'stroke-width': 2.5, 'stroke-linecap': 'round' |
| 127 |
}); |
| 128 |
|
| 129 |
var secondHand = null, secondTail = null; |
| 130 |
if (opts.showSecondHand !== false) { |
| 131 |
secondHand = createEl('line', { |
| 132 |
x1: cx, y1: cy, x2: cx, y2: cy - 40, |
| 133 |
stroke: opts.secondHandColor || '#ef4444', |
| 134 |
'stroke-width': 1.5, 'stroke-linecap': 'round', |
| 135 |
'class': 'bkbg-clk-second-hand' |
| 136 |
}); |
| 137 |
secondTail = createEl('line', { |
| 138 |
x1: cx, y1: cy, x2: cx, y2: cy + 10, |
| 139 |
stroke: opts.secondHandColor || '#ef4444', |
| 140 |
'stroke-width': 1.5, 'stroke-linecap': 'round' |
| 141 |
}); |
| 142 |
} |
| 143 |
|
| 144 |
svg.appendChild(hourHand); |
| 145 |
svg.appendChild(minuteHand); |
| 146 |
if (secondHand) svg.appendChild(secondHand); |
| 147 |
if (secondTail) svg.appendChild(secondTail); |
| 148 |
|
| 149 |
// Center dot |
| 150 |
svg.appendChild(createEl('circle', { cx: cx, cy: cy, r: 3.5, fill: opts.centerDotColor || '#ef4444' })); |
| 151 |
svg.appendChild(createEl('circle', { cx: cx, cy: cy, r: 1.5, fill: '#fff' })); |
| 152 |
|
| 153 |
function updateHands(t) { |
| 154 |
var h12 = t.h % 12; |
| 155 |
var hDeg = h12 * 30 + t.m * 0.5; |
| 156 |
var mDeg = t.m * 6 + t.s * 0.1; |
| 157 |
var sDeg = t.s * 6; |
| 158 |
|
| 159 |
function rotate(hand, angle, len) { |
| 160 |
var rad = toRad(angle); |
| 161 |
hand.setAttribute('x2', cx + len * Math.cos(rad)); |
| 162 |
hand.setAttribute('y2', cy + len * Math.sin(rad)); |
| 163 |
} |
| 164 |
|
| 165 |
rotate(hourHand, hDeg, 28); |
| 166 |
rotate(minuteHand, mDeg, 38); |
| 167 |
|
| 168 |
if (secondHand) { |
| 169 |
rotate(secondHand, sDeg, 40); |
| 170 |
var tailRad = toRad(sDeg + 180); |
| 171 |
secondTail.setAttribute('x2', cx + 10 * Math.cos(tailRad)); |
| 172 |
secondTail.setAttribute('y2', cy + 10 * Math.sin(tailRad)); |
| 173 |
} |
| 174 |
} |
| 175 |
|
| 176 |
return { svg: svg, updateHands: updateHands }; |
| 177 |
} |
| 178 |
|
| 179 |
function typoCssVarsForEl(typo, prefix, el) { |
| 180 |
if (!typo || typeof typo !== 'object') return; |
| 181 |
var map = { |
| 182 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 183 |
decoration:'text-decoration', transform:'text-transform', |
| 184 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 185 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 186 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 187 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 188 |
}; |
| 189 |
Object.keys(map).forEach(function(k) { |
| 190 |
if (typo[k] !== undefined && typo[k] !== '') { |
| 191 |
var v = typo[k]; |
| 192 |
if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) v = v + (typo.sizeUnit || 'px'); |
| 193 |
else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) v = v + (typo.lineHeightUnit || ''); |
| 194 |
else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) v = v + (typo.letterSpacingUnit || 'px'); |
| 195 |
else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) v = v + (typo.wordSpacingUnit || 'px'); |
| 196 |
el.style.setProperty(prefix + map[k], String(v)); |
| 197 |
} |
| 198 |
}); |
| 199 |
} |
| 200 |
|
| 201 |
function initBlock(root) { |
| 202 |
var optsRaw = root.getAttribute('data-opts'); |
| 203 |
var opts; |
| 204 |
try { opts = JSON.parse(optsRaw); } catch (e) { opts = {}; } |
| 205 |
|
| 206 |
// Apply background |
| 207 |
if (opts.sectionBg) root.style.background = opts.sectionBg; |
| 208 |
|
| 209 |
// Typography CSS vars |
| 210 |
typoCssVarsForEl(opts.titleTypo, '--bkbg-clk-title-', root); |
| 211 |
typoCssVarsForEl(opts.subtitleTypo, '--bkbg-clk-subtitle-', root); |
| 212 |
if (opts.fontSize && opts.fontSize !== 22) root.style.setProperty('--bkbg-clk-title-sz', opts.fontSize + 'px'); |
| 213 |
if (opts.subtitleSize && opts.subtitleSize !== 14) root.style.setProperty('--bkbg-clk-subtitle-sz', opts.subtitleSize + 'px'); |
| 214 |
|
| 215 |
// Apply title styles |
| 216 |
var titleEl = root.querySelector('.bkbg-clk-title'); |
| 217 |
if (titleEl) { |
| 218 |
titleEl.style.color = opts.titleColor || '#1e1b4b'; |
| 219 |
} |
| 220 |
var subtitleEl = root.querySelector('.bkbg-clk-subtitle'); |
| 221 |
if (subtitleEl) { |
| 222 |
subtitleEl.style.color = opts.titleColor || '#1e1b4b'; |
| 223 |
} |
| 224 |
|
| 225 |
// Build clock wrap |
| 226 |
var wrap = document.createElement('div'); |
| 227 |
wrap.className = 'bkbg-clk-clock-wrap'; |
| 228 |
root.appendChild(wrap); |
| 229 |
|
| 230 |
// Build and insert SVG clock |
| 231 |
var clock = buildClock(opts); |
| 232 |
wrap.appendChild(clock.svg); |
| 233 |
|
| 234 |
// Date element |
| 235 |
var dateEl = null; |
| 236 |
if (opts.showDate) { |
| 237 |
dateEl = document.createElement('div'); |
| 238 |
dateEl.className = 'bkbg-clk-date'; |
| 239 |
dateEl.style.color = opts.titleColor || '#1e1b4b'; |
| 240 |
wrap.appendChild(dateEl); |
| 241 |
} |
| 242 |
|
| 243 |
// Digital time element |
| 244 |
var digitalEl = null; |
| 245 |
if (opts.showDigitalTime) { |
| 246 |
digitalEl = document.createElement('div'); |
| 247 |
digitalEl.className = 'bkbg-clk-digital'; |
| 248 |
digitalEl.style.color = opts.titleColor || '#1e1b4b'; |
| 249 |
wrap.appendChild(digitalEl); |
| 250 |
} |
| 251 |
|
| 252 |
function tick() { |
| 253 |
var t = getTimeInZone(opts.timezone); |
| 254 |
clock.updateHands(t); |
| 255 |
if (dateEl) dateEl.textContent = getDateInZone(opts.timezone); |
| 256 |
if (digitalEl) digitalEl.textContent = getDigitalInZone(opts.timezone); |
| 257 |
} |
| 258 |
|
| 259 |
tick(); // initial render |
| 260 |
setInterval(tick, 1000); |
| 261 |
} |
| 262 |
|
| 263 |
document.querySelectorAll('.bkbg-clk-app').forEach(function (root) { |
| 264 |
initBlock(root); |
| 265 |
}); |
| 266 |
})(); |
| 267 |
|