import { useGameMeta } from "../state/gameMeta.tsx"; import { freshness, type SourceHealth } from "../../shared/feed.ts"; import { formatAbsolute, formatRemaining } from "../../shared/time.ts"; export const REPO_URL = "https://github.com/StereotypicalCat/gacha-event-tracker"; /** * Where a reader takes a problem or an idea. * * `template=` names the file in `.github/ISSUE_TEMPLATE/`, so renaming one of * those files breaks these links — GitHub falls back to the template chooser * rather than erroring, which is a soft landing but not the intended one. */ export const BUG_URL = `${REPO_URL}/issues/new?template=bug_report.yml`; export const FEATURE_URL = `${REPO_URL}/issues/new?template=feature_request.yml`; /** * The bug form, with the footer's own freshness line already filled in. * * A wrong end date and a stale calendar look identical to a reader, and eight of * the sources cannot be fetched from CI at all — so "how old is this page's data" * is the first thing anyone triaging a date report has to establish, and the one * thing they cannot recover after the fact. Asking the reader to copy it works; * carrying it for them works more often. * * `refreshed` must stay the `id` of the matching field in `bug_report.yml`. * GitHub silently drops a parameter that names no field, so a drift here costs * the prefill with no error anywhere — `test/issue-templates.test.tsx` pins it. */ export function bugReportUrl(refreshed: string | null): string { if (refreshed === null) return BUG_URL; return `${BUG_URL}&refreshed=${encodeURIComponent(refreshed)}`; } /** Who built this, and where to find them. */ export const AUTHOR = { name: "Lucas Winther", site: "https://lucaswinther.info", github: "https://github.com/StereotypicalCat", kofi: "https://ko-fi.com/stereotypicalcat", } as const; const LINK = "text-muted underline decoration-hairline underline-offset-2 transition-colors duration-150 hover:text-ink hover:decoration-near"; /** * People whose ideas ended up here. * * Ideas and design, not code — nothing in this list carries a licence * obligation, which is why it sits here as thanks rather than in NOTICE with * the terms. Handles rather than names: a handle is verifiable and stable, and * someone who suggests something under a handle has not offered a name to use. * * `handle` renders verbatim, so it carries its own platform — a bare handle is * GitHub, `u/` is Reddit. That beats a `platform` field nobody would keep * consistent for a list this short. * * Unlike the source and studio credits above, this one cannot be derived from * the feed — nothing in the data knows these people exist. Hardcoded on * purpose; adding another is one line. */ export const IDEA_CREDITS = [ { handle: "phuduong85", url: "https://github.com/phuduong85/gacha-event-tracker", }, { handle: "u/eriatilox", url: "https://www.reddit.com/user/eriatilox/", }, { handle: "u/lotus_lunaris", url: "https://www.reddit.com/user/lotus_lunaris/", }, { handle: "u/Neoragex13", url: "https://www.reddit.com/user/Neoragex13/", }, { handle: "u/ShiroWaffles", url: "https://www.reddit.com/user/ShiroWaffles/", }, ] as const; /** * How many lagging games to name before summarising the rest. * * Naming them is the point — a reader can act on a name — but past a handful the * list stops being read, and every extra entry repeating the same age crowds out * the sentence that matters. */ const STALE_NAMES = 4; function GitHubMark() { return ( ); } function KofiMark() { return ( ); } /** Display name and homepage for a source host. */ const SITES: Record = { "game8.co": { name: "Game8", url: "https://game8.co" }, "endfield.wiki.gg": { name: "wiki.gg", url: "https://wiki.gg" }, }; function siteFor(url: string): { name: string; url: string } { try { const host = new URL(url).host; return SITES[host] ?? { name: host, url: `https://${host}` }; } catch { return { name: url, url }; } } /** * Credit, disclaimer, and where the code lives. * * Everything here is derived from the feed rather than written down, so adding * a source or a game credits the right people automatically. A hardcoded list * silently goes stale the moment someone adds the seventh source, and the one * thing a credit must not be is out of date. */ export function Colophon({ sources, now, }: { sources: SourceHealth[]; now: number; }) { // The tree's resolver rather than the module lookup: it is the one that // answers for a reader's own games, and the one that reads a hue for the // theme the page is in. const gameMeta = useGameMeta(); const games = [...new Set(sources.map((s) => s.game))].map((id) => gameMeta(id), ); const studios = [...new Set(games.map((g) => g.studio))]; const { refreshedAt, stale } = freshness(sources, now); // Built once so the sentence a reader reads and the value the bug form is // prefilled with cannot drift apart. const ago = refreshedAt === null ? null : formatRemaining(now - Date.parse(refreshedAt)); const refreshedLine = refreshedAt === null ? null : `${formatAbsolute(refreshedAt, true)} — ${ago} ago`; const sites = [...new Map(sources.map((s) => { const site = siteFor(s.url); return [site.name, site] as const; })).values()]; const named = [...sites.map((s) => s.name), ...studios]; return ( ); }