// 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; function getMarkerOffset(width) { const points = [ { width: 1024, value: 2 }, { width: 450, value: 1.3 }, { width: 200, value: 0.8 } ]; // Sort points by width descending for easier handling points.sort((a, b) => b.width - a.width); for (let i = 0; i < points.length - 1; i++) { const p1 = points[i]; const p2 = points[i + 1]; if (width <= p1.width && width >= p2.width) { // Linear interpolation const t = (width - p2.width) / (p1.width - p2.width); return p2.value + t * (p1.value - p2.value); } } // Extrapolation for width > max known if (width > points[0].width) { const p1 = points[0]; const p2 = points[1]; const slope = (p1.value - p2.value) / (p1.width - p2.width); return p1.value + slope * (width - p1.width); } // Extrapolation for width < min known const p1 = points[points.length - 2]; const p2 = points[points.length - 1]; const slope = (p2.value - p1.value) / (p2.width - p1.width); return p2.value + slope * (width - p2.width); } 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'); } // Unified radius based on the actual clock size const baseRadius = clock.offsetWidth / 2; // Tick lengths relative to the clock radius const lenBig = baseRadius * 0.12; const lenMid = baseRadius * 0.08; const lenSmall = baseRadius * 0.05; const outerR = baseRadius - 2; // just inside the border // Distance from center for the marker digits so they sit just inside big ticks const markerSize = markers[0] ? markers[0].offsetWidth : 0; const markerRadius = outerR - lenBig - markerSize * getMarkerOffset(clock.offsetWidth); markers.forEach((m, i) => { const angle = (i / 16) * 2 * Math.PI; m.style.left = '50%'; m.style.top = '50%'; m.style.transform = `translate(-50%, -50%) rotate(${angle}rad) translate(0, -${markerRadius}px) rotate(${-angle}rad)`; }); ticks.forEach((t, i) => { let len = lenSmall; if (t.classList.contains('big')) len = lenBig; else if (t.classList.contains('mid')) len = lenMid; const innerR = outerR - len; const angle = ((i + 1) / 256) * 2 * Math.PI; t.style.height = `${len}px`; t.style.left = '50%'; t.style.top = '50%'; t.style.transform = `translate(-50%, 0) rotate(${angle}rad) translate(0, -${innerR}px)`; 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 = `translateX(-50%) translateZ(0) rotate(${angle}deg)`; void el.offsetWidth; el.style.transition = ''; }; el.addEventListener('transitionend', handle, { once: true }); el.style.transform = `translateX(-50%) translateZ(0) rotate(${target}deg)`; } else { el.style.transform = `translateX(-50%) translateZ(0) 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(); } })();