feat: credit the sources, disclaim affiliation, link the repo
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) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
817ad41dcd
commit
1a04e9472b
+41
-13
@@ -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<View>("soon");
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
const now = useNow();
|
||||
const online = useOnline();
|
||||
const { prefs, update, toggleGame } = usePrefs();
|
||||
const { completions, toggle, merge } = useCompletions();
|
||||
|
||||
@@ -122,8 +146,14 @@ export function App() {
|
||||
<p className="font-display text-[0.9375rem] font-bold tracking-[0.02em]">
|
||||
EVENT<span className="text-near">CLOCK</span>
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs text-faint">
|
||||
<p className="mt-0.5 flex items-center gap-1.5 text-xs text-faint">
|
||||
{live.length} live · {upcoming.length} upcoming
|
||||
{!online && (
|
||||
<span className="inline-flex items-center gap-1 text-soon">
|
||||
<span aria-hidden className="size-1.5 rounded-full bg-soon" />
|
||||
offline
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -219,19 +249,17 @@ export function App() {
|
||||
onImport={(file) => void importProgress(file, merge)}
|
||||
/>
|
||||
|
||||
<footer className="px-4 pb-10 pt-2 text-xs leading-relaxed text-faint">
|
||||
<p>
|
||||
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 && (
|
||||
<p className="border-t border-hairline px-4 py-3 text-xs leading-relaxed text-soon">
|
||||
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.
|
||||
</p>
|
||||
{staleSources.length > 0 && (
|
||||
<p className="mt-2 text-soon">
|
||||
{staleSources.length} source
|
||||
{staleSources.length > 1 ? "s have" : " has"} not refreshed in over two
|
||||
days. Some end dates may have moved.
|
||||
</p>
|
||||
)}
|
||||
</footer>
|
||||
)}
|
||||
|
||||
<Colophon sources={state.feed.sources} staleCount={staleSources.length} />
|
||||
|
||||
{openRow !== null && (
|
||||
<EventDetail
|
||||
|
||||
Reference in New Issue
Block a user