// H2Learn — Network Hero Screen // Six modes: Atlas / Orbital / Focus / Anti-dup / Need-Offer / Path // Filters · detail drawer · why-connected · graph stats · legend (function() { const D = window.H2Data; const L = window.GraphLayouts; const { useState, useMemo } = React; const MODES = [ { key:'atlas', label:'Atlas', icon:'network', desc:'Force-directed clusters' }, { key:'orbital', label:'Orbital', icon:'target', desc:'Ecosystem rings' }, { key:'focus', label:'Focus', icon:'compass', desc:'My local network' }, { key:'antidup', label:'Anti-duplication', icon:'alert', desc:'Overlap detection' }, { key:'needoffer', label:'Need ↔ Offer', icon:'git', desc:'Sankey-style flow' }, { key:'path', label:'Path', icon:'arrowRight', desc:'Connection chain' }, ]; const ENTITY_FILTERS = [ { key:'person', label:'People', color:'var(--c-person)' }, { key:'project',label:'Projects', color:'var(--c-project)' }, { key:'org', label:'Orgs', color:'var(--c-org)' }, { key:'domain', label:'Domains', color:'var(--c-domain)' }, { key:'method', label:'Methods', color:'var(--c-method)' }, { key:'challenge',label:'Challenges',color:'var(--c-challenge)' }, ]; const REL_FILTERS = [ 'Similarity','Complementarity','Anti-duplication','Bridge', 'Method transfer','Shared uncertainty','Need-offer','Project compatibility', ]; const NetworkScreen = () => { const [mode, setMode] = useState('atlas'); const [selectedNode, setSelectedNode] = useState('p_sara'); const [hoveredNode, setHoveredNode] = useState(null); const [selectedEdge, setSelectedEdge] = useState(null); const [drawerView, setDrawerView] = useState('node'); // node | edge | why const [strengthMin, setStrengthMin] = useState(0.3); const [enabledTypes, setEnabledTypes] = useState(new Set(['person','project','org','domain','method'])); const [enabledRels, setEnabledRels] = useState(new Set(REL_FILTERS)); const [dimNonAdjacent, setDimNonAdjacent] = useState(false); // Compose graph data per mode const graph = useMemo(() => { if (mode === 'atlas') return { nodes: L.ATLAS.nodes, edges: L.ATLAS.edges, clusters: L.ATLAS.clusters }; if (mode === 'orbital') { const placed = L.placeOrbital(L.ORBITAL); return { nodes: Object.values(placed), edges: L.ORBITAL.edges, rings: L.ORBITAL.rings, ringCenter: { cx: L.ORBITAL.cx, cy: L.ORBITAL.cy } }; } if (mode === 'focus') { const f = L.buildFocus(selectedNode || 'p_sara'); return { nodes: f.nodes, edges: f.edges }; } if (mode === 'antidup') return { nodes: L.ANTIDUP.nodes, edges: L.ANTIDUP.edges, clusters: L.ANTIDUP.clusters }; if (mode === 'path') { const f = L.buildPath([ { id:'p_sara', kind:'person', initials:'SA', label:'You · Sara', edgeReason:'Has expertise in', relationTo:'Similarity' }, { id:'d_pem', kind:'domain', initials:'P', label:'PEM', edgeReason:'Worked on by', relationTo:'Similarity' }, { id:'p_liesbeth', kind:'person', initials:'LK', label:'Liesbeth', edgeReason:'Member of', relationTo:'Similarity' }, { id:'o_tno', kind:'org', initials:'TN', label:'TNO', edgeReason:'Partner in', relationTo:'Similarity' }, { id:'pj_hydronl', kind:'project', initials:'HN', label:'HYDRO-NL', edgeReason:'Led by', relationTo:'Similarity' }, ]); return { nodes: f.nodes, edges: f.edges, viewBox: '0 0 1100 720' }; } return { nodes: [], edges: [] }; }, [mode, selectedNode]); // Filter const filtered = useMemo(() => { if (mode === 'needoffer') return graph; const ns = graph.nodes.filter(n => !enabledTypes.size || enabledTypes.has(n.kind)); const ids = new Set(ns.map(n => n.id)); const es = graph.edges.filter(e => ids.has(e.s) && ids.has(e.t) && enabledRels.has(e.type) && (e.strength || 0) >= strengthMin ); return { ...graph, nodes: ns, edges: es }; }, [graph, enabledTypes, enabledRels, strengthMin, mode]); const onNodeClick = (n) => { setSelectedNode(n.id); setSelectedEdge(null); setDrawerView('node'); if (mode === 'focus') {/* keep focus */} }; const onEdgeClick = (e) => { setSelectedEdge(e); setDrawerView('edge'); }; const stats = useMemo(() => ({ nodes: filtered.nodes?.length || 0, edges: filtered.edges?.length || 0, clusters: filtered.clusters?.length || 4, recommended: (filtered.edges || []).filter(e => e.recommended).length, antidup: (filtered.edges || []).filter(e => e.alert).length, }), [filtered]); return (
{/* ============ FILTER PANEL (left) ============ */} {/* ============ CANVAS (center) ============ */}
{/* mode bar */}
{MODES.map(m => ( ))}
⌘K
Save view Export
{/* canvas + overlays */}
{mode === 'needoffer' ? ( { setSelectedEdge({matchId:'m_01', type:'Complementarity', s:'p_sara', t:'pj_hydronl', strength: 0.88}); setDrawerView('edge'); }}/> ) : ( {mode === 'antidup' && } } /> )}
{/* ============ DETAIL DRAWER (right) ============ */}
); }; // ============ Filter panel ============ const FilterPanel = ({mode, enabledTypes, setEnabledTypes, enabledRels, setEnabledRels, strengthMin, setStrengthMin, dimNonAdjacent, setDimNonAdjacent}) => { const toggleSet = (set, setter, key) => { const next = new Set(set); if (next.has(key)) next.delete(key); else next.add(key); setter(next); }; return (
Network

Ecosystem map

248 nodes · 134 edges across 4 clusters
{ENTITY_FILTERS.map(t => ( toggleSet(enabledTypes, setEnabledTypes, t.key)} swatch={} label={t.label} /> ))} {REL_FILTERS.map(r => { const meta = window.RelMeta[r] || window.RelMeta.Similarity; return ( toggleSet(enabledRels, setEnabledRels, r)} swatch={} label={r} /> ); })}
min{strengthMin.toFixed(2)}
setStrengthMin(+e.target.value)} style={{width:'100%', accentColor:'var(--accent)'}}/>
setDimNonAdjacent(!dimNonAdjacent)} label="Dim non-adjacent"/>
View presets
My ecosystem Anti-dup risks Bridge actors
); }; const FilterGroup = ({title, children}) => (
{title}
{children}
); const FilterRow = ({on=true, onClick, swatch, label}) => ( ); const Swatch = ({type, color}) => { if (type === 'project') return ; if (type === 'org') return ; return ; }; const EdgeSwatch = ({type, color}) => { const dash = type === 'Anti-duplication' ? '4 3' : type === 'Shared uncertainty' ? '2 4' : type === 'Method transfer' ? '3 2' : null; return ( ); }; const ContextFilter = ({label, options}) => (
{label}
); // ============ Mode badge ============ const ModeBadge = ({mode}) => { const m = MODES.find(x => x.key === mode); return (
Mode {m.label}
); }; // ============ Legend ============ const Legend = () => { const items = [ { kind:'Person', color:'var(--c-person)', shape:'circle'}, { kind:'Project', color:'var(--c-project)', shape:'hex'}, { kind:'Org', color:'var(--c-org)', shape:'square'}, { kind:'Domain', color:'var(--c-domain)', shape:'circle'}, { kind:'Method', color:'var(--c-method)', shape:'circle'}, ]; const edges = [ { type:'Similarity', color:'var(--e-similar)' }, { type:'Complementarity',color:'var(--e-comp)' }, { type:'Anti-dup', color:'var(--e-antidup)', dash:'4 3' }, { type:'Bridge', color:'var(--e-bridge)' }, { type:'Method', color:'var(--e-method)', dash:'3 2' }, ]; return (
Legend
{items.map(i => (
{i.kind}
))}
{edges.map(e => (
{e.type}
))}
); }; const GraphStats = ({stats}) => (
Graph stats
); const Stat = ({v, l, highlight}) => (
{v}
{l}
); const ZoomControls = () => (
); const AntiDupBanner = () => (
3 anti-duplication clusters detected · membrane protocols ×3 · pilot data ×2 · safety curriculum ×2 Review
); // ============ Need-Offer view ============ const NeedOfferView = ({onMatch}) => { const { needs, offers, flows } = L.NEED_OFFER; const W = 1200, H = 620; const colW = 280; const lx = 60, rx = W - colW - 60; const yStep = 70, yTop = 100; const needPos = needs.reduce((m, n, i) => { m[n.id] = { x: lx + colW, y: yTop + i*yStep }; return m; }, {}); const offerPos = offers.reduce((m, o, i) => { m[o.id] = { x: rx, y: yTop + i*yStep - 30 }; return m; }, {}); return (
{/* column headers */} Needs Offers WHO IS LOOKING WHO CAN PROVIDE {/* flow ribbons */} {flows.map((f, i) => { const a = needPos[f.from], b = offerPos[f.to]; if (!a || !b) return null; const w = 6 + f.strength * 18; const c1x = a.x + 200, c1y = a.y; const c2x = b.x - 200, c2y = b.y; const isMatch = f.matchId === 'm_01'; return ( isMatch && onMatch && onMatch()} style={{cursor: isMatch?'pointer':'default'}}> {isMatch && ( {f.label} · {Math.round(f.strength*100)}% )} ); })} {/* needs (left column) */} {needs.map((n, i) => { const y = yTop + i * yStep; return ( {n.label} {n.who} ); })} {/* offers (right column) */} {offers.map((o, i) => { const y = yTop + i * yStep - 30; return ( {o.label} {o.who} ); })}
); }; // ============ Detail drawer (right) ============ const DetailDrawer = ({view, setView, selectedNode, setSelectedNode, selectedEdge, mode}) => { const node = D.byId(selectedNode); const kind = D.kindOf(selectedNode || 'p_sara'); const match = selectedEdge && selectedEdge.matchId ? D.MATCHES.find(m => m.id === selectedEdge.matchId) : null; return (
{view === 'node' && node && } {view === 'edge' && selectedEdge && } {view === 'why' && (match || selectedEdge) && } {view === 'why' && !selectedEdge && !match && ( )}
Send signal location.hash = 'match'}/>
); }; const NodeDetail = ({node, kind, setView}) => (
{kind === 'person' ? 'Person' : kind === 'project' ? 'Project' : 'Organization'}
{node.name}
{node.role || node.type} · {node.org || node.region}
{node.bio &&

{node.bio}

} {/* fingerprint */} {node.fingerprint && (
Expertise fingerprint
)} {/* project context */} {node.trl != null && (
Project context
Stage: {node.stage} TRL {node.trl} {node.scale} {node.region}
)} {/* needs/offers */} {(node.needs || node.offers) && (
{node.needs && (
Needs
{node.needs.map((n,i) => {n})}
)} {node.offers && (
Offers
{node.offers.map((o,i) => {o})}
)}
)} {/* bottlenecks / uncertainties */} {(node.bottlenecks || node.uncertainties) && (
Blockers & unknowns
{node.bottlenecks && node.bottlenecks.map((b,i) => (
{b.label}
severity {b.severity}/5
))} {node.uncertainties && node.uncertainties.map((u,i) => (
{u.label}
{u.horizon}
))}
)} {/* top connections */}
Top connections
{[ {n: D.PEOPLE[1], why:'Similarity · 92%', rel:'Similarity'}, {n: D.PROJECTS[0], why:'Complementarity · 88%', rel:'Complementarity'}, {n: D.PEOPLE[3], why:'Shared uncertainty · 71%', rel:'Shared uncertainty'}, ].map((c,i) => (
{c.n.name}
))}
{/* actions */}
Open full profile
Compare Focus here
); const EdgeDetail = ({edge, match, setView}) => { if (!edge) return
No edge selected
; const a = D.byId(edge.s), b = D.byId(edge.t); return (
{a?.name} × {b?.name}
Strength
{Math.round((edge.strength || 0)*100)}%
Confidence
{Math.round((match?.confidence || 0.85)*100)}%
Source
{match?.curated ? 'Curated' : 'System'}
{match && (
Why connected — short
{match.summary}
setView('why')}>See full explanation
)}
location.hash = 'match'} style={{justifyContent:'center'}}>Open match detail
); }; const WhyConnected = ({edge, match}) => { if (!match) return
Select an edge to see why it exists
; return (
Why connected?
{match.summary}
Signals · contribution to score
{match.signals.map((s, i) => (
{s.name} {s.score}%
{s.expl}
source · {s.source}
))}
location.hash = 'match'} style={{justifyContent:'center'}}>Open full match detail
); }; Object.assign(window, { NetworkScreen }); })();