diff --git a/README.md b/README.md index 72ee2db..10563ab 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ An interactive web app that visualizes the **CosmoChron Binary Epoch (CoBiE)** t ## Project Structure ``` -├── index.html # Main HTML, styles, and scripts +├── index.html # Main HTML markup +├── style.css # Separated styles +├── script.js # JavaScript logic ├── README.md # This documentation └── assets/ # (Optional) images or external CSS/JS ``` diff --git a/index.html b/index.html index 05aee7d..0f04736 100644 --- a/index.html +++ b/index.html @@ -4,351 +4,7 @@ CoBiE Time System Calendar - +
@@ -480,1000 +136,6 @@
- + diff --git a/script.js b/script.js new file mode 100644 index 0000000..0021edb --- /dev/null +++ b/script.js @@ -0,0 +1,993 @@ +// CoBiE Time System Implementation +// Using Unix TAI epoch (January 1, 1970, 00:00:00 TAI) +// Note: TAI differs from UTC by leap seconds (37 seconds as of 2025) +const COBIE_EPOCH = 0; // Unix TAI epoch in seconds + +const COBIE_UNITS = { + second: 1, + xenocycle: 0x10, + quantic: 0x100, + chronon: 0x1000, + eonstrip: 0x10000, + megasequence: 0x100000, + cosmocycle: 0x1000000, + galactic_year: 0x10000000, + universal_eon: 0x100000000, + celestial_era: 0x1000000000, + epoch_of_cosmos: 0x10000000000, + cosmic_aeon: 0x100000000000, + metaepoch: 0x1000000000000, + eternum: 0x10000000000000, + infinitum: 0x100000000000000, + astralmillennia: 0x1000000000000000 +}; + +const EONSTRIP_NAMES = [ + 'Solprime', 'Lunex', 'Terros', 'Aquarion', + 'Ventaso', 'Ignisar', 'Crystalos', 'Floraen', + 'Faunor', 'Nebulus', 'Astraeus', 'Umbranox', + 'Electros', 'Chronar', 'Radiantae', 'Etherion' +]; + +const MEGASEQUENCE_NAMES = [ + 'Azurean Tide', 'Sable Gleam', 'Verdanth Starfall', 'Crimson Dusk', + 'Cobalt Frost', 'Amber Blaze', 'Viridian Bloom', 'Argent Veil', + 'Helian Rise', 'Nocturne Shade', 'Celestine Aura', 'Pyralis Light', + 'Zephyrine Whisper', 'Lustran Bounty', 'Umbral Echo', 'Mythran Epoch' +]; + +let currentOffset = 0; +let currentTimezone = 'UTC'; +let manualMode = false; +let manualCobiets = 0; +let compareManualMode = false; +let compareCobiets = 0; +let updateInterval; +let lastRenderedEonstrip = null; + +function fmt(d, o) { + // shift if TAI, then format + const shifted = currentTimezone==='TAI' + ? new Date(d.getTime() + getTAIOffsetAt(d)*1000) + : d; + return shifted.toLocaleString('en-US', o); +} + +function formatSafeDate(rawDate, cobSeconds, intlOptions) { + if (rawDate instanceof Date && !isNaN(rawDate.getTime())) { + // Date is valid: optionally shift for TAI vs UTC, then format: + const shifted = currentTimezone === 'TAI' + ? new Date(rawDate.getTime() + getTAIOffsetAt(rawDate) * 1000) + : rawDate; + return shifted.toLocaleString('en-US', intlOptions); + } else { + // Invalid Date: compute an approximate calendar year + // Seconds in a tropical year ≈ 365.2425 days + const secondsPerYear = 3600 * 24 * 365.2425; + const yearsSince1970 = cobSeconds / secondsPerYear; + const approxYear = Math.round(1970 + yearsSince1970); + return `≈ Year ${approxYear.toLocaleString('en-US')}`; + } +} + +function parseCobiets(str) { + const m = /^([+-]?)([0-9A-Fa-f]+)\.([0-9A-Fa-f]{1,})$/.exec(str.trim()); + if (!m) return null; + const sign = m[1] === '-' ? -1 : 1; + + // All date‐units in descending “size” order, exactly as in COBIE_UNITS + const allDateKeys = [ + 'astralmillennia', + 'infinitum', + 'eternum', + 'metaepoch', + 'cosmic_aeon', + 'epoch_of_cosmos', + 'celestial_era', + 'universal_eon', + 'galactic_year', + 'cosmocycle', + 'megasequence', + 'eonstrip' + ]; + + // Raw hex‐string on the left of “.” + let rawDateHex = m[2]; + // Pad on the left with zeros if shorter than the number of allDateKeys + if (rawDateHex.length < allDateKeys.length) { + rawDateHex = rawDateHex.padStart(allDateKeys.length, '0'); + } + + // If user provided MORE hex‐digits than allDateKeys.length, we treat each extra + // digit as an even higher‐order “super‐unit” whose weight is: + // 16^( position_from_right_of_eonstrip + 1 ) * COBIE_UNITS['eonstrip'] + let dateKeys = [...allDateKeys]; + if (rawDateHex.length > allDateKeys.length) { + const extraCount = rawDateHex.length - allDateKeys.length; + for (let i = 0; i < extraCount; i++) { + // Insert null placeholders at the front for “beyond astralmillennia” + dateKeys.unshift(null); + } + } + + // Time portion (always exactly 4 hex digits → chronon, quantic, xenocycle, second) + const timeHexRaw = m[3]; + const timeHex = timeHexRaw.padStart(4, '0'); + const timeKeys = ['chronon', 'quantic', 'xenocycle', 'second']; + + let total = 0; + // 1) Process every date‐hex digit + for (let i = 0; i < rawDateHex.length; i++) { + const digit = parseInt(rawDateHex[i], 16); + const key = dateKeys[i]; + if (key === null) { + // “Super‐unit” weight = (16 ^ power) * (size of eonstrip), + // where power = (#digits_left_of_eonstrip) − (index_of_this_digit) − 1 + const power = rawDateHex.length - 1 - i; + total += digit * Math.pow(16, power) * COBIE_UNITS['eonstrip']; + } else { + total += digit * COBIE_UNITS[key]; + } + } + + // 2) Add time‐portion + timeHex.split('').forEach((h, i) => { + total += parseInt(h, 16) * COBIE_UNITS[timeKeys[i]]; + }); + + return sign * total; +} + +function floorDiv(a, b) { + return Math.trunc(a / b); +} + +function getCurrentTAIOffset() { + return getTAIOffsetAt(new Date()); +} + +function getHumanDiff(d1, d2) { + // make sure start ≤ end + let start = d1 < d2 ? d1 : d2; + let end = d1 < d2 ? d2 : d1; + + // 1) year/month/day difference + let years = end.getUTCFullYear() - start.getUTCFullYear(); + let months = end.getUTCMonth() - start.getUTCMonth(); + let days = end.getUTCDate() - start.getUTCDate(); + + // if day roll-under, borrow from month + if (days < 0) { + months--; + // days in the month *before* `end`’s month: + let prevMonthDays = new Date(Date.UTC( + end.getUTCFullYear(), + end.getUTCMonth(), 0 + )).getUTCDate(); + days += prevMonthDays; + } + // if month roll-under, borrow from year + if (months < 0) { + years--; + months += 12; + } + + // 2) now handle hours/min/sec by “aligning” a Date at start+Y/M/D + let aligned = new Date(Date.UTC( + start.getUTCFullYear() + years, + start.getUTCMonth() + months, + start.getUTCDate() + days, + start.getUTCHours(), + start.getUTCMinutes(), + start.getUTCSeconds() + )); + let diffMs = end.getTime() - aligned.getTime(); + + // if we overshot (negative), borrow one day + if (diffMs < 0) { + // borrow 24 h + diffMs += 24 * 3600e3; + if (days > 0) { + days--; + } else { + // days was zero, so borrow a month + months--; + if (months < 0) { + years--; + months += 12; + } + // set days to length of the previous month of `end` + days = new Date(Date.UTC( + end.getUTCFullYear(), + end.getUTCMonth(), 0 + )).getUTCDate(); + } + } + + // 3) extract h/m/s + let hours = Math.floor(diffMs / 3600e3); diffMs -= hours * 3600e3; + let minutes = Math.floor(diffMs / 60e3); diffMs -= minutes * 60e3; + let seconds = Math.floor(diffMs / 1e3); + + return { years, months, days, hours, minutes, seconds }; +} + +function getTAIOffsetAt(date) { + const taiEpoch = new Date('1958-01-01T00:00:00Z'); + if (date < taiEpoch) { return 0; } + const leapSeconds = [ + { date: '1972-01-01T00:00:00Z', offset: 10 }, + { date: '1972-07-01T00:00:00Z', offset: 11 }, + { date: '1973-01-01T00:00:00Z', offset: 12 }, + { date: '1974-01-01T00:00:00Z', offset: 13 }, + { date: '1975-01-01T00:00:00Z', offset: 14 }, + { date: '1976-01-01T00:00:00Z', offset: 15 }, + { date: '1977-01-01T00:00:00Z', offset: 16 }, + { date: '1978-01-01T00:00:00Z', offset: 17 }, + { date: '1979-01-01T00:00:00Z', offset: 18 }, + { date: '1980-01-01T00:00:00Z', offset: 19 }, + { date: '1981-07-01T00:00:00Z', offset: 20 }, + { date: '1982-07-01T00:00:00Z', offset: 21 }, + { date: '1983-07-01T00:00:00Z', offset: 22 }, + { date: '1985-07-01T00:00:00Z', offset: 23 }, + { date: '1988-01-01T00:00:00Z', offset: 24 }, + { date: '1990-01-01T00:00:00Z', offset: 25 }, + { date: '1991-01-01T00:00:00Z', offset: 26 }, + { date: '1992-07-01T00:00:00Z', offset: 27 }, + { date: '1993-07-01T00:00:00Z', offset: 28 }, + { date: '1994-07-01T00:00:00Z', offset: 29 }, + { date: '1996-01-01T00:00:00Z', offset: 30 }, + { date: '1997-07-01T00:00:00Z', offset: 31 }, + { date: '1999-01-01T00:00:00Z', offset: 32 }, + { date: '2006-01-01T00:00:00Z', offset: 33 }, + { date: '2009-01-01T00:00:00Z', offset: 34 }, + { date: '2012-07-01T00:00:00Z', offset: 35 }, + { date: '2015-07-01T00:00:00Z', offset: 36 }, + { date: '2017-01-01T00:00:00Z', offset: 37 } + ]; + + for (let i = 0; i < leapSeconds.length; i++) { + const leapDate = new Date(leapSeconds[i].date); + if (date < leapDate) { + return i === 0 ? 10 : leapSeconds[i - 1].offset; + } + } + return 37; // Most recent known offset +} + +function toCobiets(date) { + const utcSec = floorDiv(date.getTime(), 1000); + const taiSec = utcSec + getTAIOffsetAt(date); + return taiSec - COBIE_EPOCH; +} + +function fromCobiets(cobiets) { + const taiSeconds = cobiets + COBIE_EPOCH; + const taiMs = taiSeconds * 1000; + let utcMs = taiMs + for (let i = 0; i < 3; i++) { + const off = getTAIOffsetAt(new Date(utcMs)); + utcMs = taiMs - off * 1000; + } + return new Date(utcMs); +} + +const UNIT_KEYS = [ + 'astralmillennia', + 'infinitum', + 'eternum', + 'metaepoch', + 'cosmic_aeon', + 'epoch_of_cosmos', + 'celestial_era', + 'universal_eon', + 'galactic_year', + 'cosmocycle', + 'megasequence', + 'eonstrip', + 'chronon', + 'quantic', + 'xenocycle', + 'second' +]; +function breakdownNonNeg(cob) { + let rem = cob, bd = {}; + for (let key of UNIT_KEYS) { + bd[key] = floorDiv(rem, COBIE_UNITS[key]); + rem %= COBIE_UNITS[key]; + } + return bd; +} + +function formatCobieTimestamp(cobiets) { + const sign = cobiets < 0 ? '-' : '+'; + const absCob = Math.abs(cobiets); + const bd = breakdownNonNeg(absCob); + + // All date‐units in descending “size” order + const dateUnits = [ + 'astralmillennia', + 'infinitum', + 'eternum', + 'metaepoch', + 'cosmic_aeon', + 'epoch_of_cosmos', + 'celestial_era', + 'universal_eon', + 'galactic_year', + 'cosmocycle', + 'megasequence', + 'eonstrip' + ]; + + // Build a raw hex‐string by concatenating each unit’s hex + let rawDateHex = dateUnits + .map(key => bd[key].toString(16)) + .join(''); + + // Trim leading zeros but leave at least one “0” if everything is zero + rawDateHex = rawDateHex.replace(/^0+/, ''); + if (rawDateHex === '') rawDateHex = '0'; + + // Time portion: exactly 4 hex digits (chronon → second) + const timeHex = [ + bd.chronon, + bd.quantic, + bd.xenocycle, + bd.second + ].map(n => n.toString(16)).join(''); + + const paddedTimeHex = timeHex.padStart(4, '0'); + + return sign + rawDateHex + '.' + paddedTimeHex; +} + +function updateCurrentTime() { + let cobiets, baseDate; + if (manualMode) { + cobiets = manualCobiets; + baseDate = fromCobiets(cobiets); + } else { + baseDate = new Date(); + cobiets = toCobiets(baseDate); + } + 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 taiOffset = getTAIOffsetAt(baseDate); + let displayDate = baseDate; + if (currentTimezone === 'TAI') { + displayDate = new Date(baseDate.getTime() + taiOffset * 1000); + } + + document.getElementById('regularTime').textContent = currentTimezone + ': ' + displayDate.toLocaleString('en-US', options); + + 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)'; + + + const bd = breakdownNonNeg(Math.abs(cobiets)); + const currentStrip = bd.eonstrip; + if (currentStrip !== lastRenderedEonstrip) { + updateCalendar(); + lastRenderedEonstrip = currentStrip; + } else { + updateTimeBreakdown(cobiets); + } +} + +function updateTimeBreakdown(cobiets) { + // 1) Break down absolute CoBiE seconds into each unit + const breakdown = breakdownNonNeg(Math.abs(cobiets)); + + // 2) Compute the “start” CoBiE‐seconds for each unit (in ascending order) + const eocStart = breakdown.epoch_of_cosmos * COBIE_UNITS.epoch_of_cosmos; + const cerStart = eocStart + breakdown.celestial_era * COBIE_UNITS.celestial_era; + const ueoStart = cerStart + breakdown.universal_eon * COBIE_UNITS.universal_eon; + const gyrStart = ueoStart + breakdown.galactic_year * COBIE_UNITS.galactic_year; + const ccyStart = gyrStart + breakdown.cosmocycle * COBIE_UNITS.cosmocycle; + const mqsStart = ccyStart + breakdown.megasequence * COBIE_UNITS.megasequence; + const eosStart = mqsStart + breakdown.eonstrip * COBIE_UNITS.eonstrip; + + // 3) Compute each “end” by adding (unitSize − 1) + const eocEnd = eocStart + COBIE_UNITS.epoch_of_cosmos - 1; + const cerEnd = cerStart + COBIE_UNITS.celestial_era - 1; + const ueoEnd = ueoStart + COBIE_UNITS.universal_eon - 1; + const gyrEnd = gyrStart + COBIE_UNITS.galactic_year - 1; + const ccyEnd = ccyStart + COBIE_UNITS.cosmocycle - 1; + const mqsEnd = mqsStart + COBIE_UNITS.megasequence - 1; + 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 + }; + + // + // ── Build the “core” units (always visible): Galactic Year → Second ────────────── + // + const coreHtml = ` +
+ Galactic Year + + 0x${breakdown.galactic_year.toString(16).toUpperCase()} (${breakdown.galactic_year}) + +
+
+ ${(() => { + const rawStart = fromCobiets(gyrStart); + const rawEnd = fromCobiets(gyrEnd); + return ` + Started: ${formatSafeDate(rawStart, gyrStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, gyrEnd, dateOptions)} + `; + })()} +
+ +
+ Cosmocycle + + 0x${breakdown.cosmocycle.toString(16).toUpperCase()} (${breakdown.cosmocycle}) + +
+
+ ${(() => { + const rawStart = fromCobiets(ccyStart); + const rawEnd = fromCobiets(ccyEnd); + return ` + Started: ${formatSafeDate(rawStart, ccyStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, ccyEnd, dateOptions)} + `; + })()} +
+ +
+ Megasequence + + 0x${breakdown.megasequence.toString(16).toUpperCase()} (${breakdown.megasequence}) – ${MEGASEQUENCE_NAMES[breakdown.megasequence] || 'Unknown'} + +
+
+ ${(() => { + const rawStart = fromCobiets(mqsStart); + const rawEnd = fromCobiets(mqsEnd); + return ` + Started: ${formatSafeDate(rawStart, mqsStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, mqsEnd, dateOptions)} + `; + })()} +
+ +
+ Eonstrip + + 0x${breakdown.eonstrip.toString(16).toUpperCase()} (${breakdown.eonstrip}) – ${EONSTRIP_NAMES[breakdown.eonstrip]} + +
+
+ ${(() => { + const rawStart = fromCobiets(eosStart); + const rawEnd = fromCobiets(eosEnd); + return ` + Started: ${formatSafeDate(rawStart, eosStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, eosEnd, dateOptions)} + `; + })()} +
+ +
+ Chronon + + 0x${breakdown.chronon.toString(16).toUpperCase()} (${breakdown.chronon}) + +
+
+ Quantic + + 0x${breakdown.quantic.toString(16).toUpperCase()} (${breakdown.quantic}) + +
+
+ Xenocycle + + 0x${breakdown.xenocycle.toString(16).toUpperCase()} (${breakdown.xenocycle}) + +
+
+ Second + + 0x${breakdown.second.toString(16).toUpperCase()} (${breakdown.second}) + +
+ `; + document.getElementById('coreBreakdown').innerHTML = coreHtml; + + // + // ── Build the “extended” units (hidden by default) in order: Astralmillennia → Universal Eon ─── + // + const extendedHtml = ` +
+ Astralmillennia + + 0x${breakdown.astralmillennia.toString(16).toUpperCase()} (${breakdown.astralmillennia}) + +
+
+ ${(() => { + const startAmt = breakdown.astralmillennia * COBIE_UNITS.astralmillennia; + const endAmt = startAmt + (COBIE_UNITS.astralmillennia - 1); + const rawStart = fromCobiets(startAmt); + const rawEnd = fromCobiets(endAmt); + return ` + Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + `; + })()} +
+ +
+ Infinitum + + 0x${breakdown.infinitum.toString(16).toUpperCase()} (${breakdown.infinitum}) + +
+
+ ${(() => { + const startAmt = breakdown.infinitum * COBIE_UNITS.infinitum; + const endAmt = startAmt + (COBIE_UNITS.infinitum - 1); + const rawStart = fromCobiets(startAmt); + const rawEnd = fromCobiets(endAmt); + return ` + Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + `; + })()} +
+ +
+ Eternum + + 0x${breakdown.eternum.toString(16).toUpperCase()} (${breakdown.eternum}) + +
+
+ ${(() => { + const startAmt = breakdown.eternum * COBIE_UNITS.eternum; + const endAmt = startAmt + (COBIE_UNITS.eternum - 1); + const rawStart = fromCobiets(startAmt); + const rawEnd = fromCobiets(endAmt); + return ` + Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + `; + })()} +
+ +
+ Metaepoch + + 0x${breakdown.metaepoch.toString(16).toUpperCase()} (${breakdown.metaepoch}) + +
+
+ ${(() => { + const startAmt = breakdown.metaepoch * COBIE_UNITS.metaepoch; + const endAmt = startAmt + (COBIE_UNITS.metaepoch - 1); + const rawStart = fromCobiets(startAmt); + const rawEnd = fromCobiets(endAmt); + return ` + Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + `; + })()} +
+ +
+ Cosmic Aeon + + 0x${breakdown.cosmic_aeon.toString(16).toUpperCase()} (${breakdown.cosmic_aeon}) + +
+
+ ${(() => { + const startAmt = breakdown.cosmic_aeon * COBIE_UNITS.cosmic_aeon; + const endAmt = startAmt + (COBIE_UNITS.cosmic_aeon - 1); + const rawStart = fromCobiets(startAmt); + const rawEnd = fromCobiets(endAmt); + return ` + Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)} + `; + })()} +
+ +
+ Epoch of Cosmos + + 0x${breakdown.epoch_of_cosmos.toString(16).toUpperCase()} (${breakdown.epoch_of_cosmos}) + +
+
+ ${(() => { + const rawStart = fromCobiets(eocStart); + const rawEnd = fromCobiets(eocEnd); + return ` + Started: ${formatSafeDate(rawStart, eocStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, eocEnd, dateOptions)} + `; + })()} +
+ +
+ Celestial Era + + 0x${breakdown.celestial_era.toString(16).toUpperCase()} (${breakdown.celestial_era}) + +
+
+ ${(() => { + const rawStart = fromCobiets(cerStart); + const rawEnd = fromCobiets(cerEnd); + return ` + Started: ${formatSafeDate(rawStart, cerStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, cerEnd, dateOptions)} + `; + })()} +
+ +
+ Universal Eon + + 0x${breakdown.universal_eon.toString(16).toUpperCase()} (${breakdown.universal_eon}) + +
+
+ ${(() => { + const rawStart = fromCobiets(ueoStart); + const rawEnd = fromCobiets(ueoEnd); + return ` + Started: ${formatSafeDate(rawStart, ueoStart, dateOptions)}
+ Ends: ${formatSafeDate(rawEnd, ueoEnd, dateOptions)} + `; + })()} +
+ `; + document.getElementById('extendedBreakdown').innerHTML = extendedHtml; + + // + // ── If “manual mode” is active, append the Compare/Difference rows beneath whichever section is visible ── + // + if (manualMode) { + const baseCob = compareManualMode ? compareCobiets : toCobiets(new Date()); + const baseDate = fromCobiets(baseCob); + const manualDate = fromCobiets(manualCobiets); + const human = getHumanDiff(baseDate, manualDate); + const diffCob = manualCobiets - baseCob; + + const diffHtml = ` +
+ Compare + ${formatCobieTimestamp(baseCob)} +
+
+ Difference (CoBiE) + ${formatCobieTimestamp(diffCob)} +
+
+ Difference + + ${human.years}y ${human.months}m ${human.days}d ${human.hours}h ${human.minutes}m ${human.seconds}s + +
+ `; + + const extDiv = document.getElementById('extendedBreakdown'); + const coreDiv = document.getElementById('coreBreakdown'); + if (extDiv.style.display === 'block') { + extDiv.insertAdjacentHTML('beforeend', diffHtml); + } else { + coreDiv.insertAdjacentHTML('beforeend', diffHtml); + } + + // Re‐attach the “edit‐on‐click” listener for the Compare field + document.getElementById('diffField').addEventListener('click', enterDiffEdit); + } +} + +function enterDiffEdit() { + const span = document.getElementById('diffField'); + const val = span.textContent; + const input = document.createElement('input'); + input.id = 'diffInput'; + input.value = val; + input.style.background = 'transparent'; + input.style.border = 'none'; + input.style.color = 'inherit'; + input.style.width = '12ch'; + span.replaceWith(input); + input.focus(); + input.setSelectionRange(1, val.length); + input.addEventListener('keydown', e => { if (e.key === 'Enter') commitDiff(); }); + input.addEventListener('blur', commitDiff); +} + +function commitDiff() { + const input = document.getElementById('diffInput'); + const parsed = parseCobiets(input.value); + const span = document.createElement('span'); + span.id = 'diffField'; + span.className = 'unit-value'; + if (parsed !== null) { + compareManualMode = true; + compareCobiets = parsed; + span.textContent = formatCobieTimestamp(parsed); + } else { + span.textContent = input.defaultValue; + } + input.replaceWith(span); + span.addEventListener('click', enterDiffEdit); + // refresh breakdown display with new comparison + updateCurrentTime(); +} + +function updateCalendar() { + let currentCob, baseCob, currentBd, currentTime; + if (manualMode) { + currentCob = manualCobiets; + currentBd = breakdownNonNeg(Math.abs(manualCobiets)); + currentTime = currentCob % COBIE_UNITS.eonstrip; + const curEonstrip = currentCob % COBIE_UNITS.megasequence; + const msStartCob = currentCob - curEonstrip + (currentOffset * COBIE_UNITS.megasequence); + baseCob = msStartCob; + } else { + const now = new Date(); + currentCob = toCobiets(now); + currentBd = breakdownNonNeg(Math.abs(currentCob)); + currentTime = currentCob % COBIE_UNITS.eonstrip; + const curEonstrip = currentCob % COBIE_UNITS.megasequence; + const msStartCob = currentCob - curEonstrip + (currentOffset * COBIE_UNITS.megasequence); + baseCob = msStartCob; + } + const baseBd = breakdownNonNeg(Math.abs(baseCob)); + const sign = baseCob < 0 ? '-' : '+'; + + // Header + const msName = MEGASEQUENCE_NAMES[ baseBd.megasequence ] || 'Unknown'; + document.getElementById('calendarHeader').textContent = + `${sign} ${baseBd.megasequence.toString(16)}: ${msName}`; + + document.getElementById('currentPeriod').textContent = + `${sign}${baseBd.galactic_year.toString(16)}${baseBd.cosmocycle.toString(16)}${baseBd.megasequence.toString(16)}`; + + // Grid + const grid = document.getElementById('eonstripGrid'); + 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 + }; + + for (let i = 0; i < 16; i++) { + const cellCob = baseCob + i * COBIE_UNITS.eonstrip; + const cellBd = breakdownNonNeg(Math.abs(cellCob)); + const startDate = fromCobiets(cellCob); + const card = document.createElement('div'); + card.className = 'eonstrip-card'; + + if (currentOffset === 0 && i === currentBd.eonstrip) + card.classList.add('current'); + + card.innerHTML = ` +
${EONSTRIP_NAMES[i]}
+
+ ${sign}${cellBd.galactic_year.toString(16)}${cellBd.cosmocycle.toString(16)}${cellBd.megasequence.toString(16)}${i.toString(16)} +
+
+ ${startDate.toLocaleDateString('en-US', dateOpts)} +
`; + const tooltip = document.createElement('div'); + tooltip.className = 'tooltip'; + tooltip.innerHTML = showEonstripDetails(i, cellCob, dateOpts); + card.appendChild(tooltip); + grid.appendChild(card); + (function(cob) { + card.addEventListener('click', () => { + currentOffset = 0; + manualMode = true; + manualCobiets = cob; + clearInterval(updateInterval); + document.querySelector('.current-time').classList.add('manual'); + updateCurrentTime(); + }); + })(cellCob + currentTime); + } + updateTimeBreakdown(currentCob); +} + +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 + }; + + return ` + ${EONSTRIP_NAMES[index]} (0x${index.toString(16).toUpperCase()})
+ Start: ${startDate.toLocaleString('en-US', options)}
+ End: ${endDate.toLocaleString('en-US', options)}
+ Duration: ${COBIE_UNITS.eonstrip} sec (~${(COBIE_UNITS.eonstrip/3600).toFixed(2)} h) + `; +} + +function navigatePeriod(evt, direction) { + // base step = 1 megasequence + let step = 1; + + if (evt.altKey && evt.shiftKey && evt.ctrlKey) { + // Epoch of Cosmos = 16⁵ MS + step = 16**5; + } else if (evt.altKey && evt.ctrlKey) { + // Celestial Era = 16⁴ MS + step = 16**4; + } else if (evt.altKey && evt.shiftKey) { + // Universal Eon = 16³ MS + step = 16**3; + } else if (evt.altKey) { + // Galactic Year = 16² MS + step = 16**2; + } else if (evt.shiftKey) { + // Cosmocycle = 16¹ MS + step = 16**1; + } + + currentOffset += direction * step; + updateCalendar(); +} + +function goToNow() { + manualMode = false; + manualCobiets = 0; + compareManualMode = false; + currentOffset = 0; + updateCurrentTime(); + updateCalendar(); + clearInterval(updateInterval); + updateInterval = setInterval(updateCurrentTime, 1000); + document.querySelector('.current-time').classList.remove('manual'); +} + +function enterEdit() { + const span = document.getElementById('cobieTime'); + const val = span.textContent; + const input = document.createElement('input'); + input.id = 'cobieInput'; + input.value = val; + input.className = 'cobie-time'; + input.style.background = 'transparent'; + input.style.border = 'none'; + input.style.color = 'inherit'; + input.style.textAlign = 'center'; + input.style.width = '12ch'; + span.replaceWith(input); + input.focus(); + input.setSelectionRange(1, val.length); + input.addEventListener('keydown', e => { if (e.key === 'Enter') commitInput(); }); + input.addEventListener('blur', commitInput); +} + +function commitInput() { + const input = document.getElementById('cobieInput'); + const str = input.value; + const parsed = parseCobiets(str); + + // remember what the old numeric value was + // if we weren't already in manualMode, + // this will be "now" + const oldCob = manualMode + ? manualCobiets + : toCobiets(new Date()); + + // build the new span + const span = document.createElement('div'); + span.id = 'cobieTime'; + span.className = 'cobie-time'; + + if (parsed === null) { + // invalid: restore the old numeric value + span.textContent = formatCobieTimestamp(oldCob); + // keep manualMode exactly as it was + manualCobiets = oldCob; + // manualMode = true; + } else { + // valid: switch into manual mode at the new value + manualMode = true; + manualCobiets = parsed; + clearInterval(updateInterval); + span.textContent = formatCobieTimestamp(parsed); + updateCurrentTime(); + updateCalendar(); + document.querySelector('.current-time').classList.add('manual'); + } + + // swap elements + input.replaceWith(span); + span.addEventListener('click', enterEdit); +} + +// Timezone change handler +document.getElementById('timezone').addEventListener('change', (e) => { + currentTimezone = e.target.value; + updateCurrentTime(); + updateCalendar(); +}); + +// Set default timezone based on user's locale +const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; +const timezoneSelect = document.getElementById('timezone'); +const options = Array.from(timezoneSelect.options); +const matchingOption = options.find(opt => opt.value === userTimezone); +if (matchingOption) { + timezoneSelect.value = userTimezone; + currentTimezone = userTimezone; +} + +updateCurrentTime(); +updateCalendar(); +updateInterval = setInterval(updateCurrentTime, 1000); +document.getElementById('cobieTime').addEventListener('click', enterEdit); + +document.getElementById('toggleExtended').addEventListener('click', () => { + const extDiv = document.getElementById('extendedBreakdown'); + const arrow = document.querySelector('#toggleExtended .arrow-icon'); + const text = document.querySelector('#toggleExtended .btn-text'); + + if (extDiv.style.display === 'block') { + // hide it + extDiv.style.display = 'none'; + arrow.style.transform = 'rotate(0deg)'; + text.textContent = 'Show Cosmic Units'; + } else { + // show it + extDiv.style.display = 'block'; + arrow.style.transform = 'rotate(180deg)'; + text.textContent = 'Hide Cosmic Units'; + } + + // Because “manual mode” might have appended diff rows, we need to rerun updateTimeBreakdown + // to ensure diff rows end up in the correct section + updateTimeBreakdown(manualMode ? manualCobiets : toCobiets(new Date())); +}); diff --git a/style.css b/style.css new file mode 100644 index 0000000..abc62fb --- /dev/null +++ b/style.css @@ -0,0 +1,343 @@ + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%); + color: #e0e0e0; + min-height: 100vh; + overflow-x: hidden; + } + + .container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + } + + .header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background: rgba(255, 255, 255, 0.05); + border-radius: 15px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .tooltip { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.8); + color: #fff; + padding: 8px 12px; + border-radius: 4px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s; + z-index: 9999; + } + + h1 { + font-size: 2.5em; + background: linear-gradient(45deg, #00ffff, #ff00ff); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 10px; + animation: glow 3s ease-in-out infinite; + } + + @keyframes glow { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } + } + + .current-time { + background: rgba(0, 255, 255, 0.1); + border: 2px solid rgba(0, 255, 255, 0.3); + border-radius: 10px; + padding: 20px; + margin-bottom: 20px; + text-align: center; + position: relative; + overflow: hidden; + } + + .current-time.manual::before { + animation-play-state: paused; + } + + .current-time::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(0, 255, 255, 0.1), transparent); + transform: rotate(45deg); + animation: sweep 3s linear infinite; + } + + @keyframes sweep { + 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } + 100% { transform: translateX(100%) translateY(100%) rotate(45deg); } + } + + .cobie-time { + font-size: 2.5em; + font-family: 'Courier New', monospace; + letter-spacing: 2px; + margin: 10px 0; + text-shadow: 0 0 20px rgba(0, 255, 255, 0.5); + } + + .regular-time { + font-size: 1.2em; + color: #aaa; + } + + .timezone-selector { + margin: 20px 0; + text-align: center; + } + + select { + padding: 10px 20px; + font-size: 16px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 5px; + color: #fff; + cursor: pointer; + transition: all 0.3s ease; + } + + select:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); + } + + .calendar-controls { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + margin: 20px 0; + } + + button { + padding: 10px 20px; + font-size: 16px; + background: linear-gradient(45deg, #00ffff, #0080ff); + border: none; + border-radius: 5px; + color: #fff; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + } + + button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255, 255, 255, 0.3); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; + } + + button:hover::before { + width: 300px; + height: 300px; + } + + button:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 255, 255, 0.3); + } + + .calendar-view { + background: rgba(255, 255, 255, 0.05); + border-radius: 15px; + padding: 20px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .calendar-header { + text-align: center; + font-size: 1.5em; + margin-bottom: 20px; + color: #00ffff; + } + + .eonstrip-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 15px; + margin-bottom: 20px; + } + + .eonstrip-card { + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 10px; + padding: 15px; + text-align: center; + transition: all 0.3s ease; + cursor: pointer; + position: relative; + overflow: visible; + white-space: normal; + } + + .eonstrip-card::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); + opacity: 0; + transition: opacity 0.3s ease; + } + + .eonstrip-card:hover::before { + opacity: 1; + } + + .eonstrip-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0, 255, 255, 0.2); + border-color: rgba(0, 255, 255, 0.5); + z-index: 1000; + } + + .eonstrip-card:hover .tooltip { + opacity: 1; + } + + .eonstrip-card.current { + background: rgba(0, 255, 255, 0.2); + border-color: rgba(0, 255, 255, 0.5); + } + + .eonstrip-name { + font-size: 1em; + font-weight: bold; + color: #00ffff; + margin-bottom: 5px; + } + + .eonstrip-hex { + font-size: 0.85em; /* was default monospace size */ + font-family: 'Courier New', monospace; + color: #ffaaff; + margin-bottom: 10px; + } + + .eonstrip-dates { + font-size: 0.7em; + color: #aaa; + line-height: 1.4; + } + + .time-details { + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + padding: 20px; + margin-top: 20px; + } + + .time-unit { + display: flex; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .time-unit:last-child { + border-bottom: none; + } + + .unit-name { + color: #00ffff; + font-weight: bold; + } + + .unit-value { + font-family: 'Courier New', monospace; + color: #ffaaff; + } + + .toggle-btn { + background: linear-gradient(45deg, #00ffff, #0080ff); + border: none; + border-radius: 8px; + color: #fff; + font-size: 0.9em; + padding: 8px 16px; + cursor: pointer; + position: relative; + overflow: hidden; + text-transform: uppercase; + letter-spacing: 1px; + box-shadow: 0 0 10px rgba(0,255,255,0.4); + transition: all 0.3s ease; + } + + .toggle-btn .arrow-icon { + display: inline-block; + margin-right: 6px; + transition: transform 0.3s ease; + } + + .toggle-btn:hover { + transform: translateY(-2px); + box-shadow: 0 0 20px rgba(0,255,255,0.6); + } + + .extended-section { + display: none; /* hidden by default */ + margin-top: 10px; + animation: fadeIn 0.4s ease; + } + + /* Simple fade‐in for when extended units show */ + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @media only screen + and (max-width: 812px) /* iPhone portrait widths go up to ~812px */ + and (orientation: portrait) { + + /* scale down your main text by 30% */ + html { + font-size: 70%; + } + + /* if you prefer targeting only the big “cobie-time” element: */ + .cobie-time { + font-size: 1.75em; /* was 2.5em, which is 70% of 2.5em */ + } + } + + .eonstrip-grid { + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + }