/* Delivery globe. One dot per delivered programme, not one per country — the dots are placed on a deterministic ring around each country's centroid so a dense country reads as dense. Real Natural Earth geometry via d3 + topojson; hover is desktop-only detail, and the chip row below carries the same numbers for touch and reduced-motion. */ const GD = window.SX_DATA; /* Self-hosted: the map must draw on a network that blocks third-party CDNs. */ const TOPO_URL = "assets/data/countries-110m.json"; const CENTROIDS = { Pakistan: [69.3, 30.4], "United States": [-98.5, 39.5], "United Kingdom": [-1.8, 53.2], Canada: [-106, 56], Japan: [138.2, 36.2], "United Arab Emirates": [54.3, 24.3], "Saudi Arabia": [45.1, 24.1], Ukraine: [31.2, 49.0], }; const TIER_R = { flagship: 5.5, deployment: 4, delivery: 3 }; /* One hue per weight tier: accent for flagship (the thing to look at), field green for a live deployment, amber for a delivered-and-handed-over build. */ const TIER_C = { flagship: "var(--sx-accent)", deployment: "var(--sx-field)", delivery: "var(--sx-amber)" }; /* Natural Earth 110m spells some names differently from the project record. */ const GEO_ALIAS = { "United States of America": "United States", "USA": "United States", "United States": "United States", "United Kingdom": "United Kingdom", "England": "United Kingdom" }; const MAX_DOTS = 4; /* Offsets are derived from the projection scale, never fixed px: at a 434px world a 22px ring equals 15° of longitude and would land a Lahore programme in Laos. */ function ringOffset(i, base) { if (i === 0) return [0, 0]; const a = ((i - 1) / (MAX_DOTS - 1)) * Math.PI * 1.4 - 0.5; return [Math.cos(a) * base, Math.sin(a) * base * 0.72]; } const FLAG_BY_COUNTRY = Object.fromEntries((GD.countries || []).map((c) => [c.name, c.flag])); const flagFor = (name) => FLAG_BY_COUNTRY[name] || ""; const SHORT_NAME = { "United Arab Emirates": "UAE", "United States": "United States" }; function DeliveryGlobe({ mobile, height }) { const wrapRef = React.useRef(null); const svgRef = React.useRef(null); const [geo, setGeo] = React.useState(null); const [width, setWidth] = React.useState(mobile ? 340 : 1000); const [hover, setHover] = React.useState(null); const [active, setActive] = React.useState(null); React.useEffect(() => { const el = wrapRef.current; if (!el) return; const ro = new ResizeObserver(() => setWidth(el.clientWidth || 1000)); ro.observe(el); setWidth(el.clientWidth || 1000); return () => ro.disconnect(); }, []); React.useEffect(() => { const d3 = window.d3, topojson = window.topojson; if (!d3 || !topojson) return; let dead = false; d3.json(TOPO_URL).then((topo) => { if (dead) return; setGeo(topojson.feature(topo, topo.objects.countries).features); }).catch(() => {}); return () => { dead = true; }; }, []); const located = GD.projects.filter((p) => p.country); /* Natural Earth is width-bound at this aspect, so the canvas height follows the measured width — a passed height only acts as a ceiling. */ const h = Math.round(Math.min(height || 560, Math.max(mobile ? 170 : 240, width / 2.05))); const byCountry = {}; located.forEach((p) => { (byCountry[p.country] = byCountry[p.country] || []).push(p); }); const d3 = window.d3; const projection = d3 && d3.geoNaturalEarth1().fitSize([width, h], { type: "Sphere" }); const path = d3 && d3.geoPath(projection); const delivered = new Set(Object.keys(byCountry)); const dots = []; const overflow = []; const dotBase = Math.max(4.5, width * 0.0085); if (projection) { Object.entries(byCountry).forEach(([country, list]) => { const c = CENTROIDS[country]; if (!c) return; const [cx, cy] = projection(c); const rank = { flagship: 0, deployment: 1, delivery: 2 }; const byRank = list.slice().sort((a, b) => rank[a.tier] - rank[b.tier]); /* Take the heaviest first, but keep one of each tier present so every legend entry resolves to a dot the reader can actually find. */ const order = []; ["flagship", "deployment", "delivery"].forEach((t) => { const f = byRank.find((p) => p.tier === t); if (f) order.push(f); }); byRank.forEach((p) => { if (!order.includes(p)) order.push(p); }); order.slice(0, MAX_DOTS).forEach((p, i) => { const [dx, dy] = ringOffset(i, dotBase); dots.push({ p, x: cx + dx, y: cy + dy, r: TIER_R[p.tier], c: TIER_C[p.tier] || "var(--sx-accent)" }); }); if (order.length > MAX_DOTS) overflow.push({ country, n: order.length - MAX_DOTS, x: cx + dotBase * 1.6, y: cy - dotBase * 1.4 }); }); } return (
setHover(null)}> {path && ( <> {(geo || []).map((f, i) => { const on = delivered.has(GEO_ALIAS[f.properties.name] || f.properties.name); return ; })} {d3.range(-60, 90, 30).map((lat) => ( )).slice(0, 1)} {dots.map((d, i) => { const dim = hover && hover.p.slug !== d.p.slug; return ( setHover(d)} onClick={() => setActive(d.p)} /> {hover && hover.p.slug === d.p.slug && ( )} ); })} {overflow.map((o) => ( 9 ? 26 : 20} height={15} rx={4} fill="var(--surface-overlay)" stroke="var(--sx-accent-line)" /> 9 ? 13 : 10)} y={o.y + 3} textAnchor="middle" fill="var(--text-accent)" style={{ font: "500 10px var(--font-mono)" }}>+{o.n} ))} )} {hover && !mobile && (

{flagFor(hover.p.country) && } {hover.p.tier} · {hover.p.country.toLowerCase()}

{hover.p.client}

{hover.p.title}

{hover.p.sector.toLowerCase()} · {hover.p.year || hover.p.era}

)}
{[["flagship", 5.5, TIER_C.flagship], ["deployment", 4, TIER_C.deployment], ["delivery", 3, TIER_C.delivery]].map(([label, r, col]) => ( {label} ))}
); } Object.assign(window, { DeliveryGlobe });