CoBiE/clock.js
2025-06-15 10:21:56 +02:00

162 lines
4.8 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
const radius = clock.offsetWidth / 2 - 20;
markers.forEach((m, i) => {
const angle = (i / 16) * 2 * Math.PI;
const x = radius * Math.sin(angle);
const y = -radius * Math.cos(angle);
m.style.left = `${clock.offsetWidth / 2 + x}px`;
m.style.top = `${clock.offsetHeight / 2 + y}px`;
});
// Tick lengths based on radius
const lenBig = radius * 0.12;
const lenMid = radius * 0.08;
const lenSmall = radius * 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 innerR = radius - 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}px`;
t.style.top = `${clock.offsetHeight / 2 + y}px`;
t.style.transform = `rotate(${angle}rad)`;
});
}
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();
}
})();