`fetchFeed` cannot start until main.js has downloaded, parsed and mounted React, so the one request this page exists to make was the last one to leave. Nothing about the URL depends on the bundle, so the preload scanner can have it in flight in the first few milliseconds instead. Measured on a 4G/4x-CPU profile with a cold cache and gzip on: the events reach the screen at ~470ms instead of ~570ms, and the loading state stops being something a reader can see rather than merely being brief. First contentful paint is unchanged within noise — the win is that the feed is off the critical path entirely, arriving at 216ms while the bundle is still downloading. `crossorigin` on that link 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 throws the preload away and downloads the feed twice, which is worse than not preloading at all. Verified by counting requests in a real browser, because no test here can see it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
103 lines
4.7 KiB
HTML
103 lines
4.7 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 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=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>
|