2026-07-20 00:40:04 +00:00
|
|
|
|
// A small, dismissible "Add to Home Screen" hint for iPhone/iPad visitors.
|
|
|
|
|
|
//
|
|
|
|
|
|
// iOS only grants Web Push to a site the user has added to the Home Screen via
|
|
|
|
|
|
// Safari's Share sheet (iOS 16.4+). There is no beforeinstallprompt on iOS, so
|
|
|
|
|
|
// the only nudge available is telling people where the control is. This module
|
|
|
|
|
|
// shows that nudge — and only when it can actually help: real iOS Safari, not
|
|
|
|
|
|
// already installed, not previously dismissed. Everything degrades to nothing.
|
|
|
|
|
|
|
|
|
|
|
|
// Not the "tg:" prefix — cache.js sweeps that namespace on every load. The
|
|
|
|
|
|
// durable app preferences ("thermograph:unit", "thermograph:loc") use this one.
|
|
|
|
|
|
const KEY = "thermograph:ios-a2hs";
|
|
|
|
|
|
|
|
|
|
|
|
function isIOS() {
|
|
|
|
|
|
const ua = navigator.userAgent || "";
|
|
|
|
|
|
// iPadOS 13+ reports as "Macintosh"; a touch point count disambiguates it.
|
|
|
|
|
|
return /iphone|ipad|ipod/i.test(ua) ||
|
|
|
|
|
|
(/Macintosh/.test(ua) && (navigator.maxTouchPoints || 0) > 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isStandalone() {
|
|
|
|
|
|
return window.navigator.standalone === true ||
|
|
|
|
|
|
(window.matchMedia && window.matchMedia("(display-mode: standalone)").matches);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isSafari() {
|
|
|
|
|
|
// Add to Home Screen only works from Safari proper — not Chrome (CriOS),
|
|
|
|
|
|
// Firefox (FxiOS), Edge (EdgiOS), Opera (OPiOS), or most in-app webviews.
|
|
|
|
|
|
const ua = navigator.userAgent || "";
|
|
|
|
|
|
if (/crios|fxios|edgios|opios/i.test(ua)) return false;
|
|
|
|
|
|
return /safari/i.test(ua);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function dismissed() {
|
|
|
|
|
|
try { return localStorage.getItem(KEY) === "off"; } catch (e) { return false; }
|
|
|
|
|
|
}
|
|
|
|
|
|
function remember() {
|
|
|
|
|
|
try { localStorage.setItem(KEY, "off"); } catch (e) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function build() {
|
|
|
|
|
|
const el = document.createElement("div");
|
|
|
|
|
|
el.className = "ios-a2hs";
|
|
|
|
|
|
el.setAttribute("role", "note");
|
|
|
|
|
|
el.setAttribute("aria-label", "Install Thermograph on your Home Screen");
|
|
|
|
|
|
// The share glyph mirrors iOS's own Share icon so the instruction is scannable.
|
|
|
|
|
|
el.innerHTML =
|
|
|
|
|
|
'<svg class="ios-a2hs-share" viewBox="0 0 24 24" aria-hidden="true" fill="none" ' +
|
|
|
|
|
|
'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
|
|
|
|
'<path d="M12 3v12"/><path d="M8 7l4-4 4 4"/>' +
|
|
|
|
|
|
'<path d="M5 12v7a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7"/></svg>' +
|
|
|
|
|
|
'<div class="ios-a2hs-text"><b>Add Thermograph to your Home Screen</b>' +
|
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
|
|
|
|
'<span>Tap Share, then <b>Add to Home Screen</b>. It’s how to get weather alerts on iPhone.</span></div>' +
|
2026-07-20 00:40:04 +00:00
|
|
|
|
'<button type="button" class="ios-a2hs-x" aria-label="Dismiss">×</button>';
|
|
|
|
|
|
el.querySelector(".ios-a2hs-x").addEventListener("click", () => {
|
|
|
|
|
|
remember();
|
|
|
|
|
|
el.remove();
|
|
|
|
|
|
});
|
|
|
|
|
|
return el;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function maybeShow() {
|
|
|
|
|
|
if (!isIOS() || isStandalone() || !isSafari() || dismissed()) return;
|
|
|
|
|
|
if (document.querySelector(".ios-a2hs")) return;
|
|
|
|
|
|
document.body.appendChild(build());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wait a beat so the hint never competes with first paint, and only on an
|
|
|
|
|
|
// interactive document.
|
|
|
|
|
|
if (document.readyState === "loading") {
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => setTimeout(maybeShow, 1200));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setTimeout(maybeShow, 1200);
|
|
|
|
|
|
}
|