{"id":422,"date":"2026-04-14T20:07:54","date_gmt":"2026-04-14T20:07:54","guid":{"rendered":"https:\/\/www.ecrn-owc.eu\/?page_id=422"},"modified":"2026-04-16T16:17:46","modified_gmt":"2026-04-16T16:17:46","slug":"publications","status":"publish","type":"page","link":"https:\/\/v1.ecrn-owc.eu\/index.php\/publications\/","title":{"rendered":"Publications"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div class=\"wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<!-- ECRN Network Graph \u2014 paste into WordPress via Custom HTML block -->\n<style>\n#ecrn-graph-wrap {\n  width: calc(100vw - 0px);\n  max-width: none !important;\n  margin-left: calc(-50vw + 50%) !important;\n  margin-right: calc(-50vw + 50%) !important;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  box-sizing: border-box;\n  overflow-x: hidden;\n}\n#ecrn-graph-container {\n  width: 100%;\n  height: 600px;\n  min-height: 400px;\n  background: #f8fafc;\n  border: 1px solid #e2e8f0;\n  border-radius: 12px;\n  position: relative;\n  overflow: hidden;\n}\n#ecrn-graph-container svg {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n#ecrn-legend {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px 20px;\n  padding: 10px 16px;\n  background: #fff;\n  border: 1px solid #e2e8f0;\n  border-radius: 10px;\n  margin-top: 8px;\n  align-items: center;\n}\n.ecrn-leg-item {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  font-size: 12px;\n  color: #4a5568;\n}\n.ecrn-leg-dot {\n  border-radius: 50%;\n  flex-shrink: 0;\n}\n.ecrn-leg-line {\n  width: 24px;\n  height: 2px;\n  flex-shrink: 0;\n  border-radius: 2px;\n}\n.ecrn-leg-sep { width: 1px; height: 18px; background: #e2e8f0; }\n#ecrn-tooltip {\n  position: absolute;\n  pointer-events: none;\n  background: #fff;\n  border: 1px solid #e2e8f0;\n  border-radius: 10px;\n  padding: 10px 14px;\n  font-size: 13px;\n  line-height: 1.5;\n  color: #2d3748;\n  box-shadow: 0 8px 24px rgba(0,0,0,0.10);\n  opacity: 0;\n  transition: opacity 0.12s;\n  max-width: 230px;\n  z-index: 20;\n}\n.tt-name { font-weight: 700; font-size: 13px; margin-bottom: 2px; }\n.tt-inst { color: #718096; font-size: 11px; margin-bottom: 4px; }\n.tt-badge {\n  display: inline-block;\n  font-size: 11px;\n  font-weight: 600;\n  padding: 2px 8px;\n  border-radius: 20px;\n}\n#ecrn-controls {\n  position: absolute;\n  top: 10px;\n  right: 10px;\n  display: flex;\n  flex-direction: column;\n  gap: 5px;\n  z-index: 10;\n}\n.ecrn-btn {\n  width: 28px;\n  height: 28px;\n  background: #fff;\n  border: 1px solid #e2e8f0;\n  border-radius: 7px;\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 15px;\n  color: #4a5568;\n  user-select: none;\n  box-shadow: 0 1px 4px rgba(0,0,0,0.07);\n}\n.ecrn-btn:hover { background: #edf2f7; }\n#ecrn-hint {\n  position: absolute;\n  bottom: 8px;\n  left: 12px;\n  font-size: 11px;\n  color: #cbd5e0;\n  pointer-events: none;\n}\n<\/style>\n\n<div id=\"ecrn-graph-wrap\">\n  <div id=\"ecrn-graph-container\">\n    <div id=\"ecrn-tooltip\"><\/div>\n    <div id=\"ecrn-controls\">\n      <div class=\"ecrn-btn\" id=\"ecrnZoomIn\">+<\/div>\n      <div class=\"ecrn-btn\" id=\"ecrnZoomOut\">\u2212<\/div>\n      <div class=\"ecrn-btn\" id=\"ecrnReset\">\u2299<\/div>\n    <\/div>\n    <div id=\"ecrn-hint\">Drag \u00b7 Use +\/\u2212 to zoom \u00b7 Click to highlight<\/div>\n  <\/div>\n  <div id=\"ecrn-legend\">\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-dot\" style=\"width:16px;height:16px;background:#4f6ef7;\"><\/div>Professor \/ Senior Researcher<\/div>\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-dot\" style=\"width:13px;height:13px;background:#38b2ac;\"><\/div>Postdoctoral Researcher<\/div>\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-dot\" style=\"width:11px;height:11px;background:#ed8936;\"><\/div>PhD Student<\/div>\n    <div class=\"ecrn-leg-sep\"><\/div>\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-line\" style=\"background:#4f6ef7;\"><\/div>ECRN collaboration<\/div>\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-line\" style=\"background:#ed8936;background:repeating-linear-gradient(90deg,#ed8936 0,#ed8936 4px,transparent 4px,transparent 8px);\"><\/div>Supervision<\/div>\n    <div class=\"ecrn-leg-item\"><div class=\"ecrn-leg-line\" style=\"background:#38b2ac;\"><\/div>Same institution<\/div>\n  <\/div>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/d3@7\/dist\/d3.min.js\"><\/script>\n<script>\n(function waitForD3() {\n  if (typeof d3 === \"undefined\") { setTimeout(waitForD3, 50); return; }\n\n  const COLORS = {\n    professor: { fill: \"#4f6ef7\", stroke: \"#3451c7\" },\n    postdoc:   { fill: \"#38b2ac\", stroke: \"#2c8a85\" },\n    student:   { fill: \"#ed8936\", stroke: \"#c4692a\" },\n  };\n  const RADII = { professor: 24, postdoc: 19, student: 15 };\n  const LINK_COLORS = { ECRN: \"#4f6ef7\", supervision: \"#ed8936\", institution: \"#38b2ac\" };\n  const BADGE_CSS = {\n    professor: \"background:#ebf4ff;color:#2b6cb0\",\n    postdoc:   \"background:#e6fffa;color:#2c7a7b\",\n    student:   \"background:#fffbeb;color:#b45309\",\n  };\n  const ROLE_LABEL = { professor: \"Senior Researcher\", postdoc: \"Postdoctoral Researcher\", student: \"PhD Student\" };\n\n  \/\/ initials: first letter of first name + first letter of last name\n  function initials(name) {\n    \/\/ Strip titles\n    const clean = name.replace(\/^(Prof\\.|Dr\\.|Dra\\.) \/, \"\").trim();\n    const parts = clean.split(\" \").filter(w => w.length > 0);\n    if (parts.length === 1) return parts[0][0].toUpperCase();\n    return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n  }\n\n  const nodes = [\n    \/\/ \u2500\u2500 Academics & Researchers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    { id:\"zabih\",      name:\"Prof. Zabih Ghassemlooy\",    inst:\"Northumbria University, UK\",                          role:\"professor\" },\n    { id:\"stanislav\",  name:\"Prof. Stanislav Zv\u00e1novec\",   inst:\"Czech Technical University in Prague\",                role:\"professor\" },\n    { id:\"luis\",       name:\"Dr. Luis Nero Alves\",        inst:\"University of Aveiro, Portugal\",                      role:\"professor\" },\n    { id:\"joaquin\",    name:\"Dr. Joaquin Perez Soler\",    inst:\"Universitat de Val\u00e8ncia\",                             role:\"professor\" },\n    { id:\"anna\",       name:\"Dra. Anna Maria Vegni\",      inst:\"Roma Tre University, Italy\",                          role:\"professor\" },\n    { id:\"hoa\",        name:\"Prof. Hoa Le Minh\",          inst:\"Northumbria University, UK\",                          role:\"professor\" },\n    { id:\"rafael\",     name:\"Prof. Rafael P\u00e9rez-Jim\u00e9nez\", inst:\"Univ. Las Palmas de Gran Canaria, Spain\",             role:\"professor\" },\n    { id:\"qiang\",      name:\"Dr. Qiang Wu\",               inst:\"Northumbria University, UK\",                          role:\"professor\" },\n    { id:\"yongtao\",    name:\"Dr. Yongtao Qu\",             inst:\"Northumbria University, UK\",                          role:\"professor\" },\n    { id:\"pedro\",      name:\"Dr. Pedro Fonseca\",          inst:\"Universidade de Aveiro, Portugal\",                    role:\"professor\" },\n    { id:\"mohammad\",   name:\"Dr. Mohammad-Ali Khalighi\",  inst:\"Institut Fresnel, France\",                            role:\"professor\" },\n    { id:\"monica\",     name:\"Dr. M\u00f3nica Figueiredo\",      inst:\"Instituto de Telecomunica\u00e7\u00f5es, Portugal\",             role:\"professor\" },\n    { id:\"matej\",      name:\"Dr. Mat\u011bj Komanec\",          inst:\"Czech Technical University in Prague\",                role:\"professor\" },\n    \/\/ \u2500\u2500 Postdoctoral Researchers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    { id:\"carlos\",     name:\"Carlos Guerra\",              inst:\"Czech Technical University in Prague\",                role:\"postdoc\"   },\n    { id:\"vicente\",    name:\"Vicente Matus\",              inst:\"Univ. Las Palmas de Gran Canaria\",                    role:\"postdoc\"   },\n    \/\/ \u2500\u2500 Students Network \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    { id:\"satish\",     name:\"Satish Kumar\",               inst:\"Wireless & Fiber Optics \u00b7 Czech TU Prague\",           role:\"student\"   },\n    { id:\"claudia\",    name:\"Claudia Leoni\",              inst:\"Roma Tre University\",                                 role:\"student\"   },\n    { id:\"christos\",   name:\"Christos Giachoudis\",        inst:\"Fresnel Institut \u00b7 Centrale M\u00e9diterran\u00e9e\",            role:\"student\"   },\n    { id:\"alex\",       name:\"Alex Cameron\",               inst:\"Northumbria University, UK\",                          role:\"student\"   },\n    { id:\"francisco\",  name:\"Francisco Rau\",              inst:\"MaxLinear \u00b7 Universitat de Val\u00e8ncia\",                 role:\"student\"   },\n    { id:\"atiya\",      name:\"Atiya Fatima Usmani\",        inst:\"Instituto de Telecomunica\u00e7\u00f5es & Univ. de Aveiro\",     role:\"student\"   },\n    { id:\"jan\",        name:\"Jan Voc\u00edlka\",                inst:\"Czech Technical University in Prague\",                role:\"student\"   },\n    { id:\"luis2\",      name:\"Luis Miguel Giraldo\",        inst:\"Universitat de Val\u00e8ncia, Spain\",                      role:\"student\"   },\n    { id:\"miguel\",     name:\"Miguel R\u00eago\",                inst:\"Instituto de Telecomunica\u00e7\u00f5es, Portugal\",             role:\"student\"   },\n    { id:\"julian\",     name:\"Juli\u00e1n Sol\u00eds Torrej\u00f3n\",      inst:\"Northumbria University, UK\",                          role:\"student\"   },\n    { id:\"haili\",      name:\"Haili Ma\",                   inst:\"Northumbria University, UK\",                          role:\"student\"   },\n    { id:\"adeel\",      name:\"Adeel Faruq\",                inst:\"Northumbria University, UK\",                          role:\"student\"   },\n    { id:\"nora\",       name:\"Atiyeh Nora Pouralizadeh\",   inst:\"Fraunhofer HHI, Germany\",                             role:\"student\"   },\n    { id:\"radek\",      name:\"Radek Nesn\u00eddal\",             inst:\"Czech Technical University in Prague\",                role:\"student\"   },\n    { id:\"yiming\",     name:\"Yiming Shen\",                inst:\"Eblana Photonics, Dublin, Ireland\",                   role:\"student\"   },\n    { id:\"alexandros\", name:\"Alexandros Aslanidis\",       inst:\"Harokopio University, Greece\",                        role:\"student\"   },\n    { id:\"raul\",       name:\"Raul Zamorano-Illanes\",      inst:\"Optical Comms \u00b7 Northumbria University\",              role:\"student\"   },\n  ];\n\n  const links = [\n    \/\/ ECRN collaborations (senior researchers)\n    { source:\"zabih\",     target:\"stanislav\",  type:\"ECRN\" },\n    { source:\"zabih\",     target:\"luis\",       type:\"ECRN\" },\n    { source:\"zabih\",     target:\"joaquin\",    type:\"ECRN\" },\n    { source:\"zabih\",     target:\"anna\",       type:\"ECRN\" },\n    { source:\"zabih\",     target:\"rafael\",     type:\"ECRN\" },\n    { source:\"zabih\",     target:\"pedro\",      type:\"ECRN\" },\n    { source:\"zabih\",     target:\"mohammad\",   type:\"ECRN\" },\n    { source:\"zabih\",     target:\"monica\",     type:\"ECRN\" },\n    { source:\"zabih\",     target:\"matej\",      type:\"ECRN\" },\n    { source:\"zabih\",     target:\"hoa\",        type:\"institution\" },\n    { source:\"zabih\",     target:\"qiang\",      type:\"institution\" },\n    { source:\"zabih\",     target:\"yongtao\",    type:\"institution\" },\n    { source:\"stanislav\", target:\"luis\",       type:\"ECRN\" },\n    { source:\"stanislav\", target:\"joaquin\",    type:\"ECRN\" },\n    { source:\"stanislav\", target:\"matej\",      type:\"institution\" },\n    { source:\"rafael\",    target:\"vicente\",    type:\"institution\" },\n    { source:\"vicente\",   target:\"luis\",       type:\"ECRN\" },\n    { source:\"vicente\",   target:\"stanislav\",  type:\"ECRN\" },\n    { source:\"luis\",      target:\"pedro\",      type:\"institution\" },\n    { source:\"luis\",      target:\"monica\",     type:\"ECRN\" },\n    { source:\"pedro\",     target:\"monica\",     type:\"institution\" },\n    { source:\"anna\",      target:\"joaquin\",    type:\"ECRN\" },\n    { source:\"mohammad\",  target:\"stanislav\",  type:\"ECRN\" },\n    \/\/ Postdoc same institution\n    { source:\"stanislav\", target:\"carlos\",     type:\"institution\" },\n    { source:\"rafael\",    target:\"vicente\",    type:\"institution\" },\n    { source:\"vicente\",   target:\"luis\",       type:\"ECRN\" },\n    { source:\"vicente\",   target:\"stanislav\",  type:\"ECRN\" },\n    \/\/ Supervision\n    { source:\"satish\",    target:\"stanislav\",  type:\"supervision\" },\n    { source:\"satish\",    target:\"carlos\",     type:\"supervision\" },\n    { source:\"claudia\",   target:\"anna\",       type:\"supervision\" },\n    { source:\"christos\",  target:\"mohammad\",   type:\"supervision\" },\n    { source:\"raul\",      target:\"zabih\",      type:\"supervision\" },\n    { source:\"raul\",      target:\"yongtao\",    type:\"supervision\" },\n    { source:\"raul\",      target:\"stanislav\",  type:\"supervision\" },\n    { source:\"raul\",      target:\"hoa\",        type:\"supervision\" },\n    { source:\"raul\",      target:\"qiang\",      type:\"supervision\" },\n    { source:\"francisco\", target:\"joaquin\",    type:\"supervision\" },\n    { source:\"alex\",      target:\"zabih\",      type:\"supervision\" },\n    { source:\"atiya\",     target:\"pedro\",      type:\"supervision\" },\n    { source:\"atiya\",     target:\"monica\",     type:\"supervision\" },\n    { source:\"atiya\",     target:\"luis\",       type:\"supervision\" },\n    { source:\"jan\",       target:\"stanislav\",  type:\"supervision\" },\n    { source:\"jan\",       target:\"matej\",      type:\"supervision\" },\n    { source:\"luis2\",     target:\"joaquin\",    type:\"supervision\" },\n    { source:\"miguel\",    target:\"pedro\",      type:\"supervision\" },\n    { source:\"miguel\",    target:\"luis\",       type:\"supervision\" },\n    { source:\"julian\",    target:\"zabih\",      type:\"supervision\" },\n    { source:\"haili\",     target:\"qiang\",      type:\"supervision\" },\n    { source:\"adeel\",     target:\"qiang\",      type:\"supervision\" },\n    { source:\"nora\",      target:\"mohammad\",   type:\"supervision\" },\n    { source:\"radek\",     target:\"stanislav\",  type:\"supervision\" },\n    { source:\"yiming\",    target:\"zabih\",      type:\"supervision\" },\n    { source:\"alexandros\",target:\"joaquin\",    type:\"supervision\" },\n    { source:\"alexandros\",target:\"stanislav\",  type:\"supervision\" },\n  ];\n\n  function init() {\n    const container = document.getElementById(\"ecrn-graph-container\");\n    const tooltip   = document.getElementById(\"ecrn-tooltip\");\n\n    const W = container.offsetWidth  || 900;\n    const H = container.offsetHeight || 600;\n\n    const svg = d3.select(container).append(\"svg\")\n      .attr(\"width\", W).attr(\"height\", H);\n\n    const defs = svg.append(\"defs\");\n\n    \/\/ Arrowheads\n    Object.entries(LINK_COLORS).forEach(([type, color]) => {\n      defs.append(\"marker\")\n        .attr(\"id\", \"arr-\" + type)\n        .attr(\"viewBox\", \"0 -4 8 8\")\n        .attr(\"refX\", 8).attr(\"refY\", 0)\n        .attr(\"markerWidth\", 5).attr(\"markerHeight\", 5)\n        .attr(\"orient\", \"auto\")\n        .append(\"path\").attr(\"d\", \"M0,-4L8,0L0,4\")\n        .attr(\"fill\", color).attr(\"opacity\", 0.7);\n    });\n\n    \/\/ Glow\n    const glow = defs.append(\"filter\").attr(\"id\", \"ecrnGlow\");\n    glow.append(\"feGaussianBlur\").attr(\"stdDeviation\", 3).attr(\"result\", \"blur\");\n    const fm = glow.append(\"feMerge\");\n    fm.append(\"feMergeNode\").attr(\"in\", \"blur\");\n    fm.append(\"feMergeNode\").attr(\"in\", \"SourceGraphic\");\n\n    \/\/ Shadow\n    const shad = defs.append(\"filter\").attr(\"id\", \"ecrnShad\")\n      .attr(\"x\",\"-40%\").attr(\"y\",\"-40%\").attr(\"width\",\"180%\").attr(\"height\",\"180%\");\n    shad.append(\"feDropShadow\")\n      .attr(\"dx\",0).attr(\"dy\",2).attr(\"stdDeviation\",3)\n      .attr(\"flood-color\",\"#000\").attr(\"flood-opacity\",0.10);\n\n    const g = svg.append(\"g\");\n\n    const zoom = d3.zoom().scaleExtent([0.3, 3])\n      .on(\"zoom\", e => g.attr(\"transform\", e.transform));\n    \/\/ Only allow zoom via buttons, not scroll wheel or touch\n    svg.call(zoom)\n      .on(\"wheel.zoom\", null)\n      .on(\"dblclick.zoom\", null)\n      .on(\"touchstart.zoom\", null)\n      .on(\"touchmove.zoom\", null)\n      .on(\"touchend.zoom\", null);\n\n    document.getElementById(\"ecrnZoomIn\").onclick  = () => svg.transition().duration(300).call(zoom.scaleBy, 1.3);\n    document.getElementById(\"ecrnZoomOut\").onclick = () => svg.transition().duration(300).call(zoom.scaleBy, 0.77);\n    document.getElementById(\"ecrnReset\").onclick   = () => svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n\n    const sim = d3.forceSimulation(nodes)\n      .force(\"link\", d3.forceLink(links).id(d => d.id)\n        .distance(d => d.type === \"supervision\" ? 100 : 160)\n        .strength(d => d.type === \"supervision\" ? 0.8 : 0.4))\n      .force(\"charge\", d3.forceManyBody().strength(-350))\n      .force(\"center\", d3.forceCenter(W \/ 2, H \/ 2))\n      .force(\"collide\", d3.forceCollide().radius(d => RADII[d.role] + 22))\n      .force(\"x\", d3.forceX(W \/ 2).strength(0.03))\n      .force(\"y\", d3.forceY(H \/ 2).strength(0.08));\n\n    \/\/ Links\n    const linkSel = g.append(\"g\").selectAll(\"line\").data(links).enter().append(\"line\")\n      .attr(\"stroke\", d => LINK_COLORS[d.type])\n      .attr(\"stroke-width\", d => d.type === \"ECRN\" ? 2 : 1.5)\n      .attr(\"stroke-dasharray\", d => d.type === \"supervision\" ? \"5,4\" : null)\n      .attr(\"opacity\", d => d.type === \"institution\" ? 0.55 : 0.75)\n      .attr(\"marker-end\", d => \"url(#arr-\" + d.type + \")\");\n\n    \/\/ Nodes\n    const nodeSel = g.append(\"g\").selectAll(\"g\").data(nodes).enter().append(\"g\")\n      .style(\"cursor\", \"pointer\")\n      .call(d3.drag()\n        .on(\"start\", (e,d) => { if(!e.active) sim.alphaTarget(0.2).restart(); d.fx=d.x; d.fy=d.y; })\n        .on(\"drag\",  (e,d) => { d.fx=e.x; d.fy=e.y; })\n        .on(\"end\",   (e,d) => { if(!e.active) sim.alphaTarget(0); d.fx=null; d.fy=null; }))\n      .on(\"click\", onNodeClick)\n      .on(\"mouseover\", onOver)\n      .on(\"mousemove\", onMove)\n      .on(\"mouseout\",  onOut);\n\n    \/\/ White halo\n    nodeSel.append(\"circle\")\n      .attr(\"r\", d => RADII[d.role] + 3)\n      .attr(\"fill\", \"#fff\")\n      .attr(\"filter\", \"url(#ecrnShad)\");\n\n    \/\/ Colored circle\n    nodeSel.append(\"circle\")\n      .attr(\"class\", \"nc\")\n      .attr(\"r\", d => RADII[d.role])\n      .attr(\"fill\", d => COLORS[d.role].fill)\n      .attr(\"stroke\", d => COLORS[d.role].stroke)\n      .attr(\"stroke-width\", 2);\n\n    \/\/ Initials: first letter of first name + first letter of last name\n    nodeSel.append(\"text\")\n      .attr(\"text-anchor\", \"middle\").attr(\"dy\", \"0.35em\")\n      .attr(\"font-size\", d => d.role === \"professor\" ? \"11px\" : \"9px\")\n      .attr(\"font-weight\", \"700\").attr(\"fill\", \"#fff\")\n      .attr(\"pointer-events\", \"none\")\n      .text(d => initials(d.name));\n\n    \/\/ Labels below node\n    nodeSel.append(\"text\")\n      .attr(\"class\", \"nl\")\n      .attr(\"text-anchor\", \"middle\")\n      .attr(\"dy\", d => RADII[d.role] + 14)\n      .attr(\"font-size\", d => d.role === \"professor\" ? \"10.5px\" : \"9.5px\")\n      .attr(\"font-weight\", d => d.role === \"professor\" ? \"700\" : \"500\")\n      .attr(\"fill\", \"#2d3748\")\n      .attr(\"pointer-events\", \"none\")\n      .text(d => d.name.replace(\/^(Prof\\.|Dr\\.|Dra\\.) \/, \"\"))\n      .each(function(d) {\n        const el = d3.select(this);\n        const words = el.text().split(\" \");\n        if (words.length > 2) {\n          el.text(words.slice(0,2).join(\" \"));\n          el.append(\"tspan\").attr(\"x\",0).attr(\"dy\",\"12px\").text(words.slice(2).join(\" \"));\n        }\n      });\n\n    \/\/ Click highlight\n    let sel = null;\n    svg.on(\"click\", () => { sel = null; resetHL(); });\n\n    function onNodeClick(event, d) {\n      event.stopPropagation();\n      sel = sel === d.id ? null : d.id;\n      sel ? highlightHL(d) : resetHL();\n    }\n\n    function highlightHL(d) {\n      const conn = new Set([d.id]);\n      links.forEach(l => {\n        if (l.source.id === d.id) conn.add(l.target.id);\n        if (l.target.id === d.id) conn.add(l.source.id);\n      });\n      nodeSel.selectAll(\"circle,text\").attr(\"opacity\", nd => conn.has(nd.id) ? 1 : 0.15);\n      linkSel\n        .attr(\"opacity\", l => (l.source.id===d.id||l.target.id===d.id) ? 1 : 0.04)\n        .attr(\"stroke-width\", l => (l.source.id===d.id||l.target.id===d.id) ? 3 : 1.5);\n    }\n    function resetHL() {\n      nodeSel.selectAll(\"circle,text\").attr(\"opacity\", 1);\n      linkSel\n        .attr(\"opacity\", d => d.type===\"institution\" ? 0.55 : 0.75)\n        .attr(\"stroke-width\", d => d.type===\"ECRN\" ? 2 : 1.5);\n    }\n\n    \/\/ Tooltip\n    function onOver(event, d) {\n      tooltip.style.opacity = \"1\";\n      tooltip.innerHTML = `<div class=\"tt-name\">${d.name}<\/div><div class=\"tt-inst\">${d.inst}<\/div><span class=\"tt-badge\" style=\"${BADGE_CSS[d.role]}\">${ROLE_LABEL[d.role]}<\/span>`;\n      d3.select(this).select(\"circle.nc\").attr(\"filter\",\"url(#ecrnGlow)\");\n    }\n    function onMove(event) {\n      const r = container.getBoundingClientRect();\n      let l = event.clientX - r.left + 14;\n      let t = event.clientY - r.top  + 14;\n      if (l + 240 > W) l = event.clientX - r.left - 250;\n      if (t + 110 > H) t = event.clientY - r.top  - 120;\n      tooltip.style.left = l + \"px\";\n      tooltip.style.top  = t + \"px\";\n    }\n    function onOut(event, d) {\n      tooltip.style.opacity = \"0\";\n      d3.select(this).select(\"circle.nc\").attr(\"filter\", null);\n    }\n\n    \/\/ Tick\n    sim.on(\"tick\", () => {\n      linkSel\n        .attr(\"x1\", d => d.source.x).attr(\"y1\", d => d.source.y)\n        .attr(\"x2\", d => {\n          const dx=d.target.x-d.source.x, dy=d.target.y-d.source.y;\n          const dist=Math.sqrt(dx*dx+dy*dy)||1;\n          return d.target.x-(dx\/dist)*(RADII[d.target.role]+5);\n        })\n        .attr(\"y2\", d => {\n          const dx=d.target.x-d.source.x, dy=d.target.y-d.source.y;\n          const dist=Math.sqrt(dx*dx+dy*dy)||1;\n          return d.target.y-(dy\/dist)*(RADII[d.target.role]+5);\n        });\n      nodeSel.attr(\"transform\", d => `translate(${d.x},${d.y})`);\n    });\n\n    \/\/ Resize\n    new ResizeObserver(() => {\n      const nW = container.offsetWidth;\n      const nH = container.offsetHeight;\n      svg.attr(\"width\", nW).attr(\"height\", nH);\n      sim.force(\"center\", d3.forceCenter(nW\/2, nH\/2))\n         .force(\"x\", d3.forceX(nW\/2).strength(0.03))\n         .force(\"y\", d3.forceY(nH\/2).strength(0.08))\n         .alpha(0.3).restart();\n    }).observe(container);\n  }\n\n  function tryInit() {\n    const c = document.getElementById(\"ecrn-graph-container\");\n    if (c && c.offsetWidth > 0) { init(); }\n    else { setTimeout(tryInit, 60); }\n  }\n  tryInit();\n})();\n<\/script>\n<\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div style=\"height:37px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<!-- ECRN Publications Widget \u2014 paste into WordPress via Custom HTML block -->\n<style>\n#ecrn-pub-widget * { box-sizing: border-box; margin: 0; padding: 0; }\n#ecrn-pub-widget {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  font-size: 15px;\n  color: #2d3748;\n  max-width: 960px;\n  margin: 0 auto;\n}\n#ecrn-pub-widget .controls {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 10px;\n  margin-bottom: 20px;\n  align-items: center;\n}\n#ecrn-pub-widget input[type=\"text\"] {\n  flex: 1;\n  min-width: 200px;\n  padding: 10px 14px;\n  border: 1.5px solid #cbd5e0;\n  border-radius: 8px;\n  font-size: 14px;\n  outline: none;\n  color: #2d3748;\n  background: #fff;\n}\n#ecrn-pub-widget input[type=\"text\"]:focus { border-color: #4a5568; }\n#ecrn-pub-widget select {\n  padding: 10px 14px;\n  border: 1.5px solid #cbd5e0;\n  border-radius: 8px;\n  font-size: 14px;\n  background: #fff;\n  color: #2d3748;\n  cursor: pointer;\n}\n#ecrn-pub-widget .status {\n  font-size: 13px;\n  color: #718096;\n  margin-bottom: 16px;\n  min-height: 20px;\n}\n#ecrn-pub-widget .pub-table { width: 100%; border-collapse: collapse; }\n#ecrn-pub-widget .pub-table thead th {\n  font-size: 12px;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.6px;\n  color: #718096;\n  padding: 10px 14px;\n  border-bottom: 2px solid #e2e8f0;\n  text-align: left;\n  white-space: nowrap;\n}\n#ecrn-pub-widget .pub-table thead th.col-year { width: 60px; text-align: center; }\n#ecrn-pub-widget .pub-table thead th.col-cit  { width: 75px; text-align: center; }\n#ecrn-pub-widget .pub-table tbody tr { border-bottom: 1px solid #f0f4f8; }\n#ecrn-pub-widget .pub-table tbody tr:hover { background: #f7fafc; }\n#ecrn-pub-widget .pub-table td { padding: 12px 14px; vertical-align: top; font-size: 14px; }\n#ecrn-pub-widget td.col-year { text-align: center; font-weight: 700; color: #4a5568; white-space: nowrap; }\n#ecrn-pub-widget td.col-cit  { text-align: center; font-size: 13px; color: #718096; white-space: nowrap; }\n#ecrn-pub-widget .pub-title-link { font-weight: 600; color: #2b6cb0; text-decoration: none; line-height: 1.4; display: block; }\n#ecrn-pub-widget .pub-title-link:hover { text-decoration: underline; }\n#ecrn-pub-widget .pub-sub { font-size: 12px; color: #718096; margin-top: 3px; line-height: 1.4; }\n#ecrn-pub-widget .pub-author-tag {\n  display: inline-block; font-size: 11px; font-weight: 600;\n  padding: 2px 8px; border-radius: 20px;\n  background: #ebf4ff; color: #2b6cb0; margin-top: 5px;\n}\n#ecrn-pub-widget .no-results { text-align: center; padding: 48px 20px; color: #a0aec0; font-size: 14px; }\n#ecrn-pub-widget .spinner {\n  display: inline-block; width: 13px; height: 13px;\n  border: 2px solid #e2e8f0; border-top-color: #4a5568;\n  border-radius: 50%; animation: ecrn-spin 0.7s linear infinite;\n  vertical-align: middle; margin-right: 5px;\n}\n@keyframes ecrn-spin { to { transform: rotate(360deg); } }\n#ecrn-pub-widget .show-more-wrap {\n  text-align: center;\n  margin-top: 24px;\n  padding-bottom: 12px;\n}\n#ecrn-pub-widget .show-more-btn {\n  display: inline-block;\n  padding: 10px 32px;\n  background: #2d3748;\n  color: #fff;\n  border: none;\n  border-radius: 8px;\n  font-size: 14px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: background 0.2s;\n}\n#ecrn-pub-widget .show-more-btn:hover { background: #1a202c; }\n#ecrn-pub-widget .show-more-count {\n  display: block;\n  margin-top: 8px;\n  font-size: 12px;\n  color: #718096;\n}\n#ecrn-pub-widget .refresh-btn {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  padding: 10px 18px;\n  background: #fff;\n  color: #2d3748;\n  border: 1.5px solid #cbd5e0;\n  border-radius: 8px;\n  font-size: 14px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.2s;\n  white-space: nowrap;\n}\n#ecrn-pub-widget .refresh-btn:hover { background: #f7fafc; border-color: #4a5568; }\n#ecrn-pub-widget .refresh-btn.loading { opacity: 0.6; cursor: not-allowed; }\n#ecrn-pub-widget .refresh-btn svg { transition: transform 0.4s; }\n#ecrn-pub-widget .refresh-btn.loading svg { animation: ecrn-spin 0.7s linear infinite; }\n@media (max-width: 600px) {\n  #ecrn-pub-widget .pub-table thead th.col-cit,\n  #ecrn-pub-widget .pub-table td.col-cit { display: none; }\n}\n<\/style>\n\n<div id=\"ecrn-pub-widget\">\n  <div class=\"controls\">\n    <input type=\"text\" id=\"ecrn-search\" placeholder=\"Search title, author, venue\u2026\">\n    <select id=\"ecrn-person-filter\"><option value=\"\">All researchers<\/option><\/select>\n    <select id=\"ecrn-year-filter\"><option value=\"\">All years<\/option><\/select>\n    <button class=\"refresh-btn\" id=\"ecrn-refresh-btn\" title=\"Reload all publications\">\n      <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n        <path d=\"M23 4v6h-6\"\/><path d=\"M1 20v-6h6\"\/>\n        <path d=\"M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0019.49 15\"\/>\n      <\/svg>\n      Refresh\n    <\/button>\n  <\/div>\n  <div class=\"status\" id=\"ecrn-status\"><span class=\"spinner\"><\/span> Loading publications\u2026<\/div>\n  <div id=\"ecrn-content\"><\/div>\n  <div class=\"show-more-wrap\" id=\"ecrn-show-more-wrap\" style=\"display:none;\">\n    <button class=\"show-more-btn\" id=\"ecrn-show-more-btn\">Show 20 more<\/button>\n    <span class=\"show-more-count\" id=\"ecrn-show-more-count\"><\/span>\n  <\/div>\n<\/div>\n\n<script>\n(function () {\n\n  const PAGE_SIZE = 20;\n  let visibleCount = PAGE_SIZE;\n  let filteredPubs  = [];\n\n  \/\/ \u2500\u2500\u2500 PEOPLE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const PEOPLE = [\n    \/\/ Academics & Researchers\n    { name: \"Prof. Zabih Ghassemlooy\",    group: \"Academics\",    affil: \"Northumbria University, UK\",                          semanticId: \"47951467\"   },\n    { name: \"Prof. Stanislav Zv\u00e1novec\",   group: \"Academics\",    affil: \"Czech Technical Univ. in Prague, Czech Rep.\",         semanticId: \"2936222\" },\n    { name: \"Dr. Luis Nero Alves\",        group: \"Academics\",    affil: \"University of Aveiro, Portugal\",                      semanticId: \"34064949\"    },\n    { name: \"Dr. Joaquin Perez Soler\",    group: \"Academics\",    affil: \"Universitat de Val\u00e8ncia\",                             semanticId: \"2154721987\" },\n    { name: \"Dr. Anna Maria Vegni\",       group: \"Academics\",    affil: \"Roma Tre University, Italy\",                          semanticId: \"1780990\"    },\n    { name: \"Prof. Hoa Le Minh\",          group: \"Academics\",    affil: \"Northumbria University, UK\",                          semanticId: \"144842767\" },\n    { name: \"Prof. Rafael P\u00e9rez-Jim\u00e9nez\", group: \"Academics\",    affil: \"Universidad de Las Palmas de Gran Canaria, Spain\",    semanticId: \"1398407750\" },\n    { name: \"Dr. Qiang Wu\",               group: \"Academics\",    affil: \"Northumbria University, UK\",                          semanticId: \"2109038602\" },\n    { name: \"Dr. Yongtao Qu\",             group: \"Academics\",    affil: \"Northumbria University, UK\",                          semanticId: \"15511723\" },\n    { name: \"Dr. Pedro Fonseca\",          group: \"Academics\",    affil: \"Universidade de Aveiro, Portugal\",                    semanticId: \"2109038604\" },\n    { name: \"Dr. Mohammad-Ali Khalighi\",  group: \"Academics\",    affil: \"Institut Fresnel, France\",                            semanticId: \"3195490\" },\n    { name: \"Dr. M\u00f3nica Figueiredo\",      group: \"Academics\",    affil: \"Instituto de Telecomunica\u00e7\u00f5es, Portugal\",             semanticId: \"28011055\" },\n    { name: \"Dr. Mat\u011bj Komanec\",          group: \"Academics\",    affil: \"Czech Technical University in Prague, Czech Republic\",semanticId: \"2109038607\" },\n    \/\/ Postdoctoral Researchers\n    { name: \"Carlos Guerra\",              group: \"Postdoctoral\", affil: \"Czech Technical University in Prague\",                semanticId: \"2109155891\" },\n    { name: \"Vicente Matus\",              group: \"Postdoctoral\", affil: \"University of Las Palmas de Gran Canaria\",            semanticId: \"2109156012\" },\n    \/\/ Students Network\n    { name: \"Satish Kumar\",               group: \"Students\",     affil: \"Czech Technical University in Prague\",                semanticId: \"2275063803\" },\n    { name: \"Claudia Leoni\",              group: \"Students\",     affil: \"Roma Tre University\",                                 semanticId: \"2109154067\" },\n    { name: \"Christos Giachoudis\",        group: \"Students\",     affil: \"Fresnel Institut \u00b7 Centrale M\u00e9diterran\u00e9e\",            semanticId: \"2109177421\" },\n    { name: \"Alex Cameron\",               group: \"Students\",     affil: \"Northumbria University, UK\",                          semanticId: \"2109038608\" },\n    { name: \"Francisco Rau\",              group: \"Students\",     affil: \"MaxLinear \u00b7 Universitat de Val\u00e8ncia, Spain\",          semanticId: \"2109038812\" },\n    { name: \"Atiya Fatima Usmani\",        group: \"Students\",     affil: \"Instituto de Telecomunica\u00e7\u00f5es & Univ. de Aveiro\",     semanticId: \"2109038609\" },\n    { name: \"Jan Voc\u00edlka\",                group: \"Students\",     affil: \"Czech Technical University in Prague, Czech Republic\",semanticId: \"2109038610\" },\n    { name: \"Luis Miguel Giraldo\",        group: \"Students\",     affil: \"Universitat de Val\u00e8ncia, Spain\",                      semanticId: \"2109038611\" },\n    { name: \"Miguel R\u00eago\",                group: \"Students\",     affil: \"Instituto de Telecomunica\u00e7\u00f5es, Portugal\",             semanticId: \"2109038612\" },\n    { name: \"Juli\u00e1n Sol\u00eds Torrej\u00f3n\",      group: \"Students\",     affil: \"Northumbria University, UK\",                          semanticId: \"2109038613\" },\n    { name: \"Haili Ma\",                   group: \"Students\",     affil: \"Northumbria University, UK\",                          semanticId: \"2109038614\" },\n    { name: \"Adeel Faruq\",                group: \"Students\",     affil: \"Northumbria University, UK\",                          semanticId: \"2109038615\" },\n    { name: \"Atiyeh Nora Pouralizadeh\",   group: \"Students\",     affil: \"Fraunhofer HHI, Germany\",                             semanticId: \"2373113215\" },\n    { name: \"Radek Nesn\u00eddal\",             group: \"Students\",     affil: \"Czech Technical University in Prague, Czech Republic\",semanticId: \"2109038617\" },\n    { name: \"Yiming Shen\",                group: \"Students\",     affil: \"Eblana Photonics, Dublin, Ireland\",                   semanticId: \"2109038618\" },\n    { name: \"Alexandros Aslanidis\",       group: \"Students\",     affil: \"Harokopio University, Greece\",                        semanticId: \"2369803265\" },\n    { name: \"Raul Zamorano-Illanes\",      group: \"Students\",     affil: \"Northumbria University, UK\",                          semanticId: \"2041291066\" },\n  ];\n\n  \/\/ \u2500\u2500\u2500 GOOGLE SCHOLAR LINKS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const SCHOLAR_LINKS = {\n    \"Prof. Zabih Ghassemlooy\":    \"https:\/\/scholar.google.com\/citations?user=6G0YIUgAAAAJ&hl=en\",\n    \"Prof. Stanislav Zv\u00e1novec\":   \"https:\/\/scholar.google.cz\/citations?user=GZrhDMUAAAAJ&hl=cs\",\n    \"Dr. Luis Nero Alves\":        \"https:\/\/scholar.google.com\/citations?user=5zmfLYAAAAAJ&hl=en\",\n    \"Dr. Joaquin Perez Soler\":    \"https:\/\/scholar.google.com\/citations?user=nqdBWm0AAAAJ\",\n    \"Dr. Anna Maria Vegni\":       \"https:\/\/scholar.google.com\/citations?user=VwczFYsAAAAJ&hl=en\",\n    \"Prof. Hoa Le Minh\":          \"https:\/\/scholar.google.com\/citations?user=8Khn9ckAAAAJ&hl=en\",\n    \"Prof. Rafael P\u00e9rez-Jim\u00e9nez\": \"https:\/\/scholar.google.com\/citations?user=7dyUaNsAAAAJ&hl=es\",\n    \"Dr. Qiang Wu\":               \"https:\/\/scholar.google.com\/citations?user=vZ9ZgZgAAAAJ&hl=en\",\n    \"Dr. Yongtao Qu\":             \"https:\/\/scholar.google.com\/citations?user=vE_AWpYAAAAJ&hl=en\",\n    \"Dr. Pedro Fonseca\":          \"https:\/\/scholar.google.pt\/citations?user=ZahX9qAAAAAJ&hl=en\",\n    \"Dr. Mohammad-Ali Khalighi\":  \"https:\/\/scholar.google.com\/citations?user=bxB8kjYAAAAJ&hl=fr\",\n    \"Dr. M\u00f3nica Figueiredo\":      \"https:\/\/scholar.google.com\/citations?user=LGZp8OcAAAAJ&hl=pt-PT\",\n    \"Dr. Mat\u011bj Komanec\":          \"https:\/\/scholar.google.com\/citations?user=8xoJkqgAAAAJ&hl=es&oi=ao\",\n    \"Carlos Guerra\":              \"https:\/\/scholar.google.com\/citations?user=XJBsSN0AAAAJ&hl=es\",\n    \"Vicente Matus\":              \"https:\/\/scholar.google.com\/citations?user=Exjlq20AAAAJ&hl=es\",\n    \"Satish Kumar\":               \"https:\/\/scholar.google.com\/citations?user=AqBtxvYAAAAJ&hl=en\",\n    \"Claudia Leoni\":              \"https:\/\/scholar.google.com\/citations?user=uhHrR7kAAAAJ&hl=en\",\n    \"Christos Giachoudis\":        \"https:\/\/scholar.google.com\/citations?user=VXO3Ww8AAAAJ&hl=el\",\n    \"Alex Cameron\":               \"https:\/\/scholar.google.co.uk\/citations?user=1LXHw0cAAAAJ&hl=en\",\n    \"Francisco Rau\":              \"https:\/\/scholar.google.com\/citations?user=uFnqRGwAAAAJ&hl=es\",\n    \"Atiya Fatima Usmani\":        \"https:\/\/scholar.google.com\/citations?user=6z77PA4AAAAJ&hl=en\",\n    \"Jan Voc\u00edlka\":                \"https:\/\/scholar.google.com\/citations?user=Gj2wsKEAAAAJ&hl=cs\",\n    \"Luis Miguel Giraldo\":        \"https:\/\/scholar.google.com\/citations?user=NZoH_EYAAAAJ&hl=en\",\n    \"Miguel R\u00eago\":                \"https:\/\/scholar.google.com\/citations?user=qCXx7UAAAAAJ\",\n    \"Juli\u00e1n Sol\u00eds Torrej\u00f3n\":      \"https:\/\/scholar.google.com\/citations?user=mW59WHYAAAAJ&hl=en\",\n    \"Haili Ma\":                   \"#\",\n    \"Adeel Faruq\":                \"https:\/\/scholar.google.com\/citations?user=RHcra7wAAAAJ&hl=en\",\n    \"Atiyeh Nora Pouralizadeh\":   \"https:\/\/scholar.google.com\/citations?user=zvqyCrsAAAAJ&hl=en\",\n    \"Radek Nesn\u00eddal\":             \"https:\/\/scholar.google.com\/citations?user=3yP4AeIAAAAJ&hl=cs\",\n    \"Yiming Shen\":                \"https:\/\/scholar.google.com\/citations?user=YnEnZe0AAAAJ&hl=zh-CN\",\n    \"Alexandros Aslanidis\":       \"https:\/\/scholar.google.com\/citations?user=QnhmMlUAAAAJ&hl=el\",\n    \"Raul Zamorano-Illanes\":      \"https:\/\/scholar.google.com\/citations?user=kM5NjkEAAAAJ&hl=es\",\n  };\n\n  let allPubs = [];\n  let loadedCount = 0;\n\n  \/\/ Known placeholder IDs that have no real Semantic Scholar entry yet\n  const PLACEHOLDER_IDS = new Set([\n    \"2109038602\",\"2109038604\",\n    \"2109038607\",\"2109038608\",\"2109038609\",\n    \"2109038610\",\"2109038611\",\"2109038612\",\"2109038613\",\"2109038614\",\n    \"2109038615\",\"2109038617\",\"2109038618\",\n  ]);\n\n  async function fetchWithTimeout(url, ms = 8000) {\n    const controller = new AbortController();\n    const timer = setTimeout(() => controller.abort(), ms);\n    try {\n      const res = await fetch(url, { signal: controller.signal });\n      clearTimeout(timer);\n      return res;\n    } catch (e) {\n      clearTimeout(timer);\n      throw e;\n    }\n  }\n\n  async function fetchPubs(person) {\n    \/\/ Skip placeholder IDs immediately \u2014 no network call needed\n    if (PLACEHOLDER_IDS.has(person.semanticId)) return [];\n    try {\n      const url = `https:\/\/api.semanticscholar.org\/graph\/v1\/author\/${person.semanticId}\/papers`\n                + `?fields=title,year,venue,externalIds,citationCount,authors&limit=100`;\n      const res = await fetchWithTimeout(url, 8000);\n      if (!res.ok) throw new Error(`HTTP ${res.status}`);\n      const json = await res.json();\n      return (json.data || []).map(p => {\n        const authorsArr = (p.authors || []).map(a => a.name);\n        const authors = authorsArr.length > 5\n          ? authorsArr.slice(0, 5).join(\", \") + \" et al.\"\n          : authorsArr.join(\", \");\n        const doi = p.externalIds && p.externalIds.DOI;\n        return {\n          title:      p.title || \"(untitled)\",\n          year:       p.year  || 0,\n          venue:      p.venue || \"\",\n          citations:  p.citationCount || 0,\n          authors,\n          url:        doi\n                        ? `https:\/\/doi.org\/${doi}`\n                        : `https:\/\/api.semanticscholar.org\/graph\/v1\/paper\/${p.paperId}`,\n          authorName: person.name,\n        };\n      });\n    } catch (e) {\n      console.warn(`Failed for ${person.name}:`, e.message);\n      return [];\n    }\n  }\n\n  function getFilters() {\n    return {\n      search: document.getElementById(\"ecrn-search\").value.trim().toLowerCase(),\n      person: document.getElementById(\"ecrn-person-filter\").value,\n      year:   document.getElementById(\"ecrn-year-filter\").value,\n    };\n  }\n\n  function applyFilters() {\n    const { search, person, year } = getFilters();\n    let pubs = allPubs.slice();\n    if (person) pubs = pubs.filter(p => p.authorName === person);\n    if (year)   pubs = pubs.filter(p => String(p.year) === year);\n    if (search) pubs = pubs.filter(p =>\n      (p.title + p.authors + p.venue + p.authorName).toLowerCase().includes(search)\n    );\n    return pubs;\n  }\n\n  function render() {\n    filteredPubs = applyFilters();\n    const content  = document.getElementById(\"ecrn-content\");\n    const wrapEl   = document.getElementById(\"ecrn-show-more-wrap\");\n    const countEl  = document.getElementById(\"ecrn-show-more-count\");\n    const page     = filteredPubs.slice(0, visibleCount);\n\n    if (!filteredPubs.length) {\n      content.innerHTML = `<div class=\"no-results\">${\n        allPubs.length === 0 ? \"Loading\u2026\" : \"No publications match your filters.\"\n      }<\/div>`;\n      wrapEl.style.display = \"none\";\n      return;\n    }\n\n    const rows = page.map(p => {\n      const scholarLink = SCHOLAR_LINKS[p.authorName] || \"#\";\n      return `<tr>\n        <td class=\"col-year\">${p.year || \"\u2014\"}<\/td>\n        <td>\n          <a class=\"pub-title-link\" href=\"${p.url}\" target=\"_blank\" rel=\"noopener\">${p.title}<\/a>\n          <div class=\"pub-sub\">${[p.venue, p.authors].filter(Boolean).join(\" \u00b7 \")}<\/div>\n          <a class=\"pub-author-tag\" href=\"${scholarLink}\" target=\"_blank\" rel=\"noopener\">${p.authorName}<\/a>\n        <\/td>\n        <td class=\"col-cit\">${p.citations ? p.citations.toLocaleString() : \"\u2014\"}<\/td>\n      <\/tr>`;\n    }).join(\"\");\n\n    content.innerHTML = `\n      <table class=\"pub-table\">\n        <thead><tr>\n          <th class=\"col-year\">Year<\/th>\n          <th>Publication<\/th>\n          <th class=\"col-cit\">Citations<\/th>\n        <\/tr><\/thead>\n        <tbody>${rows}<\/tbody>\n      <\/table>`;\n\n    \/\/ Show \/ hide \"Show more\" button\n    if (visibleCount < filteredPubs.length) {\n      const remaining = filteredPubs.length - visibleCount;\n      wrapEl.style.display = \"block\";\n      countEl.textContent = `Showing ${Math.min(visibleCount, filteredPubs.length)} of ${filteredPubs.length} publications \u00b7 ${remaining} remaining`;\n    } else {\n      wrapEl.style.display = filteredPubs.length > PAGE_SIZE ? \"block\" : \"none\";\n      if (filteredPubs.length > PAGE_SIZE) {\n        countEl.textContent = `Showing all ${filteredPubs.length} publications`;\n        document.getElementById(\"ecrn-show-more-btn\").style.display = \"none\";\n      }\n    }\n  }\n\n  function updateStatus() {\n    const el = document.getElementById(\"ecrn-status\");\n    const active = PEOPLE.filter(p => !PLACEHOLDER_IDS.has(p.semanticId)).length;\n    if (loadedCount < PEOPLE.length) {\n      const done = Math.min(loadedCount, active);\n      el.innerHTML = `<span class=\"spinner\"><\/span> Loading\u2026 (${done} \/ ${active} researchers with publications)`;\n    } else {\n      const pending = PEOPLE.length - active;\n      const note = pending > 0 ? ` \u00b7 ${pending} researcher(s) pending Semantic Scholar ID` : '';\n      el.textContent = `${allPubs.length.toLocaleString()} publications \u00b7 ${active} researchers loaded${note}`;\n    }\n  }\n\n  function buildPersonFilter() {\n    const personSel = document.getElementById(\"ecrn-person-filter\");\n    personSel.innerHTML = '<option value=\"\">All researchers<\/option>';\n    const groups = [\n      { label: \"Academics & Researchers\",  key: \"Academics\"    },\n      { label: \"Postdoctoral Researchers\",  key: \"Postdoctoral\" },\n      { label: \"Students Network\",          key: \"Students\"     },\n    ];\n    groups.forEach(g => {\n      const members = PEOPLE.filter(p => p.group === g.key);\n      if (!members.length) return;\n      const optgroup = document.createElement(\"optgroup\");\n      optgroup.label = g.label;\n      members.forEach(p => {\n        const opt = document.createElement(\"option\");\n        opt.value = p.name;\n        opt.textContent = p.name;\n        optgroup.appendChild(opt);\n      });\n      personSel.appendChild(optgroup);\n    });\n  }\n\n  function buildYearFilter() {\n    const yearSel = document.getElementById(\"ecrn-year-filter\");\n    const prevYear = yearSel.value;\n    yearSel.innerHTML = '<option value=\"\">All years<\/option>';\n    const years = [...new Set(allPubs.map(p => p.year).filter(Boolean))].sort((a,b) => b-a);\n    years.forEach(y => {\n      const opt = document.createElement(\"option\");\n      opt.value = String(y); opt.textContent = y;\n      yearSel.appendChild(opt);\n    });\n    if (prevYear) yearSel.value = prevYear;\n  }\n\n  function resetPagination() {\n    visibleCount = PAGE_SIZE;\n    document.getElementById(\"ecrn-show-more-btn\").style.display = \"inline-block\";\n  }\n\n  let isLoading = false;\n\n  async function load() {\n    if (isLoading) return;\n    isLoading = true;\n\n    \/\/ Save current filter values\n    const savedSearch = document.getElementById(\"ecrn-search\").value;\n    const savedPerson = document.getElementById(\"ecrn-person-filter\").value;\n    const savedYear   = document.getElementById(\"ecrn-year-filter\").value;\n\n    \/\/ Reset publication state\n    allPubs = [];\n    loadedCount = 0;\n    resetPagination();\n\n    const refreshBtn = document.getElementById(\"ecrn-refresh-btn\");\n    refreshBtn.classList.add(\"loading\");\n    refreshBtn.disabled = true;\n\n    buildPersonFilter();\n    buildYearFilter();\n\n    \/\/ Restore filters immediately\n    document.getElementById(\"ecrn-search\").value = savedSearch;\n    document.getElementById(\"ecrn-person-filter\").value = savedPerson;\n    document.getElementById(\"ecrn-year-filter\").value = savedYear;\n\n    render();\n\n    \/\/ Fetch all in parallel, each one updates state independently when done\n    const promises = PEOPLE.map(person =>\n      fetchPubs(person).then(pubs => {\n        pubs.forEach(p => allPubs.push(p));\n        loadedCount++;\n        allPubs.sort((a, b) => (b.year || 0) - (a.year || 0));\n        buildYearFilter();\n        document.getElementById(\"ecrn-year-filter\").value = savedYear;\n        updateStatus();\n        render();\n      }).catch(e => {\n        \/\/ Never block overall load on individual failure\n        console.warn(\"Load error for person:\", e);\n        loadedCount++;\n        updateStatus();\n      })\n    );\n\n    \/\/ Wait for all, but with a hard ceiling of 15s so it never gets stuck\n    await Promise.race([\n      Promise.all(promises),\n      new Promise(resolve => setTimeout(resolve, 15000))\n    ]);\n\n    \/\/ Force loadedCount to full so status bar completes even on timeout\n    loadedCount = PEOPLE.length;\n    updateStatus();\n    render();\n\n    refreshBtn.classList.remove(\"loading\");\n    refreshBtn.disabled = false;\n    isLoading = false;\n  }\n\n  \/\/ Debounce search input so rapid typing doesn't cause repeated renders\n  let searchTimer = null;\n  document.getElementById(\"ecrn-search\").addEventListener(\"input\", () => {\n    clearTimeout(searchTimer);\n    searchTimer = setTimeout(() => { resetPagination(); render(); }, 200);\n  });\n  document.getElementById(\"ecrn-person-filter\").addEventListener(\"change\", () => { resetPagination(); render(); });\n  document.getElementById(\"ecrn-year-filter\").addEventListener(\"change\", () => { resetPagination(); render(); });\n\n  document.getElementById(\"ecrn-show-more-btn\").addEventListener(\"click\", () => {\n    visibleCount += PAGE_SIZE;\n    render();\n  });\n\n  document.getElementById(\"ecrn-refresh-btn\").addEventListener(\"click\", () => { load(); });\n\n  load();\n})();\n<\/script>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>+ \u2212 \u2299 Drag \u00b7 Use +\/\u2212 to zoom \u00b7 Click to highlight Professor \/ Senior Researcher Postdoctoral Researcher PhD Student ECRN collaboration Supervision Same institution All researchers All years Refresh Loading publications\u2026 Show 20 more<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-422","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/pages\/422","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/comments?post=422"}],"version-history":[{"count":24,"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/pages\/422\/revisions"}],"predecessor-version":[{"id":538,"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/pages\/422\/revisions\/538"}],"wp:attachment":[{"href":"https:\/\/v1.ecrn-owc.eu\/index.php\/wp-json\/wp\/v2\/media?parent=422"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}