/** * PhilVerify — Popup Script * Controls the extension popup: verify tab, history tab, settings tab. */ 'use strict' // ── Constants ───────────────────────────────────────────────────────────────── const VERDICT_COLORS = { 'Credible': '#16a34a', 'Unverified': '#d97706', 'Likely Fake': '#dc2626', } // ── Helpers ─────────────────────────────────────────────────────────────────── /** Escape HTML special chars to prevent XSS in innerHTML templates */ function safeText(str) { if (str == null) return '' return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') } /** Allow only http/https URLs; return '#' for anything else */ function safeUrl(url) { if (!url) return '#' try { const u = new URL(url) return (u.protocol === 'http:' || u.protocol === 'https:') ? u.href : '#' } catch { return '#' } } function msg(obj) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage(obj, (resp) => { if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message)) else resolve(resp) }) }) } function timeAgo(iso) { const diff = Date.now() - new Date(iso).getTime() if (diff < 60_000) return 'just now' if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago` if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago` return `${Math.floor(diff / 86_400_000)}d ago` } function isUrl(s) { try { new URL(s); return s.startsWith('http'); } catch { return false } } // ── Render helpers ──────────────────────────────────────────────────────────── function renderResult(result, container) { const color = VERDICT_COLORS[result.verdict] ?? '#5c554e' const topSource = result.layer2?.sources?.[0] const features = result.layer1?.triggered_features ?? [] const modelTier = result.layer1?.model_tier const claimMethod = result.layer2?.claim_method const hasFooter = modelTier || claimMethod container.innerHTML = `