pendorclock/src/PendorClock.ts

158 lines
4.6 KiB
TypeScript

import { LitElement, ReactiveController, ReactiveControllerHost, css, html, render } from "lit";
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 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;
constructor() {
super();
this.clock = new ClockController(this, 250);
}
connectedCallback() {
super.connectedCallback();
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);
}
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
hours = hours * 24 + now.getHours() - 16;
const year = now.getFullYear() + 16;
const dayOfYear = 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 nextMonth = pendorMonthIntervals.findIndex(i => i >= dayOfYear);
if (nextMonth === undefined || pendorMonthIntervals[nextMonth - 1] === undefined) {
return undefined;
}
const dayOfMonth = dayOfYear - pendorMonthIntervals[nextMonth - 1];
const dayOfWeek = (Math.ceil(dayOfMonth) - 1) % 6;
return `${pendorWeekdayNames[dayOfWeek]}, ${pendorMonthNames[nextMonth - 1]} ${dayOfMonth.toFixed(
0
)}, 00${year.toFixed(0)}, ${timePart}`;
}
render() {
return html`<div id="clock" part="clock">${this.tick(this.clock.value)}</div>`;
}
}