/** * WordHighlighter — Phase 8: Suspicious Word Highlighter * Highlights suspicious / clickbait trigger words in the claim text. * Uses triggered_features from Layer 1 as hint words. * * architect-review: pure presentational, no side-effects. * web-design-guidelines: uses with visible styles, screen-reader friendly. */ // Common suspicious/misinformation signal words to highlight const SUSPICIOUS_PATTERNS = [ // English signals /\b(shocking|exposed|revealed|secret|hoax|fake|false|confirmed|breaking|urgent|emergency|exclusive|banned|cover[\s-]?up|conspiracy|miracle|crisis|scandal|leaked|hidden|truth|they don't want you to know)\b/gi, // Filipino signals /\b(grabe|nakakagulat|totoo|peke|huwag maniwala|nagsisinungaling|lihim|inilabas|natuklasan|katotohanan|panlilinlang|kahirap-hirap|itinatago)\b/gi, ] function getHighlightedSegments(text, triggerWords = []) { if (!text) return [] // Build a combined pattern from both static patterns + dynamic trigger words const allPatterns = [...SUSPICIOUS_PATTERNS] if (triggerWords.length > 0) { const escaped = triggerWords.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) allPatterns.push(new RegExp(`\\b(${escaped.join('|')})\\b`, 'gi')) } // Find all match intervals const matches = [] for (const pattern of allPatterns) { pattern.lastIndex = 0 let m while ((m = pattern.exec(text)) !== null) { matches.push({ start: m.index, end: m.index + m[0].length, word: m[0] }) } } if (matches.length === 0) return [{ text, highlighted: false }] // Sort + merge overlapping intervals matches.sort((a, b) => a.start - b.start) const merged = [] for (const m of matches) { const last = merged[merged.length - 1] if (last && m.start <= last.end) { last.end = Math.max(last.end, m.end) } else { merged.push({ ...m }) } } // Build segments const segments = [] let cursor = 0 for (const { start, end, word } of merged) { if (cursor < start) segments.push({ text: text.slice(cursor, start), highlighted: false }) segments.push({ text: text.slice(start, end), highlighted: true, word }) cursor = end } if (cursor < text.length) segments.push({ text: text.slice(cursor), highlighted: false }) return segments } export default function WordHighlighter({ text = '', triggerWords = [], className = '' }) { const segments = getHighlightedSegments(text, triggerWords) const hitCount = segments.filter(s => s.highlighted).length if (segments.length === 1 && !segments[0].highlighted) { // No suspicious words found return (

{text}

) } return (
{hitCount > 0 && (

⚠ {hitCount} suspicious signal{hitCount !== 1 ? 's' : ''} detected

)}

{segments.map((seg, i) => seg.highlighted ? ( {seg.text} ) : ( {seg.text} ) )}

) }