diff --git a/script.js b/script.js index 29cfc2a..dcc2ef7 100644 --- a/script.js +++ b/script.js @@ -44,41 +44,46 @@ let updateInterval; let lastRenderedEonstrip = null; let currentDetailCob = null; -function hexToRgba(hex, alpha) { - if (!hex) return ''; +// ── Utility color helpers ──────────────────────────────────────────────── +function parseColor(hex) { + if (!hex) return [255, 255, 255]; let c = hex.replace('#', ''); - if (c.length === 3) { - c = c.split('').map(x => x + x).join(''); - } - const r = parseInt(c.substring(0,2),16); - const g = parseInt(c.substring(2,4),16); - const b = parseInt(c.substring(4,6),16); - return `rgba(${r},${g},${b},${alpha})`; + if (c.length === 3) c = c.split('').map(x => x + x).join(''); + const num = parseInt(c, 16); + return [(num >> 16) & 255, (num >> 8) & 255, num & 255]; } -function getContrastColor(hex) { - if (!hex) return '#fff'; - let c = hex.replace('#',''); - if (c.length === 3) c = c.split('').map(x=>x+x).join(''); - const r = parseInt(c.substr(0,2),16); - const g = parseInt(c.substr(2,2),16); - const b = parseInt(c.substr(4,2),16); - const yiq = (r*299 + g*587 + b*114) / 1000; +const toHex = v => v.toString(16).padStart(2, '0'); + +const hexToRgba = (hex, a = 1) => { + const [r, g, b] = parseColor(hex); + return `rgba(${r},${g},${b},${a})`; +}; + +const getContrastColor = hex => { + const [r, g, b] = parseColor(hex); + const yiq = (r * 299 + g * 587 + b * 114) / 1000; return yiq >= 128 ? '#000' : '#fff'; -} +}; -function lightenColor(hex, percent) { - if (!hex) return '#fff'; - let c = hex.replace('#',''); - if (c.length === 3) c = c.split('').map(x=>x+x).join(''); - let r = parseInt(c.substr(0,2),16); - let g = parseInt(c.substr(2,2),16); - let b = parseInt(c.substr(4,2),16); - r = Math.min(255, Math.round(r + (255 - r) * percent)); - g = Math.min(255, Math.round(g + (255 - g) * percent)); - b = Math.min(255, Math.round(b + (255 - b) * percent)); - return '#' + [r,g,b].map(x=>x.toString(16).padStart(2,'0')).join(''); -} +const lightenColor = (hex, p) => { + const [r, g, b] = parseColor(hex).map(v => + Math.min(255, Math.round(v + (255 - v) * p)) + ); + return '#' + [r, g, b].map(toHex).join(''); +}; + +const dateOptions = (long = true) => ({ + timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone, + weekday: 'long', + year: 'numeric', + month: long ? 'long' : 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false +}); function applyEventColors(elem, color, alpha) { if (!color || !elem) return; @@ -188,7 +193,46 @@ function getHumanDiff(d1, d2) { let minutes = Math.floor(diffMs / 60e3); diffMs -= minutes * 60e3; let seconds = Math.floor(diffMs / 1e3); - return { years, months, days, hours, minutes, seconds }; +return { years, months, days, hours, minutes, seconds }; +} + +// ── Event utilities ────────────────────────────────────────────────────── +const normalizeEvent = ev => { + const baseStart = parseCobiets(ev.start || ev.cobie); + if (baseStart === null) return null; + const tzShift = ev.shiftWithTimezone ? + getTimezoneOffsetSeconds(fromCobiets(baseStart)) : 0; + const startCob = baseStart - tzShift; + const endCob = ev.end ? parseCobiets(ev.end) - tzShift : Number.POSITIVE_INFINITY; + const unitVal = COBIE_UNITS[ev.unit] || COBIE_UNITS.cosmocycle; + const interval = (ev.interval || 1) * unitVal; + let duration = 0; + if (typeof ev.duration === 'string') { + const d = parseCobiets(ev.duration); + if (d !== null) duration = d; + } else if (typeof ev.duration === 'number') { + duration = ev.duration; + } + return { startCob, endCob, interval, duration }; +}; + +function collectEventOccurrences(start, end, predicate = () => true) { + const out = []; + if (!Array.isArray(window.SPECIAL_EVENTS)) return out; + window.SPECIAL_EVENTS.forEach(ev => { + if (!predicate(ev)) return; + const meta = normalizeEvent(ev); + if (!meta || start > meta.endCob) return; + let n = Math.floor((start - meta.startCob) / meta.interval); + if (n < 0) n = 0; + let occ = meta.startCob + n * meta.interval; + if (occ + meta.duration <= start) occ += meta.interval; + while (occ < end && occ <= meta.endCob) { + out.push({ event: ev, meta, occ }); + occ += meta.interval; + } + }); + return out; } // getTAIOffsetAt, toCobiets, fromCobiets, breakdownNonNeg and @@ -206,17 +250,7 @@ function updateCurrentTime() { const cobieElem = document.getElementById('cobieTime'); if (cobieElem) cobieElem.textContent = formatCobieTimestamp(cobiets); - const options = { - timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone, - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }; + const options = dateOptions(); const taiOffset = getTAIOffsetAt(baseDate); let displayDate = baseDate; @@ -226,9 +260,9 @@ function updateCurrentTime() { document.getElementById('regularTime').textContent = currentTimezone + ': ' + displayDate.toLocaleString('en-US', options); - options.timeZone = 'UTC'; + const optionsUTC = { ...options, timeZone: 'UTC' }; const taiDate = new Date(baseDate.getTime() + taiOffset * 1000); - document.getElementById('taiTime').textContent = 'TAI UTC: ' + taiDate.toLocaleString('en-US', options) + ' (UTC + ' + taiOffset + 's)'; + document.getElementById('taiTime').textContent = 'TAI UTC: ' + taiDate.toLocaleString('en-US', optionsUTC) + ' (UTC + ' + taiOffset + 's)'; const bd = breakdownNonNeg(Math.abs(cobiets)); @@ -265,17 +299,7 @@ function updateTimeBreakdown(cobiets) { const eosEnd = eosStart + COBIE_UNITS.eonstrip - 1; // 4) Intl formatting options - const dateOptions = { - timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone, - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }; + const optsLong = dateOptions(); // // ── Build the “core” units (always visible): Galactic Year → Second ────────────── @@ -292,8 +316,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(gyrStart); const rawEnd = fromCobiets(gyrEnd); return ` - Started: ${formatSafeDate(rawStart, gyrStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, gyrEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, gyrStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, gyrEnd, optsLong)} `; })()} @@ -309,8 +333,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(ccyStart); const rawEnd = fromCobiets(ccyEnd); return ` - Started: ${formatSafeDate(rawStart, ccyStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, ccyEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, ccyStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, ccyEnd, optsLong)} `; })()} @@ -326,8 +350,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(mqsStart); const rawEnd = fromCobiets(mqsEnd); return ` - Started: ${formatSafeDate(rawStart, mqsStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, mqsEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, mqsStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, mqsEnd, optsLong)} `; })()} @@ -343,8 +367,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(eosStart); const rawEnd = fromCobiets(eosEnd); return ` - Started: ${formatSafeDate(rawStart, eosStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, eosEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, eosStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, eosEnd, optsLong)} `; })()} @@ -393,8 +417,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(startAmt); const rawEnd = fromCobiets(endAmt); return ` - Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + Started: ${formatSafeDate(rawStart, startAmt, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)} `; })()} @@ -412,8 +436,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(startAmt); const rawEnd = fromCobiets(endAmt); return ` - Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + Started: ${formatSafeDate(rawStart, startAmt, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)} `; })()} @@ -431,8 +455,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(startAmt); const rawEnd = fromCobiets(endAmt); return ` - Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + Started: ${formatSafeDate(rawStart, startAmt, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)} `; })()} @@ -450,8 +474,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(startAmt); const rawEnd = fromCobiets(endAmt); return ` - Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + Started: ${formatSafeDate(rawStart, startAmt, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)} `; })()} @@ -469,8 +493,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(startAmt); const rawEnd = fromCobiets(endAmt); return ` - Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + Started: ${formatSafeDate(rawStart, startAmt, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, optsLong)} `; })()} @@ -486,8 +510,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(eocStart); const rawEnd = fromCobiets(eocEnd); return ` - Started: ${formatSafeDate(rawStart, eocStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, eocEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, eocStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, eocEnd, optsLong)} `; })()} @@ -503,8 +527,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(cerStart); const rawEnd = fromCobiets(cerEnd); return ` - Started: ${formatSafeDate(rawStart, cerStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, cerEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, cerStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, cerEnd, optsLong)} `; })()} @@ -520,8 +544,8 @@ function updateTimeBreakdown(cobiets) { const rawStart = fromCobiets(ueoStart); const rawEnd = fromCobiets(ueoEnd); return ` - Started: ${formatSafeDate(rawStart, ueoStart, dateOptions)}
- Ends: ${formatSafeDate(rawEnd, ueoEnd, dateOptions)} + Started: ${formatSafeDate(rawStart, ueoStart, optsLong)}
+ Ends: ${formatSafeDate(rawEnd, ueoEnd, optsLong)} `; })()} @@ -638,12 +662,7 @@ function updateCalendar() { grid.innerHTML = ''; // reuse the same dateOpts you use elsewhere: - const dateOpts = { - timeZone: currentTimezone==='TAI' ? 'UTC' : currentTimezone, - year: 'numeric', month: 'short', day: 'numeric', - hour: '2-digit', minute: '2-digit', second: '2-digit', - hour12: false - }; + const dateOpts = dateOptions(false); for (let i = 0; i < 16; i++) { const cellCob = baseCob + i * COBIE_UNITS.eonstrip; @@ -664,43 +683,16 @@ function updateCalendar() { ${startDate.toLocaleDateString('en-US', dateOpts)} `; - if (Array.isArray(window.SPECIAL_EVENTS)) { - const cellStart = cellCob; - const cellEnd = cellCob + COBIE_UNITS.eonstrip; - window.SPECIAL_EVENTS.forEach(ev => { - if (ev.showMega === false) return; - const baseStart = parseCobiets(ev.start || ev.cobie); - if (baseStart === null) return; - const tzShift = ev.shiftWithTimezone ? getTimezoneOffsetSeconds(fromCobiets(baseStart)) : 0; - const startCob = baseStart - tzShift; - const endCob = ev.end ? parseCobiets(ev.end) - tzShift : Number.POSITIVE_INFINITY; - const unitVal = COBIE_UNITS[ev.unit] || COBIE_UNITS.cosmocycle; - const interval = (ev.interval || 1) * unitVal; - let duration = 0; - if (typeof ev.duration === 'string') { - const d = parseCobiets(ev.duration); - if (d !== null) duration = d; - } else if (typeof ev.duration === 'number') { - duration = ev.duration; - } - - if (cellStart > endCob) return; - - let n = Math.floor((cellStart - startCob) / interval); - if (n < 0) n = 0; - let occ = startCob + n * interval; - if (occ + duration <= cellStart) { - occ += interval; - } - - if (occ < cellEnd && occ + duration > cellStart && occ <= endCob) { - const tag = document.createElement('div'); - tag.className = 'event-tag'; - tag.textContent = ev.label; - card.appendChild(tag); - } - }); - } + collectEventOccurrences( + cellCob, + cellCob + COBIE_UNITS.eonstrip, + ev => ev.showMega !== false + ).forEach(({ event }) => { + const tag = document.createElement('div'); + tag.className = 'event-tag'; + tag.textContent = event.label; + card.appendChild(tag); + }); const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.innerHTML = showEonstripDetails(i, cellCob, dateOpts); @@ -719,17 +711,7 @@ function showEonstripDetails(index, startCobiets, opts) { const startDate = fromCobiets(startCobiets); const endDate = fromCobiets(startCobiets + COBIE_UNITS.eonstrip - 1); - const options = opts || { - timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone, - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }; + const options = opts || dateOptions(); return ` ${EONSTRIP_NAMES[index]} (0x${index.toString(16).toUpperCase()})
@@ -773,49 +755,19 @@ function showEonstripDetail(index, startCob) { updateDetailCurrentTime(); if (Array.isArray(window.SPECIAL_EVENTS)) { - const events = []; const start = startCob; const end = startCob + COBIE_UNITS.eonstrip; - window.SPECIAL_EVENTS.forEach(ev => { - if (ev.showDetail === false) return; - const baseStart = parseCobiets(ev.start || ev.cobie); - if (baseStart === null) return; - const tzShift = ev.shiftWithTimezone ? getTimezoneOffsetSeconds(fromCobiets(baseStart)) : 0; - const startCobEv = baseStart - tzShift; - const endCobEv = ev.end ? parseCobiets(ev.end) - tzShift : Number.POSITIVE_INFINITY; - const unitVal = COBIE_UNITS[ev.unit] || COBIE_UNITS.cosmocycle; - const interval = (ev.interval || 1) * unitVal; - let duration = 0; - if (typeof ev.duration === 'string') { - const d = parseCobiets(ev.duration); - if (d !== null) duration = d; - } else if (typeof ev.duration === 'number') { - duration = ev.duration; - } - - if (start > endCobEv) return; - - let n = Math.floor((start - startCobEv) / interval); - if (n < 0) n = 0; - let occ = startCobEv + n * interval; - if (occ + duration <= start) occ += interval; - - while (occ < end && occ <= endCobEv) { - const relStart = (occ - start) / COBIE_UNITS.eonstrip; - const relEnd = (occ + duration - start) / COBIE_UNITS.eonstrip; - events.push({ - label: ev.label, - color: ev.color, - start: relStart, - end: relEnd, - cobStart: occ, - cobEnd: occ + duration, - seriesStart: startCobEv, - seriesEnd: endCobEv - }); - occ += interval; - } - }); + const events = collectEventOccurrences(start, end, ev => ev.showDetail !== false) + .map(({ event, meta, occ }) => ({ + label: event.label, + color: event.color, + start: (occ - start) / COBIE_UNITS.eonstrip, + end: (occ + meta.duration - start) / COBIE_UNITS.eonstrip, + cobStart: occ, + cobEnd: occ + meta.duration, + seriesStart: meta.startCob, + seriesEnd: meta.endCob + })); events.sort((a,b)=>a.start-b.start); const groups = []; @@ -877,12 +829,7 @@ function showEonstripDetail(index, startCob) { const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; - const optsShort = { - timeZone: currentTimezone === 'TAI' ? 'UTC' : currentTimezone, - year: 'numeric', month: 'short', day: 'numeric', - hour: '2-digit', minute: '2-digit', - hour12: false - }; + const optsShort = dateOptions(false); const startStr = formatCobieTimestamp(ev.cobStart); const endStr = formatCobieTimestamp(ev.cobEnd);