/* * theme.ts — PX-02: the Telegram theme signal → data-tg-theme slot switch. * --------------------------------------------------------------------- * Light/dark is a SLOT SWITCH, not a color import: Telegram's signals * (`colorScheme`, or the luminance of `themeParams.header_bg_color` as * the old-client fallback) select between two Rams token pairs defined * in theme.css (`--shell-bg` / `--shell-on-surface`). Telegram's own hex * values are read ONLY as a 0–1 luminance signal (T-03-11) — no * Telegram color ever reaches a CSS custom property. * * Pitfall 7 (D-10): applyTheme's ONLY statement is the data-attribute * set. MapLibre is deliberately absent — the map style is never re-set * on themeChanged; CSS custom properties re-slot and the map stays * Rams-constant. The invariant is greppable (theme.test.mjs asserts no * setStyle call exists in this source). */ import type { TelegramWebAppLike } from "./webapp.ts"; type ThemeBranch = "light" | "dark"; export function resolveBranch(wa: TelegramWebAppLike): ThemeBranch { // Primary signal — present on all clients this phase targets. if (wa.colorScheme === "dark" || wa.colorScheme === "light") return wa.colorScheme; // Fallback (UI-SPEC §Theme mapping): luminance of header_bg_color ≥ 0.5 → light. const hex = wa.themeParams?.header_bg_color; if (typeof hex === "string" && /^#[0-9a-f]{6}$/i.test(hex)) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); const lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; return lum >= 0.5 ? "light" : "dark"; } return "light"; } export function applyTheme(wa: TelegramWebAppLike): void { // The ONLY statement a theme change is allowed to touch. MapLibre is // deliberately absent from this function (D-10 / RESEARCH Pitfall 7). document.documentElement.dataset.tgTheme = resolveBranch(wa); } export function watchTheme(wa: TelegramWebAppLike): void { applyTheme(wa); wa.onEvent?.("themeChanged", () => applyTheme(wa)); }