{current.title}
{b[1]}
; if (b[0] === 'h2') return{b[1]}
; if (b[0] === 'pre') return{b[1]};
if (b[0] === 'quote') return {b[1]}; return null; })}
/* global React, ReactDOM */ const { useState, useEffect } = React; const NICHES = [ { slug: 'pricing-watch', name: 'Pricing Watch', count: 12, active: true }, { slug: 'competitor-launches', name: 'Competitor Launches', count: 7 }, { slug: 'customer-language', name: 'Customer Language', count: 23 }, { slug: 'ai-seo-watch', name: 'AI SEO Watch', count: 5 }, { slug: 'partner-news', name: 'Partner News', count: 31 }, { slug: 'category-signals', name: 'Category Signals', count: 14 }, ]; const ITEMS = [ { id: 1, score: 0.94, src: 'competitor.com', time: '14m', title: 'RivalCo quietly adds a usage-based seat cap to its Pro plan', why: 'pricing page changed, plan language is concrete, buyer impact is clear', body: [ ['p', "RivalCo's public pricing page now gates Pro teams by seat count and monthly tracked events. The copy moved from \"unlimited team usage\" to \"built for focused teams up to 12 seats.\""], ['p', "That gives your next sales call a clean comparison point: they are narrowing Pro while your plan can stay framed around monitored sources and briefing cadence."], ['h2', <>Suggested angle>], ['p', <>Publish a short comparison note around predictable monitoring costs, not a feature-by-feature fight.>], ['pre', `{\n "signal": "pricing_change",\n "competitor": "RivalCo",\n "confidence": 0.94,\n "suggested_action": "update battlecard"\n}`], ['p', 'This is queued for the morning digest and the midday update because pricing pages are inside the Pro source set.'], ['quote', "This is the kind of change a founder wants before the weekly pipeline review, not after a prospect brings it up."], ], }, { id: 2, score: 0.91, src: 'reddit.com', time: '38m', title: 'Thread compares switching costs across three onboarding tools', why: 'buyer language, named alternatives, concrete objections', }, { id: 3, score: 0.89, src: 'producthunt.com', time: '1h', title: 'New entrant launches with "done-for-you competitor monitoring"', why: 'positioning overlap, launch copy, early comments mention budget', }, { id: 4, score: 0.82, src: 'linkedin.com', time: '2h', title: 'VP Marketing post gets traction for "founder-led category pages"', why: 'category language, tactical examples, strong comments', }, { id: 5, score: 0.78, src: 'google.com/alerts', time: '3h', title: 'Analyst note calls out consolidation in sales-assist software', why: 'market framing, source is fresh, useful for newsletter angle', }, { id: 6, score: 0.71, src: 'g2.com', time: '4h', title: 'Three reviews repeat the same complaint: "reporting is late"', why: 'repeated customer language, maps to your speed claim', }, { id: 7, score: 0.68, src: 'news.ycombinator.com', time: '5h', title: '"What do you actually use for competitive intelligence?"', why: 'tool-discovery thread, buyer pain, alternatives named', }, { id: 8, score: 0.63, src: 'company blog', time: '6h', title: 'Partner marketplace adds a dedicated AI workflows category', why: 'distribution opportunity, partner language, clear source link', }, ]; function AppReader() { const [activeNiche, setActiveNiche] = useState('pricing-watch'); const [activeItem, setActiveItem] = useState(1); const [starred, setStarred] = useState(new Set([3])); const [skipped, setSkipped] = useState(new Set()); const [btwValue, setBtwValue] = useState(''); const [toast, setToast] = useState(null); const [recentBtw, setRecentBtw] = useState(null); const current = ITEMS.find(i => i.id === activeItem); function submitBtw() { if (!btwValue.trim()) return; const notes = parseReaderNotes(btwValue); setRecentBtw({ text: btwValue, notes }); setToast({ text: btwValue, notes, t: Date.now() }); setBtwValue(''); setTimeout(() => setToast(t => (t && Date.now() - t.t > 3000) ? null : t), 4500); } function parseReaderNotes(text) { const out = []; const t = text.toLowerCase(); if (t.includes('less')) out.push({ k: 'less of', v: text.match(/less ([\w ]+)/i)?.[1]?.split(' ').slice(0,2).join(' ') || 'this topic' }); if (t.includes('more')) out.push({ k: 'more of', v: text.match(/more ([\w ]+)/i)?.[1]?.split(' ').slice(0,2).join(' ') || 'this topic' }); if (out.length === 0) out.push({ k: 'reader note', v: 'logged for next refresh' }); return out; } function toggleStar(id) { setStarred(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); } function toggleSkip(id) { setSkipped(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); } return (
{b[1]}
; if (b[0] === 'h2') return{b[1]};
if (b[0] === 'quote') return {b[1]}; return null; })}