/* ==========================================================
SafeCadence · all-tools.js · client-side catalog browser
----------------------------------------------------------
The 120 tools from the SafeCadence roadmap, faceted by
12 categories and 5 risk-severity levels. Production will
replace the CURATED array with fetch('/wp-json/wp/v2/sc_tool').
========================================================== */
const CATS = [
{ key:'scam-fraud', label:'Scam & Fraud' },
{ key:'phishing-link', label:'Phishing & Links' },
{ key:'email-security', label:'Email Security' },
{ key:'password-account', label:'Passwords & Accounts' },
{ key:'website-security', label:'Website Security' },
{ key:'network-ip', label:'Network & IP' },
{ key:'home-personal', label:'Home & Personal' },
{ key:'business-smb', label:'Business / SMB' },
{ key:'ai-security', label:'AI Security' },
{ key:'job-identity', label:'Job Seeker & Identity' },
{ key:'viral-trending', label:'Viral / Trending' },
{ key:'utility-tech', label:'Utility / Tech' },
];
const SEVS = [
{ key:'critical', label:'Critical risk' },
{ key:'high', label:'High risk' },
{ key:'medium', label:'Medium risk' },
{ key:'low', label:'Low risk' },
{ key:'info', label:'Informational' },
];
/* slug, name, category, severity, blurb, monthly uses, isNew */
const CURATED = [
// ----- Scam & Fraud (15) -----
['ai-scam-detector', 'AI Scam Detector', 'scam-fraud', 'high', 'Paste any suspicious message — AI explains the scam pattern and what to do next.', 31602, false],
['sms-scam-checker', 'SMS Scam Checker', 'scam-fraud', 'high', 'Paste a text message — we score it against known SMS scam patterns.', 9412, false],
['whatsapp-scam-checker', 'WhatsApp Scam Checker', 'scam-fraud', 'high', 'Common WhatsApp scams: fake family, parcel delivery, job, crypto.', 7820, false],
['telegram-scam-checker', 'Telegram Scam Checker', 'scam-fraud', 'high', 'Telegram-specific scams: investment groups, fake admins, airdrops.', 4910, false],
['marketplace-scam-checker', 'Facebook Marketplace Scam Checker', 'scam-fraud', 'medium', 'Spot fake listings, overpayment scams, and shipping fraud.', 6320, false],
['romance-scam-detector', 'Romance Scam Detector', 'scam-fraud', 'critical', 'Pattern-match dating-app messages against known romance-scam playbooks.', 5410, false],
['crypto-scam-detector', 'Crypto Scam Detector', 'scam-fraud', 'critical', 'Recovery scams, fake exchanges, rug-pull tells, social engineering scripts.', 8810, true ],
['fake-store-detector', 'Fake Store Detector', 'scam-fraud', 'medium', 'Domain age, payment methods, return policy — quick legitimacy check.', 4920, false],
['refund-scam-checker', 'Refund Scam Checker', 'scam-fraud', 'medium', '"We owe you a refund, click here" scams targeting Amazon/eBay/PayPal users.', 3410, false],
['gift-card-scam-detector', 'Gift Card Scam Detector', 'scam-fraud', 'high', 'Anyone asking for gift cards is almost certainly a scammer. Confirm in 5 seconds.',5120, false],
['tech-support-scam-checker', 'Tech Support Scam Checker', 'scam-fraud', 'high', 'Microsoft / Apple / "your computer is infected" pop-ups and cold calls.', 4630, false],
['irs-tax-scam-checker', 'IRS / Tax Scam Checker', 'scam-fraud', 'high', 'Fake IRS / HMRC / CRA messages threatening arrest or owed refund.', 3920, false],
['bank-fraud-message-checker', 'Bank Fraud Message Checker', 'scam-fraud', 'high', 'Fake bank alerts: "unusual login", "card frozen", "verify identity".', 6210, false],
['elder-scam-protection', 'Elder Scam Protection Checker', 'scam-fraud', 'critical', 'Plain-English checks for scams targeting older relatives.', 2810, false],
['social-media-scam-detector', 'Social Media Scam Detector', 'scam-fraud', 'medium', 'Account takeover lures, fake giveaways, "send us $5 to verify".', 3140, false],
// ----- Phishing & Links (10) -----
['phishing-link-checker', 'Phishing Link Checker', 'phishing-link', 'high', 'Domain age, redirects, lookalike risk, known-bad signals — one URL.', 38914, false],
['url-reputation-scanner', 'URL Reputation Scanner', 'phishing-link', 'high', 'Cross-reference any URL against threat-intel feeds.', 8210, false],
['suspicious-domain-checker', 'Suspicious Domain Checker', 'phishing-link', 'medium', 'Newly registered? Privacy WHOIS? Risky TLD? One quick verdict.', 7440, false],
['domain-age-lookup', 'Domain Age Lookup', 'phishing-link', 'low', 'How old is the domain? Brand-new domains are a phishing red flag.', 5910, false],
['redirect-chain-analyzer', 'Redirect Chain Analyzer', 'phishing-link', 'medium', 'See every hop a shortened or wrapped link takes before its destination.', 4210, false],
['fake-brand-url-detector', 'Fake Brand URL Detector', 'phishing-link', 'high', 'Detects "amaz0n.com", "paypa1.com", and other lookalike-domain attacks.', 3820, false],
['qr-code-link-scanner', 'QR Code Link Scanner', 'phishing-link', 'medium', 'Upload a QR image — see the destination URL and risk score before tapping.', 2940, true ],
['url-shortener-risk-checker', 'URL Shortener Risk Checker', 'phishing-link', 'medium', 'bit.ly / tinyurl / t.co — what does this actually point to?', 3610, false],
['download-link-safety-checker', 'Download Link Safety Checker', 'phishing-link', 'high', 'Heuristics for malware-hosting URLs and risky file extensions.', 2810, false],
['homograph-domain-detector', 'Homograph Domain Detector', 'phishing-link', 'high', 'Spot Cyrillic/Greek look-alike characters used in phishing URLs.', 2410, false],
// ----- Email Security (10) -----
['email-header-analyzer', 'Email Header Analyzer', 'email-security', 'medium', 'Paste raw headers — SPF/DKIM/DMARC, relay path, spoofing red flags.', 17830, false],
['spf-checker', 'SPF Checker', 'email-security', 'low', 'Look up + interpret any domain SPF record with includes resolved.', 5810, false],
['dkim-checker', 'DKIM Checker', 'email-security', 'low', 'Validate DKIM keys for any domain + selector.', 4920, false],
['dmarc-checker', 'DMARC Checker', 'email-security', 'low', 'Domain DMARC record with policy + reporting interpretation.', 4120, true ],
['email-spoof-checker', 'Email Spoof Checker', 'email-security', 'high', 'Was this email actually sent by who it claims? Header-driven verdict.', 3810, false],
['disposable-email-detector', 'Disposable Email Detector', 'email-security', 'low', 'Is this address from a temp-mail provider? Useful for signup-abuse defence.', 2910, false],
['sender-reputation-checker', 'Sender Reputation Checker', 'email-security', 'medium', 'IP / domain reputation for any "From" address.', 2810, false],
['mail-server-security-checker', 'Mail Server Security Checker', 'email-security', 'low', 'STARTTLS, MTA-STS, DANE, MX hygiene — one report per domain.', 2210, false],
['business-email-compromise', 'Business Email Compromise Checker', 'email-security', 'critical', 'Spot CEO-fraud / wire-transfer / vendor-impersonation patterns.', 3120, false],
['fake-recruiter-email-checker', 'Fake Recruiter Email Checker', 'email-security', 'high', 'LinkedIn-style recruiter emails analysed for fake-job tells.', 1810, false],
// ----- Passwords & Accounts (10) -----
['password-strength-checker', 'Password Strength Checker', 'password-account', 'info', 'Real entropy + crack-time estimate. 100% in your browser.', 22451, false],
['password-generator', 'Password Generator', 'password-account', 'info', 'Cryptographically strong passwords with full character control.', 12810, false],
['password-leak-checker', 'Password Leak Checker', 'password-account', 'high', 'Have-I-Been-Pwned-style lookup using k-anonymity — we never see your password.',9810, false],
['username-leak-checker', 'Username Leak Checker', 'password-account', 'medium', 'Has this username appeared in any known breaches?', 4210, false],
['credential-stuffing-risk', 'Credential Stuffing Risk Checker', 'password-account', 'medium', 'How exposed are you to attackers reusing leaked credentials?', 3110, false],
['mfa-readiness-checker', 'MFA Readiness Checker', 'password-account', 'low', '20 questions — what MFA gaps you have and which to fix first.', 2410, false],
['password-reuse-checker', 'Password Reuse Checker', 'password-account', 'medium', 'Local-only check for reuse across the passwords you enter.', 2110, false],
['secure-passphrase-generator', 'Secure Passphrase Generator', 'password-account', 'info', 'Memorable diceware-style passphrases with adjustable length.', 3210, false],
['account-takeover-risk-score', 'Account Takeover Risk Score', 'password-account', 'medium', '15-question risk score across your most important online accounts.', 1810, false],
['password-audit-tool', 'Password Audit Tool', 'password-account', 'medium', 'Bulk-audit a password manager export against strength + leak databases.', 1410, false],
// ----- Website Security (10) -----
['website-security-audit', 'Website Security Audit', 'website-security', 'medium', 'SSL, headers, mixed content, exposed CMS — one report per domain.', 11908, false],
['ssl-certificate-checker', 'SSL Certificate Checker', 'website-security', 'low', 'Cert chain, expiry, weak ciphers, hostname mismatch.', 6210, false],
['security-headers-checker', 'Security Headers Checker', 'website-security', 'medium', 'Mozilla-style scoring for CSP, HSTS, X-Frame-Options, and friends.', 4810, false],
['cookie-security-checker', 'Cookie Security Checker', 'website-security', 'low', 'Secure / HttpOnly / SameSite audit for any URL.', 2910, false],
['cms-exposure-checker', 'CMS Exposure Checker', 'website-security', 'medium', 'WordPress / Joomla / Drupal version & plugin disclosure.', 3110, false],
['wordpress-security-scan', 'WordPress Security Scan', 'website-security', 'medium', 'WP-specific: xmlrpc, wp-json exposure, common plugin CVEs.', 2810, false],
['subdomain-finder', 'Subdomain Finder', 'website-security', 'low', 'Passive subdomain enumeration for surface-area review.', 2210, false],
['dns-security-check', 'DNS Security Check', 'website-security', 'low', 'DNSSEC, CAA records, open resolvers, suspicious NS.', 1810, false],
['mixed-content-checker', 'Mixed Content Checker', 'website-security', 'low', 'Find HTTP assets loaded by HTTPS pages.', 1610, false],
['malware-redirect-detector', 'Malware Redirect Detector', 'website-security', 'high', 'User-agent / referer based redirect traps used by malware operators.', 2110, false],
// ----- Network & IP (10) -----
['ip-reputation-checker', 'IP Reputation Checker', 'network-ip', 'medium', 'Cross-reference any IPv4/IPv6 against multiple reputation feeds.', 3810, false],
['ip-geolocation-lookup', 'IP Geolocation Lookup', 'network-ip', 'low', 'Country/region/ISP for any public IP.', 5410, false],
['port-scanner', 'Port Scanner', 'network-ip', 'medium', 'Quick scan of common ports on a public host you own.', 2110, false],
['open-port-checker', 'Open Port Checker', 'network-ip', 'low', 'Single-port reachability tester from our public IP.', 3210, false],
['reverse-dns-lookup', 'Reverse DNS Lookup', 'network-ip', 'low', 'PTR record lookup for any IP.', 2110, false],
['ping-test-tool', 'Ping Test Tool', 'network-ip', 'info', 'Latency + packet loss to any public host.', 1810, false],
['traceroute-tool', 'Traceroute Tool', 'network-ip', 'info', 'Hop-by-hop path to any public host.', 1610, false],
['vpn-leak-test', 'VPN Leak Test', 'network-ip', 'medium', 'IP, DNS, WebRTC leak tests in one place.', 4910, false],
['dns-leak-test', 'DNS Leak Test', 'network-ip', 'medium', 'Is your VPN really tunnelling DNS, or leaking to your ISP?', 3210, false],
['webrtc-leak-test', 'WebRTC Leak Test', 'network-ip', 'medium', 'Spot WebRTC sessions revealing your real IP behind a VPN.', 2810, false],
// ----- Home & Personal (10) -----
['home-wifi-security', 'Home WiFi Security Checker', 'home-personal', 'medium', 'Walk through router config questions — find the 3 things to fix.', 2810, false],
['router-default-password', 'Router Default Password Checker', 'home-personal', 'high', 'Look up the factory password for your router model.', 2210, false],
['public-ip-exposure', 'Public IP Exposure Checker', 'home-personal', 'low', 'What does your public IP reveal? Geo, ISP, exposed services.', 1810, false],
['smart-camera-risk', 'Smart Camera Risk Checker', 'home-personal', 'medium', '20-question audit for home cameras: cloud, default creds, firmware.', 1410, false],
['iot-device-risk', 'IoT Device Risk Checker', 'home-personal', 'medium', 'Doorbells, plugs, locks — segmentation + cloud-account audit.', 1310, false],
['webcam-permission-checker', 'Webcam Permission Checker', 'home-personal', 'low', 'Walk-through: who can see your laptop camera right now?', 1110, false],
['browser-security-checker', 'Browser Security Checker', 'home-personal', 'low', 'Update status, dangerous extensions, common misconfig.', 2210, false],
['device-security-score', 'Device Security Score', 'home-personal', 'medium', '15-question device-hygiene score (laptop or phone).', 2810, false],
['tracking-cookie-scanner', 'Tracking Cookie Scanner', 'home-personal', 'low', 'Browser-side scan of cookies + storage on any page.', 1810, false],
['privacy-score-checker', 'Privacy Score Checker', 'home-personal', 'low', 'Privacy posture scorecard across browser + OS + accounts.', 2110, false],
// ----- Business / SMB (10) -----
['business-cyber-risk', 'SMB Cyber Risk Score', 'business-smb', 'medium', '20 questions — risk score and the 3 things to fix first.', 7410, false],
['cyber-insurance-readiness', 'Cyber Insurance Readiness Tool', 'business-smb', 'medium', 'Pre-application self-audit for the controls underwriters ask about.', 3210, false],
['employee-phishing-trainer', 'Employee Phishing Trainer', 'business-smb', 'medium', 'Pre-built phishing test scenarios with scoring rubric.', 2810, false],
['vendor-risk-analyzer', 'Vendor Risk Analyzer', 'business-smb', 'medium', 'SOC2 / data-handling / breach-history checklist for any vendor.', 2210, false],
['security-policy-generator', 'Security Policy Generator', 'business-smb', 'low', 'Plain-English starter policies for SMBs (10 docs).', 3110, false],
['incident-response-plan-generator','Incident Response Plan Generator','business-smb', 'medium', 'Generate a starter IR plan from 12 questions about your business.', 1810, false],
['cyber-budget-planner', 'Cyber Budget Planner', 'business-smb', 'info', 'What should an SMB realistically spend on security per employee?', 1610, false],
['compliance-gap-checker', 'Compliance Gap Checker', 'business-smb', 'medium', 'SOC2 / ISO27001 / HIPAA / PCI gap snapshot.', 2410, false],
['data-breach-cost-calculator', 'Data Breach Cost Calculator', 'business-smb', 'info', 'Estimate cost based on records, jurisdiction, controls.', 1910, false],
['security-maturity-score', 'Security Maturity Score', 'business-smb', 'info', '5-domain maturity score (NIST CSF style).', 1710, false],
// ----- AI Security (10) -----
['prompt-leak-checker', 'Prompt Leak Checker', 'ai-security', 'medium', 'Paste a prompt — does it accidentally leak system instructions?', 1810, false],
['ai-policy-generator', 'AI Policy Generator', 'ai-security', 'low', 'Starter "acceptable use of AI" policy for your team.', 2410, false],
['shadow-ai-detector', 'Shadow AI Detector', 'ai-security', 'medium', 'Where in your org is unauthorized AI being used?', 1610, false],
['ai-usage-risk-scanner', 'AI Usage Risk Scanner', 'ai-security', 'medium', 'Risk score for any AI workflow against common pitfalls.', 1310, false],
['sensitive-data-prompt-checker', 'Sensitive Data Prompt Checker', 'ai-security', 'high', 'Detect PII / secrets / credentials in prompts before sending.', 2110, false],
['ai-vendor-risk-checker', 'AI Vendor Risk Checker', 'ai-security', 'medium', 'Vendor-AI questionnaire (data residency, training, retention).', 1410, false],
['ai-security-readiness', 'AI Security Readiness Score', 'ai-security', 'medium', '12-question org-level AI security maturity check.', 1610, false],
['unsafe-prompt-detector', 'Unsafe Prompt Detector', 'ai-security', 'medium', 'Heuristics for jailbreak attempts and prompt-injection patterns.', 1410, false],
['ai-privacy-checker', 'AI Privacy Checker', 'ai-security', 'medium', 'GDPR/CCPA risks for any LLM use case.', 1310, false],
['internal-ai-governance', 'Internal AI Governance Tool', 'ai-security', 'low', 'Build a starter governance doc tailored to your stack.', 1110, false],
// ----- Job Seeker & Identity (10) -----
['fake-job-offer-detector', 'Fake Job Offer Detector', 'job-identity', 'high', 'Recruiter messages flagged for known fake-job patterns.', 14221, false],
['recruiter-message-checker', 'Recruiter Message Checker', 'job-identity', 'medium', 'Is this LinkedIn / email recruiter real, or AI-generated bait?', 4810, false],
['resume-data-exposure', 'Resume Data Exposure Checker', 'job-identity', 'low', 'What PII is in your resume that probably shouldn\'t be?', 2110, false],
['identity-theft-risk-score', 'Identity Theft Risk Score', 'job-identity', 'medium', '20-question identity-theft posture score.', 3110, false],
['ssn-exposure-safety-guide', 'SSN Exposure Safety Guide', 'job-identity', 'high', 'Step-by-step plan if your SSN may have been exposed.', 2810, false],
['scholarship-scam-checker', 'Scholarship Scam Checker', 'job-identity', 'medium', '"You won a scholarship — pay this fee" patterns.', 1410, false],
['college-scam-detector', 'College Scam Detector', 'job-identity', 'medium', 'Diploma mills, fake online programs, prepaid tuition scams.', 1310, false],
['rental-scam-detector', 'Rental Scam Detector', 'job-identity', 'high', 'Listings asking for deposits before viewing — common rental scam patterns.', 3210, false],
['seller-risk-checker', 'Seller Risk Checker', 'job-identity', 'medium', 'eBay / Etsy / Craigslist seller-account risk score.', 1810, false],
['gig-job-scam-detector', 'Gig Job Scam Detector', 'job-identity', 'medium', 'TikTok / Upwork / Fiverr "easy money" gig red flags.', 2110, false],
// ----- Viral / Trending (5) -----
['is-this-safe', 'Is This Safe? Instant Checker', 'viral-trending', 'medium', 'Universal safety check: paste any link, image URL, or message.', 6810, false],
['is-this-company-legit', 'Is This Company Legit? Checker', 'viral-trending', 'medium', 'Quick legitimacy snapshot for any company name.', 4210, false],
['is-this-screenshot-fake', 'Is This Screenshot Fake? Detector','viral-trending', 'medium','Image forensic signals for screenshots (alpha).', 3110, true ],
['voice-ai-detector', 'Is This Voice AI? Detector', 'viral-trending', 'medium', 'Upload a clip — signal-level cues for AI-generated voice (alpha).', 2940, true ],
['daily-scam-trends-map', 'Daily Scam Trends Map', 'viral-trending', 'info', 'What scams are spiking this week, by region.', 2110, false],
// ----- Utility / Tech (10) -----
['whois-lookup', 'WHOIS Lookup', 'utility-tech', 'info', 'Registrar, registration date, contact privacy for any domain.', 4810, false],
['dns-lookup', 'DNS Lookup', 'utility-tech', 'info', 'A / AAAA / CNAME / MX / TXT / NS lookup with TTL.', 5210, false],
['mx-record-checker', 'MX Record Checker', 'utility-tech', 'info', 'Mail server records + priority for any domain.', 2810, false],
['http-header-viewer', 'HTTP Header Viewer', 'utility-tech', 'info', 'Request + response headers for any URL.', 2110, false],
['base64-encoder-decoder', 'Base64 Encoder / Decoder', 'utility-tech', 'info', 'Two-way base64 with file support.', 3210, false],
['sha256-hash-generator', 'SHA-256 Hash Generator', 'utility-tech', 'info', 'Hash any text or file in your browser.', 1810, false],
['md5-hash-checker', 'MD5 Hash Checker', 'utility-tech', 'info', 'Generate or verify MD5 (legacy use only).', 1610, false],
['jwt-decoder', 'JWT Decoder', 'utility-tech', 'info', 'Decode + validate a JWT (header / payload / signature).', 2410, false],
['json-validator', 'JSON Validator', 'utility-tech', 'info', 'Validate, format, and sort any JSON payload.', 2810, false],
['robots-txt-checker', 'Robots.txt Checker', 'utility-tech', 'info', 'Test which user-agents can crawl which URLs on your site.', 1410, false],
];
const TOOLS = CURATED.map(([slug, name, cat, sev, desc, uses, isNew], idx) => ({
id: idx, slug, name, cat, sev, desc, uses, new: isNew
}));
const ACTIVE = { cats: new Set(), sevs: new Set(), q: '', sort: 'pop' };
/* ---------- rendering ---------- */
function cntByCat() {
const c = {}; CATS.forEach(c0 => c[c0.key]=0);
TOOLS.forEach(t => c[t.cat]++); return c;
}
function cntBySev() {
const c = {}; SEVS.forEach(s => c[s.key]=0);
TOOLS.forEach(t => c[t.sev]++); return c;
}
function renderFilters() {
const cs = cntByCat(), ss = cntBySev();
document.getElementById('filter-cat').innerHTML = CATS.map(c =>
``).join('');
document.getElementById('filter-sev').innerHTML = SEVS.map(s =>
``).join('');
document.querySelectorAll('.filter-rail input[type=checkbox]').forEach(el => {
el.addEventListener('change', e => {
const k = e.target.dataset.key, kind = e.target.dataset.kind;
const set = kind === 'cat' ? ACTIVE.cats : ACTIVE.sevs;
if (e.target.checked) set.add(k); else set.delete(k);
e.target.closest('label').classList.toggle('active', e.target.checked);
render();
});
});
}
function render() {
const needle = ACTIVE.q.trim().toLowerCase();
const filtered = TOOLS.filter(t => {
if (ACTIVE.cats.size && !ACTIVE.cats.has(t.cat)) return false;
if (ACTIVE.sevs.size && !ACTIVE.sevs.has(t.sev)) return false;
if (needle) {
const hay = (t.name + ' ' + t.desc + ' ' + t.slug).toLowerCase();
if (!hay.includes(needle)) return false;
}
return true;
});
filtered.sort((a,b) => {
if (ACTIVE.sort === 'az') return a.name.localeCompare(b.name);
if (ACTIVE.sort === 'za') return b.name.localeCompare(a.name);
if (ACTIVE.sort === 'new') return (b.new?1:0) - (a.new?1:0) || b.uses - a.uses;
return b.uses - a.uses;
});
const grid = document.getElementById('grid');
const empty = document.getElementById('empty');
document.getElementById('shown-count').textContent = filtered.length;
document.getElementById('total-count').textContent = TOOLS.length;
if (!filtered.length) { grid.innerHTML = ''; empty.hidden = false; return; }
empty.hidden = true;
const catMap = Object.fromEntries(CATS.map(c => [c.key, c.label]));
const sevMap = Object.fromEntries(SEVS.map(s => [s.key, s.label]));
grid.innerHTML = filtered.map(t =>
`
${escapeHtml(t.name)}
${formatUses(t.uses)}
${escapeHtml(t.desc)}
${escapeHtml(catMap[t.cat]||t.cat)}
${t.sev !== 'info' ? `${escapeHtml(sevMap[t.sev]||t.sev)}` : ''}
`
).join('');
}
function formatUses(n) {
if (n >= 1000) return (n/1000).toFixed(n >= 10000 ? 0 : 1) + 'k';
return String(n);
}
function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
function resetAll() {
ACTIVE.cats.clear(); ACTIVE.sevs.clear(); ACTIVE.q = '';
document.getElementById('q').value = '';
document.querySelectorAll('.filter-rail input[type=checkbox]').forEach(el => {
el.checked = false; el.closest('label').classList.remove('active');
});
document.getElementById('sort').value = 'pop';
ACTIVE.sort = 'pop';
render();
}
/* bootstrap */
renderFilters();
document.getElementById('q').addEventListener('input', e => { ACTIVE.q = e.target.value; render(); });
document.getElementById('sort').addEventListener('change', e => { ACTIVE.sort = e.target.value; render(); });
document.getElementById('reset').addEventListener('click', resetAll);
render();
/* '/' focuses search */
document.addEventListener('keydown', e => {
if (e.key === '/' && !['INPUT','TEXTAREA','SELECT'].includes(document.activeElement.tagName)) {
e.preventDefault(); document.getElementById('q').focus();
}
});
/* theme toggle */
const tbtn = document.getElementById('themeToggle');
if (tbtn) tbtn.addEventListener('click', e => { e.preventDefault(); const r = document.documentElement; r.dataset.theme = r.dataset.theme === 'dark' ? 'light' : 'dark'; });