/* ========================================================== 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'; });