thermograph/static/digest.js
Emi Griffith 8588051141 Remove em-dashes from site copy and tighten the prose (#206)
Replace em-dashes in user-facing copy across the server-rendered pages,
static frontend views, and the strings the app injects at runtime, using
colons, commas, parentheses or full stops as the context wants. Data
placeholder glyphs (a lone "—" for a missing reading) are left alone,
since a hyphen there reads as a minus sign in temperature columns.

Also tighten the high-visibility surfaces (home hero and meta, about,
privacy, city and records ledes, glossary blurbs) toward a plainer,
more direct voice while keeping every factual claim intact.

Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 01:48:33 +00:00

70 lines
2.7 KiB
JavaScript

// Digest signup + product-event beacons. Loaded on every page (base.html.j2).
//
// Both halves degrade: the form is a real <form method="post"> that works with
// this file blocked, and the beacons are fire-and-forget with no UI depending
// on them.
const BASE = new URL("./", import.meta.url).pathname.replace(/\/$/, "");
/** Report one product event. Aggregate-only: the server takes the referrer from
* the request itself, so nothing identifying is sent from here. */
export function track(event) {
try {
const url = `${BASE}/api/v2/event`;
const body = JSON.stringify({ event });
// sendBeacon survives the page unloading, which a fetch() started on a link
// click usually does not — that's the whole point for nav/records clicks.
if (navigator.sendBeacon) {
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
} else {
fetch(url, { method: "POST", body, headers: { "Content-Type": "application/json" },
keepalive: true }).catch(() => {});
}
} catch { /* instrumentation must never break the page */ }
}
// Anything carrying data-event reports itself when activated.
document.addEventListener("click", (e) => {
const el = e.target.closest?.("[data-event]");
if (el) track(el.dataset.event);
});
// --- digest form -------------------------------------------------------------
for (const form of document.querySelectorAll("form[data-digest]")) {
const status = form.querySelector(".digest-status");
const button = form.querySelector("button[type=submit]");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const email = form.querySelector("input[name=email]")?.value?.trim();
if (!email) return;
button.disabled = true;
try {
const res = await fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, place: form.dataset.place || null }),
});
const data = await res.json().catch(() => ({}));
if (status) {
status.textContent = data.message || "Something went wrong. Try again.";
status.hidden = false;
}
if (res.ok) {
// Collapse the inputs on success so the confirmation is the only thing
// left to read; the form is done either way.
form.querySelector(".digest-row")?.setAttribute("hidden", "");
form.querySelector(".digest-note")?.setAttribute("hidden", "");
track("home.digest_signup");
}
} catch {
if (status) {
status.textContent = "Couldn't reach the server. Try again.";
status.hidden = false;
}
} finally {
button.disabled = false;
}
});
}