1480 lines
50 KiB
HTML
1480 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>CoBiE Time System Calendar</title>
|
||
<style>
|
||
* {
|
||
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));
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>CoBiE Time System</h1>
|
||
<div class="subtitle">CosmoChron Binary Epoch Calendar</div>
|
||
</div>
|
||
|
||
<div class="current-time">
|
||
<div class="label">Current CoBiE Time</div>
|
||
<div class="cobie-time" id="cobieTime" tabindex="0">+00000000.0000</div>
|
||
<div class="regular-time" id="regularTime">Loading...</div>
|
||
<div class="regular-time" style="font-size: 0.9em; color: #888;" id="taiTime">TAI: Loading...</div>
|
||
</div>
|
||
|
||
<div class="timezone-selector">
|
||
<label for="timezone">Timezone: </label>
|
||
<select id="timezone">
|
||
<option value="UTC">UTC</option>
|
||
<option value="TAI">TAI (Atomic Time)</option>
|
||
<option value="America/New_York">Eastern Time</option>
|
||
<option value="America/Chicago">Central Time</option>
|
||
<option value="America/Denver">Mountain Time</option>
|
||
<option value="America/Los_Angeles">Pacific Time</option>
|
||
<option value="Europe/London">London</option>
|
||
<option value="Europe/Paris">Paris</option>
|
||
<option value="Asia/Tokyo">Tokyo</option>
|
||
<option value="Asia/Shanghai">Shanghai</option>
|
||
<option value="Australia/Sydney">Sydney</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="calendar-controls">
|
||
<button onclick="navigatePeriod(event, -1)">←</button>
|
||
<span id="currentPeriod">Current Period</span>
|
||
<button onclick="navigatePeriod(event, 1)">→</button>
|
||
<button onclick="goToNow()">Now</button>
|
||
</div>
|
||
|
||
<div class="calendar-view">
|
||
<div class="calendar-header" id="calendarHeader">Loading...</div>
|
||
<div class="eonstrip-grid" id="eonstripGrid"></div>
|
||
</div>
|
||
|
||
<div class="time-details">
|
||
<h3 style="text-align: center; margin-bottom: 20px; color: #00ffff;">Time Breakdown</h3>
|
||
|
||
<div class="breakdown-header" style="text-align: center; margin-bottom: 15px;">
|
||
<button id="toggleExtended" class="toggle-btn">
|
||
<span class="arrow-icon">▼</span>
|
||
<span class="btn-text">Show Cosmic Units</span>
|
||
</button>
|
||
</div>
|
||
<div id="extendedBreakdown" class="extended-section"></div>
|
||
<div id="coreBreakdown"></div>
|
||
</div>
|
||
|
||
<div class="explanations" style="margin-top: 40px; padding: 30px; background: rgba(255, 255, 255, 0.05); border-radius: 15px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1);">
|
||
<h3 style="text-align: center; margin-bottom: 20px; color: #00ffff;">Understanding CoBiE Time</h3>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">How to Read CoBiE Timestamps</h4>
|
||
<p style="line-height: 1.6;">A CoBiE timestamp follows the format <code style="background: rgba(0, 255, 255, 0.2); padding: 2px 6px; border-radius: 3px;">XXXX.XXXX</code> where:</p>
|
||
<ul style="margin-left: 20px; line-height: 1.6;">
|
||
<li>First 4 digits (before dot): Date portion - Galactic Year, Cosmocycle, Megasequence, Eonstrip</li>
|
||
<li>Last 4 digits (after dot): Time portion - Chronon, Quantic, Xenocycle, Second</li>
|
||
<li>Each digit is a hexadecimal value (0-F)</li>
|
||
</ul>
|
||
<p style="line-height: 1.6; margin-top: 10px;">For extremely large dates, an extended format is used with additional units for Epoch of Cosmos, Celestial Era, and Universal Eon.</p>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">Time Unit Conversions</h4>
|
||
<p style="line-height: 1.6;">Each CoBiE unit is exactly 16 (0x10):</p>
|
||
<ul style="margin-left: 20px; line-height: 1.6;">
|
||
<li><strong>Xenocycle (xnc / zɛn /):</strong> 16 seconds (like a brief moment)</li>
|
||
<li><strong>Quantic (qnt / kwɑn /):</strong> 256 seconds (≈ 4.3 minutes)</li>
|
||
<li><strong>Chronon (chr / krɔn /):</strong> 4 096 seconds (≈ 1.1 hours)</li>
|
||
<li><strong>Eonstrip (eon / iˈɒn /):</strong> 65 536 seconds (≈ 18.2 hours – similar to a day)</li>
|
||
<li><strong>Megasequence (mqs / ˈmɛɡə /):</strong> 1 048 576 seconds (≈ 12.1 days – like a fortnight)</li>
|
||
<li><strong>Cosmocycle (ccy / ˈkɒzmoʊ /):</strong> 16 777 216 seconds (≈ 194 days – half a year)</li>
|
||
<li><strong>Galactic Year (gyr / ˈɡæl.jɑr /):</strong> 268 435 456 seconds (≈ 8.5 years – just under a decade)</li>
|
||
<li><strong>Universal Eon (ueo / juˈiɒn /):</strong> 4 294 967 296 seconds (≈ 136 years – roughly the span from 1889 to 2025)</li>
|
||
<li><strong>Celestial Era (cer / ˈsi.rə /):</strong> 68 719 476 736 seconds (≈ 2 179 years – from around 1 CE to about 2180 CE)</li>
|
||
<li><strong>Epoch of Cosmos (eoc / i.oʊˈsi /):</strong> 1 099 511 627 776 seconds (≈ 34 865 years – roughly since the end of the last glacial period)</li>
|
||
<li><strong>Cosmic Aeon (cae / ˈkeɪ.ɒn /):</strong> 17 592 186 044 416 seconds (≈ 557 844 years – about twice the time Homo sapiens have existed)</li>
|
||
<li><strong>Metaepoch (mep / mɛt /):</strong> 281 474 976 710 656 seconds (≈ 8 925 512 years – roughly since early hominin ancestors diverged in the Miocene)</li>
|
||
<li><strong>Eternum (etn / ˈɛt.nəm /):</strong> 4 503 599 627 370 496 seconds (≈ 142 808 207 years – around the late Jurassic period when many dinosaurs roamed)</li>
|
||
<li><strong>Infinitum (inf / ɪnˈfɪn /):</strong> 72 057 594 037 927 936 seconds (≈ 2 284 931 317 years – since the Great Oxidation Event over 2 billion years ago)</li>
|
||
<li><strong>Astralmillennia (ami / æstˈmɪl /):</strong> 1 152 921 504 606 846 976 seconds (≈ 36 558 901 084 years – more than twice the age of the universe)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">Mythological Names</h4>
|
||
<p style="line-height: 1.6;">Each period has a celestial or mythological name:</p>
|
||
<ul style="margin-left: 20px; line-height: 1.6;">
|
||
<li><strong>Eonstrips (0-F):</strong> Named after celestial elements (Solprime, Lunex, Terros...)</li>
|
||
<li><strong>Megasequences (0-F):</strong> Named after cosmic phenomena (Azurean Tide, Sable Gleam...)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">TAI vs UTC</h4>
|
||
<p style="line-height: 1.6;">CoBiE uses TAI (International Atomic Time) which:</p>
|
||
<ul style="margin-left: 20px; line-height: 1.6;">
|
||
<li>Runs continuously without leap seconds</li>
|
||
<li>Is currently 37 seconds ahead of UTC (as of 2025)</li>
|
||
<li>Started at the same moment as UTC on January 1, 1958</li>
|
||
<li>Provides a stable, monotonic time reference for astronomical calculations</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">Navigation Tips</h4>
|
||
<ul style="margin-left: 20px; line-height: 1.6;">
|
||
<li>Use Previous/Next buttons to browse different megasequences</li>
|
||
<li>Click on any eonstrip card to see its full details</li>
|
||
<li>The current eonstrip is highlighted in cyan</li>
|
||
<li>All times adjust automatically to your selected timezone</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div>
|
||
<h4 style="color: #ff00ff; margin-bottom: 10px;">CoBiE Epoch & Time Scale</h4>
|
||
<p style="line-height: 1.6;">The CoBiE system uses the Unix TAI epoch as its starting point - January 1, 1970, 00:00:00 TAI (Atomic Time). This is when all CoBiE values were 0000.0000.</p>
|
||
<p style="line-height: 1.6;"><strong>Important:</strong> CoBiE uses TAI time scale, which differs from UTC by leap seconds. As of 2025, TAI is 37 seconds ahead of UTC. This means CoBiE time runs continuously without leap second adjustments.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 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 = `
|
||
<div class="time-unit">
|
||
<span class="unit-name">Galactic Year</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.galactic_year.toString(16).toUpperCase()} (${breakdown.galactic_year})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(gyrStart);
|
||
const rawEnd = fromCobiets(gyrEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, gyrStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, gyrEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Cosmocycle</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.cosmocycle.toString(16).toUpperCase()} (${breakdown.cosmocycle})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(ccyStart);
|
||
const rawEnd = fromCobiets(ccyEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, ccyStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, ccyEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Megasequence</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.megasequence.toString(16).toUpperCase()} (${breakdown.megasequence}) – ${MEGASEQUENCE_NAMES[breakdown.megasequence] || 'Unknown'}
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(mqsStart);
|
||
const rawEnd = fromCobiets(mqsEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, mqsStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, mqsEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Eonstrip</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.eonstrip.toString(16).toUpperCase()} (${breakdown.eonstrip}) – ${EONSTRIP_NAMES[breakdown.eonstrip]}
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(eosStart);
|
||
const rawEnd = fromCobiets(eosEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, eosStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, eosEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Chronon</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.chronon.toString(16).toUpperCase()} (${breakdown.chronon})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit">
|
||
<span class="unit-name">Quantic</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.quantic.toString(16).toUpperCase()} (${breakdown.quantic})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit">
|
||
<span class="unit-name">Xenocycle</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.xenocycle.toString(16).toUpperCase()} (${breakdown.xenocycle})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit">
|
||
<span class="unit-name">Second</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.second.toString(16).toUpperCase()} (${breakdown.second})
|
||
</span>
|
||
</div>
|
||
`;
|
||
document.getElementById('coreBreakdown').innerHTML = coreHtml;
|
||
|
||
//
|
||
// ── Build the “extended” units (hidden by default) in order: Astralmillennia → Universal Eon ───
|
||
//
|
||
const extendedHtml = `
|
||
<div class="time-unit">
|
||
<span class="unit-name">Astralmillennia</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.astralmillennia.toString(16).toUpperCase()} (${breakdown.astralmillennia})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const startAmt = breakdown.astralmillennia * COBIE_UNITS.astralmillennia;
|
||
const endAmt = startAmt + (COBIE_UNITS.astralmillennia - 1);
|
||
const rawStart = fromCobiets(startAmt);
|
||
const rawEnd = fromCobiets(endAmt);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Infinitum</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.infinitum.toString(16).toUpperCase()} (${breakdown.infinitum})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const startAmt = breakdown.infinitum * COBIE_UNITS.infinitum;
|
||
const endAmt = startAmt + (COBIE_UNITS.infinitum - 1);
|
||
const rawStart = fromCobiets(startAmt);
|
||
const rawEnd = fromCobiets(endAmt);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Eternum</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.eternum.toString(16).toUpperCase()} (${breakdown.eternum})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const startAmt = breakdown.eternum * COBIE_UNITS.eternum;
|
||
const endAmt = startAmt + (COBIE_UNITS.eternum - 1);
|
||
const rawStart = fromCobiets(startAmt);
|
||
const rawEnd = fromCobiets(endAmt);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Metaepoch</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.metaepoch.toString(16).toUpperCase()} (${breakdown.metaepoch})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const startAmt = breakdown.metaepoch * COBIE_UNITS.metaepoch;
|
||
const endAmt = startAmt + (COBIE_UNITS.metaepoch - 1);
|
||
const rawStart = fromCobiets(startAmt);
|
||
const rawEnd = fromCobiets(endAmt);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Cosmic Aeon</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.cosmic_aeon.toString(16).toUpperCase()} (${breakdown.cosmic_aeon})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
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 `
|
||
<span>Started: ${formatSafeDate(rawStart, startAmt, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, endAmt, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Epoch of Cosmos</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.epoch_of_cosmos.toString(16).toUpperCase()} (${breakdown.epoch_of_cosmos})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(eocStart);
|
||
const rawEnd = fromCobiets(eocEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, eocStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, eocEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Celestial Era</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.celestial_era.toString(16).toUpperCase()} (${breakdown.celestial_era})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(cerStart);
|
||
const rawEnd = fromCobiets(cerEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, cerStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, cerEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<div class="time-unit">
|
||
<span class="unit-name">Universal Eon</span>
|
||
<span class="unit-value">
|
||
0x${breakdown.universal_eon.toString(16).toUpperCase()} (${breakdown.universal_eon})
|
||
</span>
|
||
</div>
|
||
<div class="time-unit" style="padding-left: 20px; font-size: 0.9em; color: #888;">
|
||
${(() => {
|
||
const rawStart = fromCobiets(ueoStart);
|
||
const rawEnd = fromCobiets(ueoEnd);
|
||
return `
|
||
<span>Started: ${formatSafeDate(rawStart, ueoStart, dateOptions)}</span><br>
|
||
<span>Ends: ${formatSafeDate(rawEnd, ueoEnd, dateOptions)}</span>
|
||
`;
|
||
})()}
|
||
</div>
|
||
`;
|
||
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 = `
|
||
<div class="time-unit">
|
||
<span class="unit-name">Compare</span>
|
||
<span class="unit-value" id="diffField">${formatCobieTimestamp(baseCob)}</span>
|
||
</div>
|
||
<div class="time-unit">
|
||
<span class="unit-name">Difference (CoBiE)</span>
|
||
<span class="unit-value">${formatCobieTimestamp(diffCob)}</span>
|
||
</div>
|
||
<div class="time-unit">
|
||
<span class="unit-name">Difference</span>
|
||
<span class="unit-value">
|
||
${human.years}y ${human.months}m ${human.days}d ${human.hours}h ${human.minutes}m ${human.seconds}s
|
||
</span>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="eonstrip-name">${EONSTRIP_NAMES[i]}</div>
|
||
<div class="eonstrip-hex">
|
||
${sign}${cellBd.galactic_year.toString(16)}${cellBd.cosmocycle.toString(16)}${cellBd.megasequence.toString(16)}${i.toString(16)}
|
||
</div>
|
||
<div class="eonstrip-dates">
|
||
${startDate.toLocaleDateString('en-US', dateOpts)}
|
||
</div>`;
|
||
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 `
|
||
<strong>${EONSTRIP_NAMES[index]} (0x${index.toString(16).toUpperCase()})</strong><br>
|
||
Start: ${startDate.toLocaleString('en-US', options)}<br>
|
||
End: ${endDate.toLocaleString('en-US', options)}<br>
|
||
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()));
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|