diff --git a/static/account.js b/static/account.js
index 8e42d03..94b3f21 100644
--- a/static/account.js
+++ b/static/account.js
@@ -8,10 +8,17 @@
let currentUser = null; // {id, email, display_name} or null
const authCbs = []; // notified on login/logout so pages can re-gate
+// The app's base path (e.g. "/thermograph"), derived from this module's own URL
+// — it's always served from `${base}/account.js`, so this resolves correctly on
+// both the depth-1 interactive pages and the deep SEO URLs (/climate/{slug}/…),
+// where a directory-relative "api/v2/…" would otherwise resolve wrong and 404.
+const APP_BASE = new URL(".", import.meta.url).pathname.replace(/\/$/, "");
+const u = (path) => `${APP_BASE}/${String(path).replace(/^\//, "")}`;
+
// --- shared fetch helper -----------------------------------------------------
-// Base-relative URL (resolves under /thermograph automatically), cookie sent,
-// and a constant X-TG-Auth header the server can require as cheap CSRF defense
-// (trivial same-origin, impossible cross-site without a preflight we never grant).
+// Absolute app-base URL (so it works from any page depth), cookie sent, and a
+// constant X-TG-Auth header the server can require as cheap CSRF defense (trivial
+// same-origin, impossible cross-site without a preflight we never grant).
export async function apiFetch(path, { method = "GET", json, form } = {}) {
const opts = { method, credentials: "same-origin", headers: { "X-TG-Auth": "1" } };
if (json !== undefined) {
@@ -21,7 +28,7 @@ export async function apiFetch(path, { method = "GET", json, form } = {}) {
opts.headers["Content-Type"] = "application/x-www-form-urlencoded";
opts.body = new URLSearchParams(form).toString();
}
- return fetch(path, opts);
+ return fetch(u(path), opts);
}
// Parse a JSON response, throwing a friendly Error on failure. fastapi-users
@@ -285,7 +292,7 @@ function renderHeader() {
`;
diff --git a/static/climate.js b/static/climate.js
new file mode 100644
index 0000000..d30f9b6
--- /dev/null
+++ b/static/climate.js
@@ -0,0 +1,18 @@
+// Client-side temperature converter for the server-rendered climate/city/record
+// pages. Those pages render every temperature as `72°F` (see content.py `_temp`/`_temp_bare`) so the
+// value is real in the HTML for crawlers and no-JS visitors. Here we rewrite each
+// span to the active unit on load and whenever the °F/°C toggle changes — reusing
+// the same unit machinery as the interactive pages. Importing units.js also boots
+// its toggle-injection IIFE, so this one script gives the pages a working toggle.
+import { fmtTemp, onUnitChange } from "./units.js";
+
+function paint() {
+ for (const el of document.querySelectorAll(".temp[data-temp-f]")) {
+ const f = parseFloat(el.dataset.tempF);
+ if (!Number.isNaN(f)) el.textContent = fmtTemp(f, !el.hasAttribute("data-bare"));
+ }
+}
+
+paint();
+onUnitChange(paint);
diff --git a/static/units.js b/static/units.js
index 8a81e68..c4b0187 100644
--- a/static/units.js
+++ b/static/units.js
@@ -4,7 +4,24 @@
// stored/plotted values stay in °F and only the numbers shown flip.
const UNIT_KEY = "thermograph:unit";
-let _unit = (localStorage.getItem(UNIT_KEY) === "C") ? "C" : "F";
+
+// First-time default: pick °F only for the countries that actually use it (the US
+// plus a handful of territories/nations), otherwise °C — read off the browser
+// locale's region subtag (en-US → F, de-DE → C). A stored choice always wins, so
+// this only affects visitors who've never toggled. Not persisted, so an unstored
+// visitor keeps re-deriving the same locale default until they pick one.
+const F_REGIONS = new Set(["US", "PR", "GU", "VI", "AS", "MP", "UM", "BS", "BZ", "KY", "PW", "FM", "MH", "LR"]);
+function defaultUnit() {
+ const langs = (navigator.languages && navigator.languages.length)
+ ? navigator.languages : [navigator.language];
+ for (const l of langs) {
+ const m = /-([A-Za-z]{2})\b/.exec(l || "");
+ if (m) return F_REGIONS.has(m[1].toUpperCase()) ? "F" : "C";
+ }
+ return "F"; // no region subtag to go on → keep the historical default
+}
+const _stored = localStorage.getItem(UNIT_KEY);
+let _unit = (_stored === "C" || _stored === "F") ? _stored : defaultUnit();
const _unitCbs = [];
export function getUnit() { return _unit; }