172 lines
5.4 KiB
JavaScript
172 lines
5.4 KiB
JavaScript
// Minimal CoBiE analog clock logic wrapped in its own scope to
|
|
// avoid clashes with variables from other scripts on the page.
|
|
(function () {
|
|
const {
|
|
COBIE_EPOCH,
|
|
COBIE_UNITS,
|
|
floorDiv,
|
|
getTAIOffsetAt,
|
|
toCobiets
|
|
} = window.Cobie;
|
|
|
|
// CoBiE helpers pulled from cobie.js
|
|
|
|
function placeMarkers() {
|
|
const clock = document.getElementById('clock');
|
|
let markers = clock.querySelectorAll('.marker');
|
|
let ticks = clock.querySelectorAll('.tick');
|
|
|
|
// Create markers if they don't exist yet
|
|
if (markers.length === 0) {
|
|
for (let i = 0; i < 16; i++) {
|
|
const m = document.createElement('div');
|
|
m.className = 'marker';
|
|
m.textContent = i.toString(16).toUpperCase();
|
|
clock.appendChild(m);
|
|
}
|
|
markers = clock.querySelectorAll('.marker');
|
|
}
|
|
|
|
// Create tick marks once
|
|
if (ticks.length === 0) {
|
|
for (let i = 0; i < 256; i++) {
|
|
const t = document.createElement('div');
|
|
t.classList.add('tick');
|
|
if (i % 16 === 0) t.classList.add('big');
|
|
else if (i % 8 === 0) t.classList.add('mid');
|
|
// insert before markers so digits sit on top
|
|
clock.insertBefore(t, clock.firstChild);
|
|
}
|
|
ticks = clock.querySelectorAll('.tick');
|
|
}
|
|
|
|
// Position markers based on the current clock size
|
|
// Move markers slightly inward and tweak the center position so
|
|
// the ring of ticks lines up perfectly with the border.
|
|
const borderOffset = clock.offsetWidth > 300 ? 35 : 25;
|
|
const centerAdjust = { x: -1, y: -1 };
|
|
const markerRadius = clock.offsetWidth / 2 - borderOffset;
|
|
markers.forEach((m, i) => {
|
|
const angle = (i / 16) * 2 * Math.PI;
|
|
const x = markerRadius * Math.sin(angle);
|
|
const y = -markerRadius * Math.cos(angle);
|
|
m.style.left = `${clock.offsetWidth / 2 + x + centerAdjust.x}px`;
|
|
m.style.top = `${clock.offsetHeight / 2 + y + centerAdjust.y}px`;
|
|
});
|
|
|
|
// Tick lengths based on the marker radius
|
|
const lenBig = markerRadius * 0.12;
|
|
const lenMid = markerRadius * 0.08;
|
|
const lenSmall = markerRadius * 0.05;
|
|
|
|
ticks.forEach((t, i) => {
|
|
let len = lenSmall;
|
|
if (t.classList.contains('big')) len = lenBig;
|
|
else if (t.classList.contains('mid')) len = lenMid;
|
|
const outerR = clock.offsetWidth / 2 - 2;
|
|
const innerR = outerR - len;
|
|
const angle = (i / 256) * 2 * Math.PI;
|
|
const x = innerR * Math.sin(angle);
|
|
const y = -innerR * Math.cos(angle);
|
|
t.style.height = `${len}px`;
|
|
t.style.left = `${clock.offsetWidth / 2 + x - t.offsetWidth / 2 + centerAdjust.x}px`;
|
|
t.style.top = `${clock.offsetHeight / 2 + y + centerAdjust.y}px`;
|
|
t.style.transform = `rotate(${angle}rad)`;
|
|
if (clock.offsetWidth < 200 && !t.classList.contains('big') && !t.classList.contains('mid')) {
|
|
t.style.display = 'none';
|
|
} else {
|
|
t.style.display = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
const lastAngles = {
|
|
handXeno: 0,
|
|
handQuantic: 0,
|
|
handChronon: 0,
|
|
handEonstrip: 0,
|
|
handMegasequence: 0
|
|
};
|
|
|
|
function rotateHand(id, angle) {
|
|
const el = document.getElementById(id);
|
|
if (!el) return;
|
|
const prev = lastAngles[id];
|
|
|
|
if (angle < prev) {
|
|
// When wrapping around (e.g. 15 → 0), animate to one full turn
|
|
// and then snap back to the new angle to avoid a jump.
|
|
const target = angle + 360;
|
|
const handle = () => {
|
|
el.removeEventListener('transitionend', handle);
|
|
// Snap back without animation
|
|
el.style.transition = 'none';
|
|
el.style.transform = `rotate(${angle}deg)`;
|
|
void el.offsetWidth;
|
|
el.style.transition = '';
|
|
};
|
|
el.addEventListener('transitionend', handle, { once: true });
|
|
el.style.transform = `rotate(${target}deg)`;
|
|
} else {
|
|
el.style.transform = `rotate(${angle}deg)`;
|
|
}
|
|
|
|
lastAngles[id] = angle;
|
|
}
|
|
|
|
function renderClock(cob) {
|
|
// Use fractional progress within each unit so angles stay small
|
|
const xf = (cob % COBIE_UNITS.quantic) / COBIE_UNITS.quantic;
|
|
const qf = (cob % COBIE_UNITS.chronon) / COBIE_UNITS.chronon;
|
|
const cf = (cob % COBIE_UNITS.eonstrip) / COBIE_UNITS.eonstrip;
|
|
const ef = (cob % COBIE_UNITS.megasequence) / COBIE_UNITS.megasequence;
|
|
const mf = (cob % COBIE_UNITS.cosmocycle) / COBIE_UNITS.cosmocycle;
|
|
rotateHand('handXeno', xf * 360);
|
|
rotateHand('handQuantic', qf * 360);
|
|
rotateHand('handChronon', cf * 360);
|
|
rotateHand('handEonstrip', ef * 360);
|
|
rotateHand('handMegasequence', mf * 360);
|
|
}
|
|
|
|
function updateClock() {
|
|
renderClock(toCobiets(new Date()));
|
|
}
|
|
|
|
let intervalId = null;
|
|
|
|
function startClock() {
|
|
clearInterval(intervalId);
|
|
updateClock();
|
|
intervalId = setInterval(updateClock, 1000);
|
|
}
|
|
|
|
function showTime(cob) {
|
|
clearInterval(intervalId);
|
|
renderClock(cob);
|
|
}
|
|
|
|
function initClock() {
|
|
placeMarkers();
|
|
startClock();
|
|
const clk = document.getElementById('clock');
|
|
if (clk) {
|
|
clk.addEventListener('click', () => {
|
|
document.body.classList.toggle('fullscreen-clock');
|
|
// Re-position markers after toggling fullscreen
|
|
requestAnimationFrame(placeMarkers);
|
|
});
|
|
}
|
|
window.addEventListener('resize', placeMarkers);
|
|
window.CobieClock = {
|
|
start: startClock,
|
|
showTime
|
|
};
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initClock);
|
|
} else {
|
|
initClock();
|
|
}
|
|
})();
|