/* Design 7 — "Pulse" Map as full-screen background; a single floating panel on the left with three stacked sections: Spending by country, Spending by sector, Recent deals. Click a country to drill into its sectors and deals. Light SaaS palette — navy + cyan accent on slate. */ const psFmt = (v, d = 1) => v.toLocaleString('en-GB', { minimumFractionDigits: d, maximumFractionDigits: d }); const psInt = (v) => v.toLocaleString('en-GB'); function PSPanelBack({ onBack, label }) { return ( ); } // CPV → human-readable label, sourced from the 45-division palette. const cpvName = (cpv) => { const code = String(cpv).slice(0, 2); const s = (window.EU_SECTOR_PALETTE || []).find((x) => x.code === code); return s ? s.officialName : 'Other'; }; // ISO-3 alpha → ISO-2 alpha lookup (EU-27 only). const iso2FromIso3 = (iso3) => { if (!iso3) return null; const c = window.EU_COUNTRIES.find((x) => x.iso === iso3); return c ? c.iso2 : null; }; // Render the buyer flag, with a secondary winner flag offset behind when the // winner country differs from the buyer country. function DealFlags({ buyerIso2, winnerIso2 }) { const flag = (iso2) => `https://flagcdn.com/${iso2.toLowerCase()}.svg`; const showSecondary = winnerIso2 && buyerIso2 && winnerIso2 !== buyerIso2; return (
{showSecondary && ( {winnerIso2} )} {buyerIso2}
); } function DealDetailModal({ deal, onClose }) { if (!deal) return null; const isForeignSupplier = deal.winnerCountry && deal.winnerCountry !== deal.country; const isDirect = deal.procedure === 'direct'; const isSingleBidder = deal.bidsReceived === 1 && !isDirect; // Estimated vs actual delta let estDelta = null; if (deal.estimatedValue) { const pct = ((deal.value - deal.estimatedValue) / deal.estimatedValue) * 100; estDelta = { pct: Math.abs(pct).toFixed(1), under: pct < 0 }; } return (
e.stopPropagation()}>
{deal.id} {deal.pubDate && ·} {deal.pubDate && Published {deal.pubDate}}
{deal.title}
€{deal.value.toLocaleString('en-GB', { minimumFractionDigits: 1, maximumFractionDigits: 1 })}M
{estDelta && (
vs est. €{deal.estimatedValue.toFixed(1)}M {estDelta.under ? '↓' : '↑'} {estDelta.pct}%
)}
{deal.sectorName} {isSingleBidder && ● 1 bidder} {isDirect && ⚡ Direct award} {isForeignSupplier && Cross-border} {deal.winnerSme && SME winner} {deal.euFundsName && EU-funded}
{/* Direct-award justification */} {deal.directAwardJustification && (
Why direct award
{deal.directAwardReason}
{deal.directAwardJustification}
)} {/* Buyer */}
Who bought it
Buyer
{deal.buyer}
{deal.buyerCity && <>
City
{deal.buyerCity}, {deal.country}
} {deal.buyerLegalType && <>
Legal type
{deal.buyerLegalType}
} {deal.authorityActivity && <>
Main activity
{deal.authorityActivity}
}
{/* Winner */}
Who got the money
Winner
{deal.winner}{deal.winnerSme ? ' (SME)' : ''}
{deal.winnerCountry && <>
Country
{deal.winnerCountry}{isForeignSupplier ? ' · foreign' : ''}
} {deal.winnerCity && <>
City
{deal.winnerCity}
} {deal.uboName && <>
Beneficial owner
{deal.uboName}{deal.uboCountry ? ` (${deal.uboCountry})` : ''}
}
{/* Procurement */}
How it was procured
Procedure
{deal.procedure}
{!isDirect && <>
Bids received
{deal.bidsReceived}{isSingleBidder ? ' · single bidder' : ''}
} {!isDirect && deal.bidsSme > 0 && <>
SME bids
{deal.bidsSme} of {deal.bidsReceived}
} {deal.bidLow && deal.bidHigh && ( <>
Bid range
€{deal.bidLow.toFixed(1)}M — €{deal.bidHigh.toFixed(1)}M
)}
{/* What */}
What was bought
CPV code
{deal.cpv} · {cpvName(deal.cpv)}
{deal.placeOfPerformance && <>
Place
{deal.placeOfPerformance}
} {deal.nutsRegion && <>
NUTS region
{deal.nutsRegion}
}
{/* EU funds */} {deal.euFundsName && (
Funding
EU programme
{deal.euFundsName}
)} {/* Timeline */}
Timeline
{deal.contractDate && <>
Contract signed
{deal.contractDate} 2026
}
); } function useViewport() { const [vp, setVp] = React.useState({ w: window.innerWidth, h: window.innerHeight }); React.useEffect(() => { const onResize = () => setVp({ w: window.innerWidth, h: window.innerHeight }); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []); return vp; } function PulseDashboard() { const { w: vw, h: vh } = useViewport(); const [selected, setSelected] = React.useState(null); // ISO3 or null for overview const [hovered, setHovered] = React.useState(null); const [expandedCountries, setExpandedCountries] = React.useState(false); const [expandedTenders, setExpandedTenders] = React.useState(false); const [expandedSuppliers, setExpandedSuppliers] = React.useState(false); const [expandedDeals, setExpandedDeals] = React.useState(false); const [activeDeal, setActiveDeal] = React.useState(null); const [period, setPeriod] = React.useState('ttm'); // 'ttm' | '2025' | '2026' const [view, setView] = React.useState('overview'); // 'overview' | 'tenders' | 'suppliers' // dealsScreen takes over the panel when set. // { scope: 'eu' | 'country', sectorCode?: string, sectorName?: string } const [dealsScreen, setDealsScreen] = React.useState(null); const [dealsSearch, setDealsSearch] = React.useState(''); const [dealsLimit, setDealsLimit] = React.useState(50); // Reset paging + search whenever we enter a new dealsScreen context. React.useEffect(() => { setDealsSearch(''); setDealsLimit(50); }, [dealsScreen && dealsScreen.scope, dealsScreen && dealsScreen.sectorCode]); // Switching view also clears any selected country + deals screen const goToView = (next) => { setSelected(null); setDealsScreen(null); setView(next); }; const sorted = React.useMemo(() => [...window.EU_COUNTRIES].sort((a, b) => b.spend - a.spend), []); const max = sorted[0].spend; const total = window.EU_TOTAL_SPEND; const country = selected ? window.EU_COUNTRIES.find((c) => c.iso === selected) : null; const countrySectors = country ? window.getCountrySectors(country) : null; const countryDeals = country ? window.getCountryDeals(country) : null; const recentDeals = React.useMemo(() => window.getEuRecentDeals(200), []); const sectorTotals = React.useMemo(() => { return window.EU_SECTOR_PALETTE .map((s) => ({ ...s, value: window.EU_TOTAL_OF_SECTOR(s.key) })) .sort((a, b) => b.value - a.value) .map((s, _, arr) => { const sum = arr.reduce((t, x) => t + x.value, 0); return { ...s, share: (s.value / sum) * 100 }; }); }, []); const sectorMax = sectorTotals[0].value; const fillFor = (c) => { if (!c) return '#cbd5e1'; const t = 0.28 + 0.72 * Math.min(1, Math.pow(c.spend / max, 0.45)); const lerp = (a, b) => Math.round(a + (b - a) * t); // light sky -> deep navy return `rgb(${lerp(186, 23)}, ${lerp(220, 45)}, ${lerp(245, 110)})`; }; // Sector colour by CPV group; lightness modulated by share within group. // Avoids a 45-hue rainbow (per spec). Three base hues per: supplies/works/services. const GROUP_BASE = { supplies: [14, 165, 233], // sky 500 works: [30, 58, 138], // navy 900 services: [6, 182, 212], // cyan 500 other: [148, 163, 184], // slate 400 }; const sectorColor = (s, refMax) => { const base = GROUP_BASE[s.group] || GROUP_BASE.other; const t = 0.45 + 0.55 * Math.min(1, Math.pow((s.value || 0) / refMax, 0.5)); // mix toward white at low t, toward base at high t const mix = (a, b) => Math.round(a + (b - a) * t); return `rgb(${mix(230, base[0])}, ${mix(238, base[1])}, ${mix(246, base[2])})`; }; return (
{/* Map full-bg */}
setSelected(iso)} onHover={setHovered} hoveredIso={hovered} interactive> {(({ proj, zoom: z }) => { if (!proj) return null; const renderPill = (iso, fill) => { const cap = EU_CAPITALS[iso]; if (!cap) return null; const [cx, cy] = proj([cap[1], cap[0]]); const c = window.EU_COUNTRIES.find((x) => x.iso === iso); const name = c ? c.name : iso; const w = name.length * 7.4 + 28; const inv = 1 / z; return ( {name} ); }; return ( <> {hovered && hovered !== selected && renderPill(hovered, 'rgba(15, 23, 42, 0.92)')} {selected && renderPill(selected, '#0f172a')} ); })}
{/* Floating brand top-right */}
Pulse
{/* Stats widget — top-right under the brand */}
27
countries
142K
contracts
€812B
tracked spend
{/* Left panel */} {/* Map legend bottom right */}
€ awarded · trailing 12mo
{[0.05, 0.2, 0.4, 0.7, 1].map((t, i) => )}
040B90B180B+
{/* Deal detail modal — bottom right */} {activeDeal && setActiveDeal(null)} />}
); } // Capital cities — used to anchor the selected-country label on the map. const EU_CAPITALS = { DEU: [52.520, 13.405], FRA: [48.857, 2.352], ITA: [41.903, 12.496], ESP: [40.417, -3.704], POL: [52.230, 21.012], NLD: [52.370, 4.895], SWE: [59.329, 18.069], BEL: [50.850, 4.351], DNK: [55.676, 12.568], AUT: [48.208, 16.373], FIN: [60.170, 24.937], ROU: [44.439, 26.097], CZE: [50.075, 14.437], PRT: [38.722, -9.139], GRC: [37.984, 23.728], IRL: [53.350, -6.260], HUN: [47.498, 19.040], SVK: [48.149, 17.111], BGR: [42.698, 23.319], HRV: [45.815, 15.982], SVN: [46.056, 14.508], LTU: [54.687, 25.280], LVA: [56.946, 24.106], EST: [59.437, 24.754], LUX: [49.611, 6.130], CYP: [35.185, 33.382], MLT: [35.899, 14.514], }; window.PulseDashboard = PulseDashboard;