HomeMAL' // MANUFACTORUM
datacrypt
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 &mdash; 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 &rarr; 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&ndash;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">&#9670;</span><span class="sigil"></span><span class="wing">&#9670;</span></div>

    <!-- ============ Imperial -> Gregorian ============ -->
    <section class="panel" id="i2g">
      <h2 class="panel-title">Imperial &rarr; 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&ndash;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&ndash;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&hellip;</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 &mdash; 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>