feat: add shared time, games and feed contracts

Time is this product's subject, so the vocabulary lives in one module:
remaining, progress through a window, and urgency. Urgency is derived from
absolute time left, deliberately independent of proportion — a 90-day event
with three hours left is as urgent as a 3-day one.

An unannounced end is never urgent and has no progress: "we don't know" and
"loads of time" are different facts, and conflating them is what this
product exists to avoid.

Games carry a hue for identity only. Urgency is a separate axis, so one
glance answers both "whose event is this?" and "how long have I got?".

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-15 00:28:17 +02:00
co-authored by Claude Opus 5
parent 3b2feadf5d
commit 45ad5b39da
4 changed files with 325 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
import type { GameId } from "./schema.ts";
export interface GameMeta {
id: GameId;
name: string;
/** Short label for narrow lanes and chips. */
short: string;
/**
* Hue identity. This axis encodes *which game* only — urgency is a separate
* axis (see time.ts). Keeping them orthogonal is what lets a glance answer
* "whose event is this?" and "how long have I got?" at the same time.
*/
hue: string;
}
export const GAMES: Record<GameId, GameMeta> = {
genshin: { id: "genshin", name: "Genshin Impact", short: "Genshin", hue: "#4EA8DE" },
hsr: { id: "hsr", name: "Honkai: Star Rail", short: "Star Rail", hue: "#7B8CFF" },
zzz: { id: "zzz", name: "Zenless Zone Zero", short: "ZZZ", hue: "#F2A03D" },
wuwa: { id: "wuwa", name: "Wuthering Waves", short: "Wuwa", hue: "#3DD6A0" },
arknights: { id: "arknights", name: "Arknights", short: "Arknights", hue: "#9AA3B8" },
endfield: { id: "endfield", name: "Arknights: Endfield", short: "Endfield", hue: "#E8635A" },
nte: { id: "nte", name: "Neverness to Everness", short: "NTE", hue: "#C77DFF" },
};
export const GAME_LIST: GameMeta[] = Object.values(GAMES);
export function gameMeta(id: GameId): GameMeta {
return GAMES[id];
}