further changes to backend for adjusting points, showing timestamps, and how categories are listed. uploaded build of react and actual frontend source

This commit is contained in:
jenz
2026-09-30 16:02:33 +02:00
parent 54827b01ba
commit 6019e6854d
40 changed files with 5256 additions and 21 deletions
@@ -0,0 +1,74 @@
const number = new Intl.NumberFormat("en-US");
/** 12.345 -> "12.345", 83.2 -> "1:23.200", 3723.5 -> "1:02:03.500". */
export function formatTime(seconds: number): string {
const ms = Math.round(seconds * 1000);
const h = Math.floor(ms / 3_600_000);
const m = Math.floor((ms % 3_600_000) / 60_000);
const s = Math.floor((ms % 60_000) / 1000);
const rest = String(ms % 1000).padStart(3, "0");
if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${rest}`;
if (m > 0) return `${m}:${String(s).padStart(2, "0")}.${rest}`;
return `${s}.${rest}`;
}
/** Signed difference, e.g. "+1.204" or "−0.500". */
export function formatDelta(seconds: number): string {
const sign = seconds < 0 ? "−" : "+";
return sign + formatTime(Math.abs(seconds));
}
export function formatNumber(n: number): string {
return number.format(n);
}
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
["year", 31_536_000],
["month", 2_592_000],
["week", 604_800],
["day", 86_400],
["hour", 3_600],
["minute", 60],
];
/** Unix seconds -> "3 weeks ago". */
export function timeAgo(unixSeconds: number, now = Date.now() / 1000): string {
// Never show "in 3 minutes" when a server clock is slightly ahead.
const diff = Math.min(0, unixSeconds - now);
for (const [unit, size] of UNITS) {
if (Math.abs(diff) >= size) return rtf.format(Math.round(diff / size), unit);
}
return "just now";
}
/** Unix seconds -> "27 Sep 2026, 14:03:09" in the viewer's own time zone. */
export function formatDate(unixSeconds: number): string {
return new Date(unixSeconds * 1000).toLocaleString("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
}
export function serverLabel(tag: string): string {
return tag.toUpperCase();
}
/** Which bonus band a position is in, for the label next to the multiplier. */
export function bonusLabel(position: number, completions: number): string | null {
if (completions < 100) return null;
const x = (position - 1) / completions;
if (x < 0.01) return "Top 1%";
if (x < 0.05) return "Top 5%";
return null;
}
/** How many players on a board get points (half from 200 completions on). */
export function pointedCount(completions: number): number {
return completions < 200 ? completions : Math.floor(completions / 2);
}