From 1a04e9472b3fe800d457901e63056ac2796112b0 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sat, 15 Aug 2026 00:52:41 +0200 Subject: [PATCH] feat: credit the sources, disclaim affiliation, link the repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Game8's editors compile the calendars this depends on, and the studios make the games — both belong on the same screen as the data rather than one navigation step away. States plainly that this is an unofficial fan-made tool with no connection to Game8, HoYoverse, Kuro Games, Hypergryph or Hotta Studio, and that the source page is the authority when the two disagree. Co-Authored-By: Claude Opus 5 (1M context) --- src/client/App.tsx | 54 +++++++++++++----- src/client/components/Colophon.tsx | 91 ++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/client/components/Colophon.tsx diff --git a/src/client/App.tsx b/src/client/App.tsx index 220012e..84712d5 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -6,6 +6,7 @@ import { EventRow, type RowEvent } from "./components/EventRow.tsx"; import { NextUp } from "./components/NextUp.tsx"; import { Timeline } from "./components/Timeline.tsx"; import { Welcome } from "./components/Welcome.tsx"; +import { Colophon } from "./components/Colophon.tsx"; import { useCompletions } from "./state/useCompletions.ts"; import { usePrefs } from "./state/usePrefs.ts"; import { clockFor, DAY, endingSoonestFirst, formatRemaining } from "../shared/time.ts"; @@ -13,6 +14,28 @@ import type { GameId } from "../shared/schema.ts"; type View = "soon" | "calendar"; +/** + * Connection state. Offline is not an error here — the service worker serves + * the last feed it saw and countdowns run off the local clock — but it does + * change what the reader can trust, so it is surfaced rather than hidden. + */ +function useOnline(): boolean { + const [online, setOnline] = useState(() => + typeof navigator === "undefined" ? true : navigator.onLine, + ); + useEffect(() => { + const up = () => setOnline(true); + const down = () => setOnline(false); + window.addEventListener("online", up); + window.addEventListener("offline", down); + return () => { + window.removeEventListener("online", up); + window.removeEventListener("offline", down); + }; + }, []); + return online; +} + /** Ticks once a second so countdowns stay honest without re-fetching. */ function useNow(intervalMs = 1000): number { const [now, setNow] = useState(() => Date.now()); @@ -28,6 +51,7 @@ export function App() { const [view, setView] = useState("soon"); const [openId, setOpenId] = useState(null); const now = useNow(); + const online = useOnline(); const { prefs, update, toggleGame } = usePrefs(); const { completions, toggle, merge } = useCompletions(); @@ -122,8 +146,14 @@ export function App() {

EVENTCLOCK

-

+

{live.length} live · {upcoming.length} upcoming + {!online && ( + + + offline + + )}

@@ -219,19 +249,17 @@ export function App() { onImport={(file) => void importProgress(file, merge)} /> -
-

- Dates come from community wikis and are shown in your local time. Every - event links to its source — check there before the last hours. + {!online && ( +

+ You're offline. These are the events last downloaded + {" "} + {formatRemaining(now - Date.parse(state.feed.generatedAt))} ago, and + countdowns are still running. Anything rescheduled since then won't + show until you reconnect.

- {staleSources.length > 0 && ( -

- {staleSources.length} source - {staleSources.length > 1 ? "s have" : " has"} not refreshed in over two - days. Some end dates may have moved. -

- )} -
+ )} + + {openRow !== null && ( s.game))]; + + return ( + + ); +}