223 lines
6.6 KiB
TypeScript
223 lines
6.6 KiB
TypeScript
import { LitElement, ReactiveController, ReactiveControllerHost, css, html, render } from "lit";
|
|
import { property } from "lit/decorators/property.js";
|
|
|
|
const terranMonthIntervals = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
|
|
const pendorMonthIntervals = [
|
|
0, 1, 25, 49, 73, 97, 121, 145, 146, 147, 171, 195, 211, 243, 267, 291, 292,
|
|
];
|
|
|
|
const pendorMonthNames = [
|
|
"Yestar",
|
|
"Narrin",
|
|
"Nenim",
|
|
"Sulim",
|
|
"Virta",
|
|
"Lothess",
|
|
"Narnya",
|
|
"Attendes",
|
|
"Loende",
|
|
"Cerim",
|
|
"Urim",
|
|
"Yavar",
|
|
"Narquel",
|
|
"Hiss",
|
|
"Ring",
|
|
"Mettare",
|
|
];
|
|
|
|
const centaurMonthIntervals = [0, 1, 43, 85, 145, 146, 147, 189, 249, 291, 292];
|
|
|
|
const centaurMonthNames = [
|
|
"Yestr",
|
|
"Tuil",
|
|
"Layr",
|
|
"Yaiv",
|
|
"Attendes",
|
|
"Loende",
|
|
"Quel",
|
|
"Rive",
|
|
"Cair",
|
|
"Mettare",
|
|
];
|
|
|
|
const pendorWeekdayNames = ["Seren", "Anar", "Noren", "Aldea", "Erwer", "Elenya"];
|
|
|
|
const prefix = (n: number) => `${n < 10 ? "0" : ""}${n.toFixed(0)}`;
|
|
|
|
export class ClockController implements ReactiveController {
|
|
host: ReactiveControllerHost;
|
|
|
|
value = new Date();
|
|
timeout: number;
|
|
// Node and the DOM do not agree on the type. Grrr.
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
private _timerID?: any;
|
|
|
|
constructor(host: ReactiveControllerHost, timeout = 1000) {
|
|
(this.host = host).addController(this);
|
|
this.timeout = timeout;
|
|
}
|
|
|
|
hostConnected() {
|
|
this._timerID = setInterval(() => {
|
|
this.value = new Date();
|
|
this.host.requestUpdate();
|
|
}, this.timeout);
|
|
}
|
|
|
|
hostDisconnected() {
|
|
clearInterval(this._timerID);
|
|
this._timerID = undefined;
|
|
}
|
|
}
|
|
|
|
const styles = css`
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
all: unset;
|
|
display: revert;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:host {
|
|
padding-top: 0;
|
|
letter-spacing: 1px;
|
|
--default-font-size: calc(clamp(0.63rem, calc(0.5rem + 0.63vw), 0.9rem));
|
|
font-family: Bitwise, Audiowide, Tahoma, Arial, Helvetica, sans-serif;
|
|
flex: 0 1 auto;
|
|
text-align: left;
|
|
}
|
|
|
|
div#clock {
|
|
padding: 0.175rem 0.375rem 0.175rem 0.375rem;
|
|
background-color: var(--pendorclock-background-color, #000030);
|
|
color: var(--pendorclock-color, #ffffff);
|
|
font-size: var(--pendorclock-font-size, --default-font-size);
|
|
line-height: var(--pendorclock-line-height, 1.35);
|
|
font-weight: var(--pendorclock-font-weight, 700);
|
|
min-width: 20ch;
|
|
max-width: 35ch;
|
|
text-align: center;
|
|
}
|
|
`;
|
|
|
|
const fontStyle = css`
|
|
@font-face {
|
|
font-family: "Audiowide";
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url(https://fonts.gstatic.com/s/audiowide/v20/l7gdbjpo0cum0ckerWCdlg_O.woff2)
|
|
format("woff2");
|
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
|
|
U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
|
U+FEFF, U+FFFD;
|
|
}
|
|
`;
|
|
|
|
export class PendorClock extends LitElement {
|
|
static get styles() {
|
|
return styles;
|
|
}
|
|
|
|
clock: ClockController;
|
|
|
|
@property({ type: Boolean, attribute: "with-internal-font" })
|
|
useInternalFont = false;
|
|
|
|
@property({ type: Boolean, attribute: "centaurs" })
|
|
useCentaurCalendar = false;
|
|
|
|
constructor() {
|
|
super();
|
|
this.clock = new ClockController(this, 250);
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
if (!this.useInternalFont) {
|
|
return;
|
|
}
|
|
if (document.getElementById("pendor-font-block")) {
|
|
return;
|
|
}
|
|
const head = document.head || document.getElementsByTagName("head")[0];
|
|
const style = html`<style id="pendor-font-block" type="text/css">
|
|
${fontStyle}
|
|
</style>`;
|
|
render(style, head);
|
|
}
|
|
|
|
pendorDate(days: number) {
|
|
const nextMonth = pendorMonthIntervals.findIndex((i) => i >= days);
|
|
if (nextMonth === undefined || pendorMonthIntervals[nextMonth - 1] === undefined) {
|
|
return undefined;
|
|
}
|
|
const thisMonth = nextMonth - 1;
|
|
|
|
// Holidays! Which have no Day-of-Week or Day-of-Month
|
|
if ([0, 145, 146, 291].includes(days)) {
|
|
return `${pendorMonthNames[thisMonth]}`;
|
|
}
|
|
|
|
const dayOfMonth = days - pendorMonthIntervals[thisMonth];
|
|
const dayOfWeek = (Math.ceil(days) - 1) % 6;
|
|
return `${pendorWeekdayNames[dayOfWeek]}, ${
|
|
pendorMonthNames[thisMonth]
|
|
} ${dayOfMonth.toFixed(0)}`;
|
|
}
|
|
|
|
centaurDate(days: number) {
|
|
const nextMonth = centaurMonthIntervals.findIndex((i) => i >= days);
|
|
if (nextMonth === undefined || centaurMonthIntervals[nextMonth - 1] === undefined) {
|
|
return undefined;
|
|
}
|
|
const thisMonth = nextMonth - 1;
|
|
|
|
// Holidays! Which have no Day-of-Week or Day-of-Month
|
|
if ([0, 145, 146, 291].includes(days)) {
|
|
return `${centaurMonthNames[thisMonth]}`;
|
|
}
|
|
|
|
const dayOfMonth = days - centaurMonthIntervals[thisMonth];
|
|
const dayOfWeek = (Math.ceil(days) - 1) % 6;
|
|
return `${pendorWeekdayNames[dayOfWeek]}, ${
|
|
centaurMonthNames[thisMonth]
|
|
} ${dayOfMonth.toFixed(0)}`;
|
|
}
|
|
|
|
tick(now: Date) {
|
|
let hours = terranMonthIntervals[now.getMonth()] + now.getDate();
|
|
if (now.getMonth() > 2 && now.getFullYear() % 4 == 0) {
|
|
hours++;
|
|
}
|
|
|
|
// DST Calculation, and wildly wrong, but WTF. It was prejudiced against where I live,
|
|
// sorry. I just liked watching the clock turnover at midnight every four days, when
|
|
// Terra's and Pendor's clocks had the same midnight.
|
|
|
|
hours = hours * 24 + now.getHours() - 16;
|
|
|
|
// Canonically, Pendor was seeded in 1884 CE. This is just a convenience to get the dates
|
|
// closer. This sixteen has nothing to do with the one above.
|
|
const year = now.getFullYear() + 16;
|
|
|
|
const days = hours / 30;
|
|
hours = hours % 30;
|
|
|
|
let seconds = (now.getSeconds() + now.getMinutes() * 60) / 2.25;
|
|
const minutes = seconds / 40;
|
|
seconds = seconds % 40;
|
|
|
|
const timePart = `${hours.toFixed(0)}:${prefix(minutes)}:${prefix(seconds)}`;
|
|
const daysPart = this.useCentaurCalendar ? this.centaurDate(days) : this.pendorDate(days);
|
|
return `${daysPart}, 00${year.toFixed(0)}, ${timePart}`;
|
|
}
|
|
|
|
render() {
|
|
return html`<div id="clock" part="clock">${this.tick(this.clock.value)}</div>`;
|
|
}
|
|
}
|