The new reader app replaces the old UI: Checklist with a rail (focus pills, Next to expire, today's dailies, pinned routines), Timeline in a new and a classic look, Routines, Archive and a Settings area, in a Structured (default) and a Relaxed interface. It follows the approved Round 6 B and Round 7 mockups, corrected screen by screen with the owner. Reader data moves to a v2 store (gacha-tracker:v2:*). Existing v1 data is migrated on first load, a recovery copy is written first, and the v1 keys are never removed, so a rollback finds them unchanged. Redeem codes for Genshin arrive as their own feed. Also: live's urgency ramp, editable per theme; completion and section-complete fireworks; a returning-user tour; the design mockups, specs, plans, reviews and handoff record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
107 lines
5.2 KiB
HTML
107 lines
5.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<base href="__BASE__" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<title>Event Clock — gacha events by what expires next</title>
|
|
<meta
|
|
name="description"
|
|
content="Live and upcoming events across your gacha games, sorted by what ends soonest."
|
|
/>
|
|
<meta name="theme-color" content="#12141c" />
|
|
|
|
<!--
|
|
This page ships dark and offers light; both are drawn deliberately, and
|
|
neither wants a third party guessing at one from the other.
|
|
|
|
color-scheme states the default rendering, so form controls and scrollbars
|
|
come out dark before any stylesheet has loaded. It is only the default:
|
|
styles.css sets the real `color-scheme` property per theme, and that wins.
|
|
|
|
darkreader-lock is Dark Reader's documented opt-out, and it still applies
|
|
now that there is a light theme — arguably more. The colours in this UI
|
|
carry meaning (the urgency ramp, the per-game hues), and an extension
|
|
re-mapping them breaks the reading, whichever ground it starts from. A
|
|
reader who wants light has a control in settings that was designed.
|
|
-->
|
|
<meta name="color-scheme" content="dark" />
|
|
<meta name="darkreader-lock" />
|
|
|
|
<!--
|
|
The theme, before first paint.
|
|
|
|
Dark is the default and needs nothing: it is what the stylesheet says with
|
|
no attribute set. A reader who chose light is the case this exists for —
|
|
without it they get a dark page for as long as the bundle takes to arrive
|
|
and mount, on every single load, which is a flash of the thing they
|
|
switched off.
|
|
|
|
It reads the same stored key as the app (`state/storage.ts`) and applies
|
|
the same attribute (`state/theme.ts`), and it cannot import either, so a
|
|
test pins the three copies together. Wrapped in try/catch and doing
|
|
nothing on failure: a reader with storage disabled gets the default theme,
|
|
never a blank page.
|
|
-->
|
|
<script>
|
|
(function () {
|
|
try {
|
|
var prefs = JSON.parse(
|
|
localStorage.getItem("gacha-tracker:v1:prefs") || "{}",
|
|
);
|
|
var reader = JSON.parse(localStorage.getItem("gacha-tracker:v2:reader") || "null");
|
|
if (reader && reader.version === 2 && reader.prefs) prefs = reader.prefs;
|
|
document.documentElement.dataset.interface = prefs.interface === "relaxed" ? "relaxed" : "structured";
|
|
document.documentElement.dataset.density = prefs.density === "compact" ? "compact" : "standard";
|
|
var light =
|
|
prefs.theme === "light" ||
|
|
(prefs.theme === "system" &&
|
|
window.matchMedia("(prefers-color-scheme: light)").matches);
|
|
if (!light) return;
|
|
document.documentElement.dataset.theme = "light";
|
|
var meta = document.querySelector('meta[name="theme-color"]');
|
|
if (meta) meta.setAttribute("content", "#edf0f7");
|
|
} catch (e) {
|
|
/* No stored preference we can read: the default stands. */
|
|
}
|
|
})();
|
|
</script>
|
|
<!--
|
|
The feed, in flight before the bundle that asks for it.
|
|
|
|
`fetchFeed` cannot run until main.js has downloaded, parsed and mounted
|
|
React, so the one request the page exists to make was starting last: on a
|
|
4G/4x-CPU profile the bundle finished at ~900ms and the feed only then
|
|
began, landing at ~1260ms. Nothing about it depends on the bundle — the URL
|
|
is a constant — so the preload scanner can have it on the wire in the first
|
|
few milliseconds instead, alongside the JS rather than behind it.
|
|
|
|
Resolved against `<base>` above, exactly as `fetchFeed` resolves it against
|
|
`document.baseURI`, so this is correct at a domain root and under a Pages
|
|
subpath alike. `crossorigin` is not decoration: `as="fetch"` has to match
|
|
the credentials mode of the `fetch()` that follows, and a mismatch is not a
|
|
no-op — the browser discards the preload and downloads the feed a second
|
|
time, which is worse than not preloading at all.
|
|
-->
|
|
<link rel="preload" href="data/events.v1.json" as="fetch" crossorigin />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Barlow+Condensed:wght@500;600;700&family=Bricolage+Grotesque:wght@500;600;700&family=Figtree:wght@400;500;600;700&family=Chakra+Petch:wght@500;600;700&family=Public+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<link rel="manifest" href="manifest.webmanifest" />
|
|
<link rel="icon" href="icon.svg" type="image/svg+xml" />
|
|
<link rel="apple-touch-icon" href="icon.svg" />
|
|
<link rel="stylesheet" href="styles.css" />
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<script type="module" src="main.js"></script>
|
|
<noscript>
|
|
<img src="https://analytics.lucaswinther.info/ingress/b85b98e5-4692-411f-b6f6-bbe7e54d8c83/pixel.gif">
|
|
</noscript>
|
|
<script defer src="https://analytics.lucaswinther.info/ingress/b85b98e5-4692-411f-b6f6-bbe7e54d8c83/script.js"></script>
|
|
</body>
|
|
</html>
|