// Temperature units — the shared °C/°F toggle and unit-aware formatting. // The API always speaks Fahrenheit; the unit is a pure display concern. Pages // format temps through `fmtTemp`/`toUnit` and re-render on `onUnitChange`, so the // stored/plotted values stay in °F and only the numbers shown flip. const UNIT_KEY = "thermograph:unit"; // 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; } // A Fahrenheit value as a number in the active unit. export function toUnit(vF) { return _unit === "C" ? (vF - 32) * 5 / 9 : vF; } // A Fahrenheit value formatted as a rounded temperature in the active unit. // `withLetter` appends the C/F letter (off by default — the nav toggle shows it). export function fmtTemp(vF, withLetter) { if (vF == null || isNaN(vF)) return "—"; return `${Math.round(toUnit(vF))}°${withLetter ? _unit : ""}`; } // A Fahrenheit-degree *difference* (a span or tolerance, not an absolute // reading) formatted in the active unit. Differences scale by 5/9 with no 32° // offset, so they can't go through toUnit/fmtTemp. export function fmtDelta(dF, withLetter) { if (dF == null || isNaN(dF)) return "—"; const d = _unit === "C" ? dF * 5 / 9 : dF; return `${Math.round(d)}°${withLetter ? _unit : ""}`; } export function onUnitChange(cb) { _unitCbs.push(cb); } export function setUnit(u) { const nu = (u === "C") ? "C" : "F"; if (nu === _unit) return; _unit = nu; try { localStorage.setItem(UNIT_KEY, _unit); } catch (e) {} document.querySelectorAll(".unit-toggle").forEach(syncUnitToggle); _unitCbs.forEach((cb) => { try { cb(_unit); } catch (e) {} }); } function syncUnitToggle(wrap) { wrap.querySelectorAll("button[data-unit]").forEach((b) => { const on = b.dataset.unit === _unit; b.classList.toggle("active", on); b.setAttribute("aria-pressed", on ? "true" : "false"); }); } // Drop a segmented °F/°C control into the header's top-right, ahead of the view // switcher. A page opts out with data-no-unit-toggle on
. (function buildUnitToggle() { const brand = document.querySelector(".brand"); if (!brand || brand.querySelector(".unit-toggle")) return; if (document.body.dataset.noUnitToggle != null) return; const wrap = document.createElement("div"); wrap.className = "unit-toggle"; wrap.setAttribute("role", "group"); wrap.setAttribute("aria-label", "Temperature units"); wrap.innerHTML = '' + ''; wrap.addEventListener("click", (e) => { const b = e.target.closest("button[data-unit]"); if (b) setUnit(b.dataset.unit); }); // Lives inside the nav menu panel: display:contents keeps it inline top-right // on desktop; on phones it stacks inside the hamburger dropdown. Fall back to a // brand sibling if the panel isn't present. const panel = brand.querySelector(".nav-panel"); if (panel) panel.appendChild(wrap); else brand.insertBefore(wrap, brand.querySelector(".view-nav")); syncUnitToggle(wrap); })();