/* global React */ const { useState: useS, useEffect: useE, useMemo: useM } = React; /* ============================= HOME ============================= */ function HomePage({ nav }) { const D = window.PT_DATA; const latestSitting = D.SITTINGS[D.SITTINGS.length - 1]; const featured = D.SPEECHES.find(s => s.sittingId === latestSitting.id && s.topics.some(t => t.slug === 'corruption')) || D.SPEECHES.find(s => s.sittingId === latestSitting.id); const todaySpeeches = D.SPEECHES.filter(s => s.sittingId === latestSitting.id).slice(0, 6); const mostActive = D.TOP_MPS.slice(0, 5); const topTopics = [...D.TOPICS].sort((a, b) => b.count - a.count); return (
{/* Edition strip */}
{fmtDateLong(latestSitting.date).toUpperCase()} · EDITION No. 47 10TH PARLIAMENT · SESSION 2 · SITTING {latestSitting.id} {D.STATS.totalSpeeches.toLocaleString()} SPEECHES INDEXED
{/* Hero */}
Latest from the chamber

What your Parliament said this week — on the record, in plain English.

Every speech from every MP in every sitting, summarised, tagged, and searchable. Built on the official Hansard. Find your MP, follow the issues you care about, and read what was actually said.

or press /
{/* Featured speech */}
Featured · {fmtDate(featured.sittingDate)}

“{featured.summary}”

{featured.mp && ( <> { e.preventDefault(); nav('mp', { id: featured.mp.id }); }} className="serif" style={{ fontWeight: 600 }}> {featured.mp.name} · {featured.mp.district} )}
{featured.topics.map(t => nav('topic', { slug: tt.slug })} />)}
Also on {fmtDate(latestSitting.date)}
{todaySpeeches.slice(1, 5).map(s => ( { e.preventDefault(); nav('speech', { id: s.id }); }} style={{ display: 'grid', gridTemplateColumns: '120px 1fr auto', gap: 12, padding: '12px 0', borderTop: '1px solid var(--rule)', alignItems: 'start' }}> {s.mp?.name.split(' ').slice(-1)[0]} {s.summary.slice(0, 120)}{s.summary.length > 120 ? '…' : ''} #{s.speechOrder} ))}
{ e.preventDefault(); nav('sitting', { date: latestSitting.date }); }} className="mono" style={{ fontSize: 11, marginTop: 12, display: 'inline-block', color: 'var(--ink-3)' }}> full sitting record →
{/* Two-column: topics + most active MPs */}

Policy areas

18 topics · {fmtN(D.STATS.totalSpeeches)} tagged
{topTopics.map(t => ( { e.preventDefault(); nav('topic', { slug: t.slug }); }} style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, padding: '10px 0', borderTop: '1px solid var(--rule)', alignItems: 'baseline' }}> {t.name} {t.count} ))}

Most active members

by speech count · this session
{mostActive.map((mp, i) => ( { e.preventDefault(); nav('mp', { id: mp.id }); }} style={{ display: 'grid', gridTemplateColumns: '28px 1fr 130px 60px', gap: 12, padding: '12px 0', borderTop: '1px solid var(--rule)', alignItems: 'center' }}> {String(i + 1).padStart(2, '0')}
{mp.name}
· {mp.district}
{mp.speechCount}
))}
{/* Recent sittings */}

Recent sittings

{ e.preventDefault(); nav('sittings'); }} className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>all sittings →
{[...D.SITTINGS].reverse().map(s => { const sitSpeeches = D.SPEECHES.filter(sp => sp.sittingId === s.id); const topicCounts = {}; sitSpeeches.forEach(sp => sp.topics.forEach(t => topicCounts[t.slug] = (topicCounts[t.slug] || 0) + 1)); const top = Object.entries(topicCounts).sort((a,b) => b[1]-a[1]).slice(0, 3).map(([slug]) => D.TOPICS.find(t => t.slug === slug)); return ( nav('sitting', { date: s.date })} style={{ cursor: 'pointer' }}> ); })}
Date Session Speeches Top topics
{fmtDate(s.date)} {s.session} {s.speechCount}
{top.map(t => t && {t.short})}
); } /* Pulse ribbon — novel view */ function PulseRibbon({ sittings, nav }) { const [hover, setHover] = useS(null); const max = Math.max(...sittings.map(s => s.speechCount)); return (
{sittings.map(s => { const h = (s.speechCount / max) * 56; return (
setHover(s)} onMouseLeave={() => setHover(null)} onClick={() => nav('sitting', { date: s.date })} title={`${s.date} · ${s.speechCount} speeches`} /> ); })}
{fmtDate(sittings[0].date).split(' ').slice(0, 2).join(' ')} {hover ? `${fmtDate(hover.date)} · ${hover.speechCount} speeches` : 'hover to inspect'} {fmtDate(sittings[sittings.length - 1].date).split(' ').slice(0, 2).join(' ')}
); } Object.assign(window, { HomePage, PulseRibbon });