Imperial Date Converter / index.html
index.html7.9 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Imperial Dating System — Adeptus Administratum</title>
<link rel="stylesheet" href="styles.css" />
<link rel="icon" href="assets/aquila.svg" type="image/svg+xml" />
</head>
<body>
<div class="bezel" aria-hidden="true"></div>
<div class="scanlines" aria-hidden="true"></div>
<div class="sweep" aria-hidden="true"></div>
<div class="vignette" aria-hidden="true"></div>
<div class="screen"><div class="scroll">
<header class="masthead">
<div class="aquila" role="img" aria-label="Imperial Aquila"></div>
<h1>Imperial Dating System</h1>
<p class="subtitle">Adeptus Administratum — Chronometric Cogitator</p>
<div class="rule"><span class="badge" aria-hidden="true"><span class="sigil"></span></span></div>
</header>
<main>
<!-- ============ Gregorian -> Imperial ============ -->
<section class="panel" id="g2i">
<h2 class="panel-title">Terran → Imperial</h2>
<p class="panel-sub">Old Terra Gregorian reckoning.</p>
<div class="fields">
<label><span class="tag">Year</span>
<input type="number" id="g-year" value="40000" step="1" />
</label>
<label><span class="tag">Month</span>
<select id="g-month"></select>
</label>
<label><span class="tag">Day</span>
<input type="number" id="g-day" value="1" min="1" max="31" />
</label>
<label><span class="tag">Hour</span>
<input type="number" id="g-hour" value="0" min="0" max="23" />
</label>
<label><span class="tag">Check <em class="hint">(0–9)</em></span>
<select id="g-check"></select>
</label>
</div>
<div class="readout" id="g-out" aria-live="polite">
<span class="slate">0 000 000 . M41</span>
</div>
<button class="copy" data-target="g-out">Transcribe</button>
</section>
<div class="divider" aria-hidden="true"><span class="wing">◆</span><span class="sigil"></span><span class="wing">◆</span></div>
<!-- ============ Imperial -> Gregorian ============ -->
<section class="panel" id="i2g">
<h2 class="panel-title">Imperial → Terran</h2>
<p class="panel-sub">Imperial record, decoded to Old Terra reckoning.</p>
<div class="fields imperial-fields">
<label><span class="tag">Check</span>
<select id="i-check"></select>
</label>
<label><span class="tag">Fraction <em class="hint">(000–999)</em></span>
<input type="number" id="i-fraction" value="0" min="0" max="999" />
</label>
<label><span class="tag">Year <em class="hint">(000–999)</em></span>
<input type="number" id="i-year" value="0" min="0" max="999" />
</label>
<label><span class="tag">Millennium <em class="hint">M…</em></span>
<input type="number" id="i-mil" value="41" min="1" />
</label>
</div>
<div class="readout" id="i-out" aria-live="polite">
<span class="slate">January 1, 40000 — 00:00 GMT</span>
</div>
<button class="copy" data-target="i-out">Transcribe</button>
</section>
</main>
<footer>
<p class="creed">The Emperor Protects</p>
</footer>
</div></div><!-- /screen -->
<script>
(() => {
"use strict";
// ---- core conversion ----
const MAKR_CONSTANT = 0.11407955;
const MONTHS = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
];
const MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const isLeapYear = (year) =>
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
const monthLengths = (year) => {
const l = MONTH_LENGTHS.slice();
if (isLeapYear(year)) l[1] = 29;
return l;
};
const dayOfYearIndex = (year, monthIndex, day) => {
const l = monthLengths(year);
let n = 0;
for (let i = 0; i < monthIndex; i++) n += l[i];
return n + (day - 1);
};
const pad = (v, w) => String(v).padStart(w, "0");
function gregorianToImperial({ year, monthIndex, day, hour, checkNumber = 0 }) {
const lengths = monthLengths(year);
const clampedDay = Math.min(Math.max(day, 1), lengths[monthIndex]);
const clampedHour = Math.min(Math.max(hour, 0), 23);
const doy = dayOfYearIndex(year, monthIndex, clampedDay);
const totalHours = doy * 24 + clampedHour;
let fraction = Math.floor(totalHours * MAKR_CONSTANT);
fraction = Math.min(Math.max(fraction, 0), 999);
const millennium = year >= 0
? Math.floor(year / 1000) + 1
: Math.floor((year + 1) / 1000) - 1;
const yearInMillennium = ((year % 1000) + 1000) % 1000;
const check = Math.min(Math.max(Math.trunc(checkNumber), 0), 9);
const formatted =
`${check} ${pad(fraction, 3)} ${pad(yearInMillennium, 3)} . M${millennium}`;
return { fraction, yearInMillennium, millennium, checkNumber: check, formatted };
}
function imperialToGregorian({ fraction, yearInMillennium, millennium, checkNumber = 0 }) {
let year = yearInMillennium + 1000 * (millennium - 1);
const days = fraction / MAKR_CONSTANT / 24;
const dayIndex = Math.floor(days);
const hourFloat = (days - dayIndex) * 24;
const hour = Math.floor(hourFloat);
const minute = Math.round((hourFloat - hour) * 60);
let lengths = monthLengths(year);
let remaining = dayIndex;
let monthIndex = 0;
while (remaining >= lengths[monthIndex]) {
remaining -= lengths[monthIndex];
monthIndex++;
if (monthIndex === 12) { monthIndex = 0; year++; lengths = monthLengths(year); }
}
const day = remaining + 1;
const formatted =
`${MONTHS[monthIndex]} ${day}, ${year} — ${pad(hour, 2)}:${pad(minute, 2)} GMT`;
return { year, monthIndex, month: MONTHS[monthIndex], day, hour, minute, formatted };
}
// ---- DOM wiring ----
const $ = (id) => document.getElementById(id);
MONTHS.forEach((m, i) => {
const o = document.createElement("option");
o.value = String(i); o.textContent = m;
$("g-month").appendChild(o);
});
for (const sel of [$("g-check"), $("i-check")]) {
for (let i = 0; i <= 9; i++) {
const o = document.createElement("option");
o.value = String(i); o.textContent = String(i);
sel.appendChild(o);
}
}
const int = (id, fallback = 0) => {
const v = parseInt($(id).value, 10);
return Number.isFinite(v) ? v : fallback;
};
function renderG2I() {
const res = gregorianToImperial({
year: int("g-year", 40000), monthIndex: int("g-month", 0),
day: int("g-day", 1), hour: int("g-hour", 0), checkNumber: int("g-check", 0),
});
$("g-out").querySelector(".slate").textContent = res.formatted;
}
function renderI2G() {
const res = imperialToGregorian({
fraction: int("i-fraction", 0), yearInMillennium: int("i-year", 0),
millennium: Math.max(1, int("i-mil", 41)), checkNumber: int("i-check", 0),
});
$("i-out").querySelector(".slate").textContent = res.formatted;
}
["g-year", "g-month", "g-day", "g-hour", "g-check"].forEach((id) =>
$(id).addEventListener("input", renderG2I));
["i-check", "i-fraction", "i-year", "i-mil"].forEach((id) =>
$(id).addEventListener("input", renderI2G));
document.querySelectorAll(".copy").forEach((btn) => {
btn.addEventListener("click", async () => {
const text = $(btn.dataset.target).querySelector(".slate").textContent.trim();
try {
await navigator.clipboard.writeText(text);
const original = btn.textContent;
btn.textContent = "Transcribed ✓";
btn.classList.add("done");
setTimeout(() => { btn.textContent = original; btn.classList.remove("done"); }, 1400);
} catch {
btn.textContent = "Clipboard sealed by the Machine-Spirit";
}
});
});
renderG2I();
renderI2G();
})();
</script>
</body>
</html>
