/* global React */
const { useState, useEffect, useMemo, useRef } = React;
const fmtDate = (s) => {
const d = new Date(s + 'T00:00:00');
return d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
};
const fmtDateLong = (s) => {
const d = new Date(s + 'T00:00:00');
return d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
};
const fmtN = (n) => n.toLocaleString('en-US');
/* ---------- Masthead / nav ---------- */
function Masthead({ route, nav }) {
const tabs = [
['home', 'Home'],
['mps', 'MPs'],
['topics', 'Topics'],
['sittings', 'Sittings'],
['heatmap', 'Pulse'],
['search', 'Search'],
];
return (
);
}
/* ---------- Footer ---------- */
function Colophon() {
return (
);
}
/* ---------- Topic tag ---------- */
function TopicTag({ topic, onClick }) {
return (
{ e.preventDefault(); onClick && onClick(topic); }}>
{topic.short || topic.name}
);
}
function PartyChip({ party }) {
if (!party) return null;
return (
{party.abbr}
);
}
/* ---------- Speech item ---------- */
function SpeechItem({ speech, showMp = true, showDate = false, nav }) {
return (
{speech.debate}
{speech.summary}
);
}
/* ---------- Sparkline ---------- */
function Sparkline({ values, width = 120, height = 28, color = 'var(--ink)' }) {
if (!values || !values.length) return null;
const max = Math.max(...values, 1);
const step = width / (values.length - 1 || 1);
const points = values.map((v, i) => `${(i * step).toFixed(1)},${(height - (v / max) * height).toFixed(1)}`).join(' ');
const bars = values.map((v, i) => {
const h = (v / max) * height;
return ;
});
return (
);
}
/* ---------- Horizontal bar (for topic breakdown) ---------- */
function HBar({ items, max, labelKey = 'label', valueKey = 'value', colorKey, onClick }) {
const m = max ?? Math.max(...items.map(i => i[valueKey]), 1);
return (
{items.map((it, i) => {
const pct = (it[valueKey] / m) * 100;
return (
onClick(it) : undefined} style={{ cursor: onClick ? 'pointer' : 'default' }}>
{it[labelKey]}
{it[valueKey]}
);
})}
);
}
/* ---------- Tweaks panel ---------- */
function TweaksPanel({ tweaks, setTweaks, active, onClose }) {
if (!active) return null;
const accents = [
{ id: 'civic-red', c: 'oklch(55% 0.15 25)' },
{ id: 'ink-blue', c: 'oklch(45% 0.12 250)' },
{ id: 'forest', c: 'oklch(45% 0.10 150)' },
{ id: 'ochre', c: 'oklch(60% 0.14 70)' },
{ id: 'plum', c: 'oklch(45% 0.12 350)' },
];
return (
Tweaks
Accent
{accents.map(a => (
Display
{['humanist','grotesque','classical'].map(f => (
))}
Density
Theme
);
}
Object.assign(window, {
fmtDate, fmtDateLong, fmtN,
Masthead, Colophon, TopicTag, PartyChip, SpeechItem,
Sparkline, HBar, TweaksPanel,
});