Download index.html from Hugdrigo/citegraph-scholar-network-lab: direct link, hf CLI and curl.
- Browser
- Download file 55.1 kB
-
https://huggingface.co/spaces/Hugdrigo/citegraph-scholar-network-lab/resolve/main/index.html
- Command line
-
hf download hf://spaces/Hugdrigo/citegraph-scholar-network-lab/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Hugdrigo/citegraph-scholar-network-lab/resolve/main/index.html
55.1 kB
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#0a1220"><meta name="description" content="CiteGraph: privacy-first scholarly citation network analysis with transparent and reproducible metrics."><title>CiteGraph · Scholarly Network Intelligence</title> | |
| <style>:root{color-scheme:dark;--bg:#080f1c;--side:#0b1423;--panel:#101b2b;--panel2:#152236;--panel3:#1b2a40;--line:#273750;--muted:#8b9bb1;--text:#edf4fc;--accent:#54d7b3;--accent2:#98a7ff;--warn:#f5b46b;--red:#ff8c9a;font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px}button,select,input{font:inherit}button{cursor:pointer}button:focus-visible,select:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:3px}h1,h2,h3,p{margin-top:0}h1{letter-spacing:-1.6px}a{color:var(--accent)}.app-shell{min-height:100vh;display:grid;grid-template-columns:252px minmax(0,1fr)}.sidebar{background:var(--side);padding:29px 17px 20px;border-right:1px solid #1c2b40;display:flex;flex-direction:column;position:sticky;top:0;height:100vh;z-index:10}.brand{display:flex;align-items:center;gap:11px;padding:1px 12px 40px}.brand-symbol{width:40px;height:40px;border:1px solid #285f5e;background:#113237;color:#83edc9;display:flex;align-items:center;justify-content:center;border-radius:13px;font-size:30px}.brand b{display:block;font-size:21px;letter-spacing:-.7px}.brand b span{color:#7de9c4}.brand small{font-size:8px;letter-spacing:1.6px;color:#8b9fb4;font-weight:800}.sidebar-head{font-size:10px;color:#6c8198;letter-spacing:1.9px;font-weight:800;display:flex;justify-content:space-between;padding:7px 14px 17px}.sidebar-head span{color:#47647d}.sidebar-nav{display:grid;gap:6px}.nav-link{display:flex;align-items:center;gap:12px;color:#9baac1;background:transparent;text-align:left;border:1px solid transparent;border-radius:9px;padding:13px 12px;font-size:12.5px;font-weight:650;width:100%}.nav-link:hover,.nav-link:focus-visible{background:#152339;color:#edf6ff}.nav-link.active{background:#12382f;color:#a7f9d8;border-color:#206552}.nav-icon{font-size:20px;font-weight:500;width:22px;text-align:center}.nav-num{margin-left:auto;color:#64778b;font-size:10px}.nav-link.active .nav-num{color:#73d9b3}.sidebar-spacer{flex:1}.sidebar-bottom{margin:0 8px;padding:17px 10px 0;border-top:1px solid #29384c}.privacy-marker{font-size:11px;font-weight:800;letter-spacing:1px;color:#adf3d8}.dot,.secure-dot{color:#56d4a6}.dot{background:#5ad3a8;display:inline-block;height:7px;width:7px;border-radius:50%;margin-right:6px}.sidebar-bottom p{color:#8999ad;font-size:11px;line-height:1.65;margin:11px 0 20px}.sidebar-version{font-size:10px;color:#6e8094;display:flex;justify-content:space-between}.sidebar-version span{color:#98a6bb}.main-shell{min-width:0}.topbar{height:79px;border-bottom:1px solid #243249;display:flex;justify-content:space-between;align-items:center;padding:0 40px;background:#0a1321}.breadcrumb{font-size:11px;letter-spacing:1.3px;font-weight:800;color:#8294aa}.breadcrumb span{margin:0 12px;color:#425771}.breadcrumb strong{color:#dae8f6}.top-actions{display:flex;gap:20px;align-items:center}.secure-dot{font-size:10px;letter-spacing:1.1px;font-weight:800}.btn{padding:10px 15px;border-radius:8px;border:1px solid transparent;font-size:12px;font-weight:800;transition:all .12s;white-space:nowrap}.btn-primary{background:#5fd7b6;color:#071c1a}.btn-primary:hover{background:#a1f9dd}.btn-outline{background:#18273a;border-color:#33465f;color:#d4e4f2}.btn-outline:hover{border-color:#6fc9ba;background:#20384b}.btn-soft{background:#1c3537;border-color:#315d5a;color:#94e3cc}.btn[disabled]{opacity:.4;cursor:not-allowed}.mobile-row{display:none}.hamburger{border:1px solid var(--line);background:var(--panel);border-radius:8px;color:var(--text);padding:8px 11px}main{padding:38px 40px 25px;max-width:1650px;margin:auto}.page-heading{display:flex;justify-content:space-between;align-items:flex-end;gap:25px;padding-bottom:29px}.eyebrow{font-size:10px;letter-spacing:2px;color:var(--accent);font-weight:900;margin-bottom:12px}.page-heading h1{font-size:clamp(27px,3.15vw,44px);line-height:1.12;margin-bottom:13px;font-weight:770}.page-heading em{font-weight:500;color:#9df0d3;font-style:normal}.page-heading p{max-width:710px;line-height:1.55;color:#94a5b9;font-size:13px;margin:0}.heading-tools{width:280px;flex:none}.field-label{color:#8498ac;font-size:10px;font-weight:850;letter-spacing:1.3px;display:block;margin-bottom:10px}.select,.field-input{background:#162439;border:1px solid #2d405a;color:#e3edf8;border-radius:9px;padding:12px;font-size:12px;width:100%;max-width:100%;outline:none}.select{cursor:pointer}.field-input:focus,.select:focus{border-color:#6ecab5}.dataset-caption{display:block;margin-top:8px;font-size:9px;letter-spacing:1px;color:#7e94a8}.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-bottom:18px}.kpi{background:var(--panel);border:1px solid #26374e;border-radius:12px;padding:20px;position:relative;min-height:130px}.kpi .label{font-size:10px;letter-spacing:1.15px;color:#8b9db1;font-weight:850}.kpi strong{font-size:31px;line-height:1.25;display:block;margin:11px 0 9px;letter-spacing:-1.3px}.kpi .context{font-size:11px;color:#8496ab}.kpi .mark{position:absolute;right:20px;top:19px;font-size:21px;color:#77c9b5}.grid-2{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(285px,.9fr);gap:16px;margin-bottom:16px}.grid-half{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin-bottom:16px}.panel{background:var(--panel);border:1px solid #293a51;border-radius:12px;min-width:0;overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 21px 15px;border-bottom:1px solid #22344a}.panel-head h2{font-size:14px;font-weight:770;margin:0;letter-spacing:-.15px}.panel-head p{color:#8298ad;font-size:10.5px;margin:5px 0 0}.panel-body{padding:19px 21px}.panel-caption{font-size:11px;line-height:1.65;color:#8ea0b2;margin:8px 0 0}.pill{font-size:9px;letter-spacing:.8px;background:#1d3441;color:#98f4d8;border:1px solid #2a5b53;padding:6px 9px;border-radius:30px;font-weight:850;white-space:nowrap}.pill.muted{background:#1f2c40;border-color:#314158;color:#a6b7cc}.graph-stage{width:100%;position:relative;background:radial-gradient(circle at center,#162b3a 0%,#101b2b 67%);min-height:345px;overflow:hidden}.graph-stage svg{display:block;width:100%;height:auto;max-height:560px}.graph-key{display:flex;gap:13px;flex-wrap:wrap;padding:10px 19px 16px;color:#91a5b9;font-size:10px}.graph-key b{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}.graph-buttons{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.graph-buttons .mini-select{max-width:170px}.mini-select{background:#142338;border:1px solid #31445c;border-radius:7px;padding:8px;color:#e6f4ff;font-size:11px}.graph-point{cursor:pointer}.graph-point circle{transition:r .12s,stroke-width .12s}.graph-point:hover circle{stroke:#fff;stroke-width:3}.graph-point text{font-size:11px;fill:#c6d5e2;paint-order:stroke;stroke:#101b2b;stroke-width:3;pointer-events:none}.graph-edge{stroke:#527d8e;opacity:.32;stroke-width:1.5;fill:none}.graph-edge.dim{opacity:.09}.graph-node.dim{opacity:.15}.tiny-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.mini-stat{padding:14px;background:#17263a;border:1px solid #2a3d52;border-radius:9px}.mini-stat small{display:block;color:#94a5b7;font-size:10px}.mini-stat strong{font-size:19px;display:block;margin-top:7px}.bars{display:grid;gap:17px}.bar-row{min-width:0}.bar-info{display:flex;justify-content:space-between;gap:10px;font-size:11px;margin-bottom:7px}.bar-info b{font-weight:650;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.bar-info span{color:#9db0c3;flex:none}.bar-bg{height:7px;background:#243448;border-radius:8px;overflow:hidden}.bar-fill{height:100%;border-radius:8px}.divider{height:1px;background:#293950;margin:15px 0}.rank-list{display:grid;gap:0}.rank-row{display:grid;grid-template-columns:35px minmax(0,1fr) 70px 80px;align-items:center;gap:8px;padding:14px 5px;border-bottom:1px solid #24344b;cursor:pointer}.rank-row:hover{background:#18293f}.rank-row:last-child{border-bottom:0}.rank-number{color:var(--accent);font-size:12px;font-weight:900}.rank-title{font-weight:730;color:#eaf4fc;font-size:12px;line-height:1.4}.rank-sub{color:#8496ab;font-size:10px;margin-top:3px}.right{text-align:right}.rank-score{font-weight:850;color:#b5f7df}.timeline{width:100%;height:auto;display:block}.row{display:flex;align-items:center;gap:10px}.between{justify-content:space-between}.wrap{flex-wrap:wrap}.search-wrap{position:relative}.search-wrap input{padding-left:36px}.search-mark{position:absolute;left:12px;top:12px;color:#8aa0b4}.paper-card{background:#152338;border:1px solid #2e4258;border-radius:9px;padding:14px;cursor:pointer;text-align:left;color:inherit;width:100%}.paper-card:hover,.paper-card.selected{border-color:#5aa992;background:#1a353b}.paper-title{font-size:13px;font-weight:800;line-height:1.4;margin-bottom:7px}.paper-meta{font-size:11px;color:#8fa4ba;line-height:1.5}.paper-topic{display:inline-block;margin-top:8px;border:1px solid #35556a;color:#a2dbdc;border-radius:20px;padding:3px 9px;font-size:10px}.paper-list{display:grid;gap:8px;max-height:520px;overflow:auto;padding-right:3px}.details-title{font-size:19px;letter-spacing:-.45px;line-height:1.33;margin-bottom:13px}.details-meta{font-size:12px;color:#8da2b7;line-height:1.7}.details-links{max-height:220px;overflow:auto;display:grid;gap:7px}.link-item{border:1px solid #293a51;border-radius:7px;background:#172538;padding:9px;text-align:left;color:#bed2e2;font-size:11px;width:100%}.link-item:hover{border-color:#57c5aa}.section-label{font-size:10px;font-weight:900;letter-spacing:1.3px;color:#99b1c3;margin:14px 0 10px}.tag-row{display:flex;gap:7px;flex-wrap:wrap}.tag{padding:5px 9px;color:#b7c9d9;border-radius:18px;border:1px solid #37506a;background:#1a2c40;font-size:10px}.empty{text-align:center;color:#9aabbc;padding:30px 10px;font-size:12px}.notice{background:#1b302f;border:1px solid #34645a;border-radius:9px;padding:13px 15px;line-height:1.55;color:#bfeadb;font-size:12px}.notice.warning{background:#322b25;border-color:#665043;color:#f2cfac}.audit-list{display:grid;gap:9px}.audit-item{border:1px solid #2d4356;background:#172536;border-radius:9px;padding:12px 14px;line-height:1.6;color:#beccda;font-size:12px;display:flex;gap:10px}.audit-item span{color:var(--warn)}.mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.code-snippet{background:#0a1320;border:1px solid #2d4055;padding:15px;border-radius:9px;color:#9ee2cc;white-space:pre-wrap;overflow-wrap:anywhere;font-size:11px;line-height:1.7}.file-box{padding:17px;border:1px dashed #4e657c;border-radius:10px;background:#132137;margin-bottom:14px}.file-box input{display:block;width:100%;font-size:12px;margin-top:10px;color:#a7bad0}.file-box strong{font-size:12px}.field-note{font-size:10.5px;line-height:1.6;color:#90a5ba;margin:8px 0 0}.action-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.action-grid .btn{text-align:left;white-space:normal}.section-title{font-size:15px;font-weight:850;margin-bottom:15px}.footer{color:#64798c;font-size:9px;letter-spacing:1px;font-weight:700;border-top:1px solid #253448;margin-top:32px;padding:24px 0;display:flex;justify-content:space-between;gap:15px;flex-wrap:wrap}.toast{position:fixed;bottom:22px;right:22px;max-width:min(460px,calc(100vw - 44px));padding:14px 20px;color:#dffcec;background:#174336;border:1px solid #45947b;border-radius:9px;box-shadow:0 12px 28px #02061099;display:none;font-size:13px;z-index:100}.toast.error{background:#42252c;border-color:#a45165;color:#ffe1e8}.toast.show{display:block}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;clip:rect(0,0,0,0);overflow:hidden;white-space:nowrap}.topic-card{padding:18px;border:1px solid #2b3d50;border-radius:10px;background:#17263a}.topic-card .top{font-size:13px;font-weight:800;margin-bottom:11px}.topic-card .big{font-size:25px;font-weight:850}.topic-card .muted{font-size:11px;color:#9bb1c2}.topic-card .stripe{height:4px;margin-top:14px;border-radius:5px}.model-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:13px;margin-bottom:18px}.two-details{display:grid;grid-template-columns:1.05fr .95fr;gap:15px}.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.toolbar .field-input{max-width:230px}.single-column{display:grid;gap:16px}.small-muted{font-size:11px;color:#94a8bb}.section-legend{color:#8ba0b3;font-size:11px;line-height:1.6} | |
| @media(max-width:1120px){.app-shell{grid-template-columns:213px minmax(0,1fr)}.sidebar{padding-left:10px;padding-right:10px}.nav-link{padding:11px 8px;font-size:11px}.topbar{padding:0 25px}main{padding:30px 25px 22px}.heading-tools{width:245px}.grid-2{grid-template-columns:1.3fr .8fr}} | |
| @media(max-width:830px){.app-shell{display:block}.sidebar{position:fixed;left:0;top:0;bottom:0;width:265px;transform:translateX(-101%);transition:transform .18s ease;box-shadow:12px 0 30px #02040a90}.sidebar.open{transform:translateX(0)}.topbar{height:65px;padding:0 19px}.breadcrumb{display:none}.mobile-row{display:flex;align-items:center;gap:10px}.mobile-brand{color:#a4f1d6;font-weight:850;font-size:16px}.top-actions{gap:10px}main{padding:26px 18px}.page-heading{align-items:flex-start;flex-direction:column;gap:19px}.heading-tools{width:100%;max-width:390px}.page-heading h1{font-size:32px}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-2,.grid-half,.two-details{grid-template-columns:1fr}.model-grid{grid-template-columns:repeat(2,minmax(0,1fr))}} | |
| @media(max-width:480px){.topbar{padding:0 13px}.top-actions .secure-dot{display:none}main{padding:24px 12px}.page-heading h1{font-size:29px}.kpi{padding:15px;min-height:120px}.kpi strong{font-size:26px}.panel-head{padding:15px}.panel-body{padding:14px}.rank-row{grid-template-columns:22px minmax(0,1fr) 50px 55px;gap:4px}.rank-score{font-size:11px}.rank-title{font-size:11px}.action-grid{grid-template-columns:1fr}.graph-key{padding:10px}.graph-stage{min-height:260px}.grid-2{gap:12px}.footer{line-height:1.7}.top-actions .btn{padding:9px 10px;font-size:11px}} | |
| </style></head><body> | |
| <div class="app-shell"> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="brand"><span class="brand-symbol">✧</span><div><b>Cite<span>Graph</span></b><small>RESEARCH INTELLIGENCE</small></div></div> | |
| <div class="sidebar-head">WORKSPACE <span>01</span></div> | |
| <nav class="sidebar-nav" id="nav" aria-label="Application navigation"> | |
| <button data-view="overview" class="nav-link active"><span class="nav-icon">◫</span> Overview <span class="nav-num">01</span></button> | |
| <button data-view="network" class="nav-link"><span class="nav-icon">⠿</span> Network Explorer <span class="nav-num">02</span></button> | |
| <button data-view="influence" class="nav-link"><span class="nav-icon">↗</span> Influence Rankings <span class="nav-num">03</span></button> | |
| <button data-view="themes" class="nav-link"><span class="nav-icon">◈</span> Themes & Timeline <span class="nav-num">04</span></button> | |
| <button data-view="audit" class="nav-link"><span class="nav-icon">▤</span> Audit & Methods <span class="nav-num">05</span></button> | |
| <button data-view="data" class="nav-link"><span class="nav-icon">⇅</span> Data & Exports <span class="nav-num">06</span></button> | |
| </nav> | |
| <div class="sidebar-spacer"></div><div class="sidebar-bottom"><div class="privacy-marker"><span class="dot"></span> LOCAL ANALYSIS</div><p>Your data stays in this browser. No uploads, no external APIs.</p><div class="sidebar-version">CiteGraph / V1.0 <span>PROJECT 43</span></div></div> | |
| </aside> | |
| <div class="main-shell"><header class="topbar"><div class="mobile-row"><button class="hamburger" id="menuBtn" aria-label="Toggle navigation" aria-expanded="false">☰</button><span class="mobile-brand">✧ CiteGraph</span></div><div class="breadcrumb">RESEARCH LAB <span>/</span> <strong id="breadcrumb">OVERVIEW</strong></div><div class="top-actions"><span class="secure-dot">● OFFLINE</span><button class="btn btn-outline" id="downloadReport">↓ Export report</button></div></header> | |
| <main id="main" tabindex="-1"><section class="page-heading"><div><div class="eyebrow" id="eyebrow">01 / RESEARCH OVERVIEW</div><h1 id="pageTitle">Mapping the structure<br><em>of scientific knowledge.</em></h1><p id="pageSubtitle">Explore research connections, trace citations and reveal intellectual influence with auditable metrics.</p></div><div class="heading-tools"><label class="field-label" for="presetSelect">DEMONSTRATION COLLECTION</label><select id="presetSelect" class="select"><option value="ai">Responsible AI Research</option><option value="climate">Climate Resilience Systems</option><option value="education">Cross-Border Education</option></select><span class="dataset-caption" id="datasetCaption">SYNTHETIC SAMPLE · REPRODUCIBLE</span></div></section> | |
| <div class="content" id="content" aria-live="polite"></div> | |
| <footer class="footer"><span>✧ CITEGRAPH · AUDITABLE SCHOLARLY ANALYTICS</span><span>All demo references are fictional · Internal-corpus metrics only</span></footer></main></div> | |
| </div><div class="toast" role="status" id="toast" aria-live="polite"></div> | |
| <script>/* CiteGraph analytical kernel. Pure, browser and Node compatible. MIT License. */ | |
| (function(root){ | |
| ; | |
| const C={}; | |
| const TEXTMAX=260; | |
| C.safeText=(v,n=TEXTMAX)=>String(v??'').replace(/[\u0000-\u001F\u007F]/g,' ').trim().slice(0,n); | |
| C.csvEscape=v=>{let s=String(v??'');if(/^[\s]*[=+@-]/.test(s))s="'"+s;return /[",\r\n]/.test(s)?'"'+s.replace(/"/g,'""')+'"':s;}; | |
| C.csv=(rows)=>rows.map(r=>r.map(C.csvEscape).join(',')).join('\r\n')+'\r\n'; | |
| C.csvParse=(str)=>{ | |
| if(typeof str!=='string'||str.length>1_500_000)throw Error('CSV missing or larger than 1.5 MB'); | |
| str=str.replace(/^\uFEFF/,'');let rows=[],r=[],v='',quoted=false,postQuote=false; | |
| for(let i=0;i<str.length;i++){ | |
| let c=str[i];if(quoted){if(c==='"'){if(str[i+1]==='"'){v+='"';i++;}else{quoted=false;postQuote=true;}}else{v+=c;}continue;} | |
| if(postQuote){if(c===','){r.push(v);v='';postQuote=false;}else if(c==='\n'||c==='\r'){r.push(v);rows.push(r);r=[];v='';postQuote=false;if(c==='\r'&&str[i+1]==='\n')i++;}else if(c!==' '&&c!=='\t'){throw Error('Unexpected character after closing CSV quote');}continue;} | |
| if(c==='"'){if(v.trim()!=='')throw Error('Quote inside unquoted CSV cell');quoted=true;} | |
| else if(c===','){r.push(v);v='';} | |
| else if(c==='\r'||c==='\n'){r.push(v);rows.push(r);r=[];v='';if(c==='\r'&&str[i+1]==='\n')i++;} | |
| else v+=c; | |
| } | |
| if(quoted)throw Error('Unclosed CSV quote');if(postQuote||v!==''||r.length){r.push(v);rows.push(r);} | |
| rows=rows.filter(row=>row.some(x=>x.trim()!==''));if(rows.length>550)throw Error('Maximum 550 CSV rows');if(rows.length<2)throw Error('CSV requires a header and at least one record');const width=rows[0].length;if(width>30)throw Error('Too many columns');if(rows.some(r=>r.length!==width))throw Error('Inconsistent column count');return rows; | |
| }; | |
| C.toRecords=(text,headers)=>{const a=C.csvParse(text);const keys=a.shift().map(x=>x.trim().toLowerCase());if(keys.some((k,i)=>keys.indexOf(k)!==i))throw Error('Duplicate column names');const miss=headers.filter(h=>!keys.includes(h));if(miss.length)throw Error('Missing column(s): '+miss.join(', '));return a.map(row=>Object.fromEntries(keys.map((k,i)=>[k,row[i]])));}; | |
| C.validate=(papers,citations)=>{ | |
| if(!Array.isArray(papers)||!Array.isArray(citations))throw Error('Dataset requires papers and citations arrays'); | |
| if(papers.length<3||papers.length>120)throw Error('Between 3 and 120 papers required'); | |
| if(citations.length>600)throw Error('Too many citations (max 600)'); | |
| const ids=new Set();const p=papers.map((x,i)=>{ | |
| if(!x||typeof x!=='object')throw Error('Paper '+(i+1)+' is invalid'); | |
| const id=C.safeText(x.id,40);if(!/^[a-zA-Z0-9_.:-]{1,40}$/.test(id))throw Error('Invalid ID at paper '+(i+1));if(ids.has(id))throw Error('Duplicate paper ID: '+id);ids.add(id); | |
| const year=Number(x.year);if(!Number.isInteger(year)||year<1950||year>2035)throw Error('Invalid year for '+id); | |
| const title=C.safeText(x.title,160),topic=C.safeText(x.topic,35),authors=C.safeText(x.authors,120); | |
| if(!title||!topic||!authors)throw Error('Paper '+id+' needs title, topic and authors'); | |
| return {id,year,title,topic,authors}; | |
| }); | |
| const pMap=new Map(p.map(x=>[x.id,x]));let seen=new Set();const e=citations.map((x,i)=>{ | |
| const source=C.safeText(x.source,40),target=C.safeText(x.target,40),key=source+'→'+target; | |
| if(!pMap.has(source)||!pMap.has(target))throw Error('Citation '+(i+1)+' refers to unknown paper'); | |
| if(source===target)throw Error('Self-citations not supported');if(seen.has(key))throw Error('Duplicate citation: '+key);seen.add(key); | |
| if(pMap.get(source).year<pMap.get(target).year)throw Error('Citation from older to newer paper: '+key); | |
| return {source,target}; | |
| }); | |
| return {papers:p,citations:e}; | |
| }; | |
| C.prng=seed=>{let s=seed>>>0;return ()=>((s=Math.imul(1664525,s)+1013904223>>>0)/4294967296);}; | |
| const PRESETS=[ | |
| {key:'ai',name:'Responsible AI Research',subtitle:'Interpretability · robustness · model governance',seed:381, | |
| topics:['Explainability','Evaluation','Robustness','Governance'],titles:[ | |
| 'Interpretable predictions under uncertainty','Evaluating reasoning with multilingual benchmarks','Model audit protocols for deployment','Robust aggregation for collaborative training','Transparent decision frameworks','Faithful feature attribution methods','Trust calibration in adaptive systems','Monitoring language systems for drift','Evaluation of model stability across domains','Governance signals in model documentation','Human oversight in algorithmic triage','Counterfactual explanations with constraints','Reliable model comparison under shift','Auditing datasets for hidden overlap','Reproducible tests for decision systems','Group-aware uncertainty evaluation','Stress testing robustness claims','Longitudinal monitoring of deployed models','Interpretability beyond feature rankings','Responsible deployment assessment','Hybrid auditing of decision pipelines','Verification of model evidence trails']}, | |
| {key:'climate',name:'Climate Resilience Systems',subtitle:'Adaptation · infrastructure · risk · policy',seed:726, | |
| topics:['Climate Risk','Infrastructure','Adaptation','Policy'],titles:[ | |
| 'Mapping compound flood risks in cities','Infrastructure fragility under heat stress','Community adaptation pathway analysis','Evaluating policies for water resilience','Network reliability during extreme weather','Regional drought exposure indices','Uncertainty in urban climate projections','Interdependence of critical infrastructure','Nature-based adaptation under constraints','Risk communication and public decisions','Spatial access under climate disruption','Recovery priorities for resilient services','Comparing early-warning design options','Land use change and flood vulnerability','Impact assessment for coastal adaptation','Quantifying cascade failures in supply chains','Equity dimensions of relocation policies','Resource allocation under climate shocks','Adaptation finance scenario modelling','Participatory resilience metrics','Monitoring green infrastructure outcomes','Evaluating climate risk governance']}, | |
| {key:'education',name:'Cross-Border Education',subtitle:'Mobility · access · language · institutions',seed:1197, | |
| topics:['Mobility','Access','Language','Institutions'],titles:[ | |
| 'Patterns of student mobility across borders','Digital access and educational opportunity','Multilingual learning in regional universities','Institutional cooperation networks','Student trajectories in border regions','Equity in remote higher education','Language practices of mobile learners','Governance of academic credit transfer','Educational aspirations and social networks','Geographical constraints in university choice','Academic writing across multiple languages','Cross-border recognition of qualifications','Evaluating virtual course accessibility','Educational experiences of commuters','Teacher networks across institutions','Language policy and inclusion','Barriers to research participation','Digital platforms in comparative education','Institutional responses to learner mobility','Social belonging among international students','Student support strategies across borders','Comparative access to doctoral programs']} | |
| ]; | |
| C.presets=PRESETS.map(x=>({key:x.key,name:x.name,subtitle:x.subtitle})); | |
| C.makePreset=(key)=>{ | |
| const cfg=PRESETS.find(x=>x.key===key)||PRESETS[0];const rand=C.prng(cfg.seed),papers=[]; | |
| cfg.titles.forEach((title,i)=>{ | |
| const year=2014+Math.floor(i/2);let topic=cfg.topics[(i*3+Math.floor(i/6))%4];papers.push({id:'P'+String(i+1).padStart(2,'0'),title,year,topic,authors:`Research Team ${String.fromCharCode(65+i%12)} · ${String.fromCharCode(70+i%7)}`}); | |
| }); | |
| let citations=[],s=new Set();papers.forEach((p,i)=>{if(!i)return;const max=Math.min(4,1+Math.floor(rand()*4));let options=papers.slice(0,i).filter(q=>q.year<=p.year);options.sort((a,b)=>((a.topic===p.topic?-3:0)+(p.year-a.year)*.32)-((b.topic===p.topic?-3:0)+(p.year-b.year)*.32)||a.id.localeCompare(b.id)); | |
| for(let j=0;j<Math.min(max,options.length);j++){const t=options[j];const k=p.id+'|'+t.id;if(!s.has(k)){s.add(k);citations.push({source:p.id,target:t.id});}} | |
| });return C.validate(papers,citations); | |
| }; | |
| C.analyze=(raw)=>{ | |
| const data=C.validate(raw.papers,raw.citations),p=data.papers,e=data.citations,n=p.length,m=e.length;const index=new Map(p.map((v,i)=>[v.id,i])); | |
| const indeg=Array(n).fill(0),outdeg=Array(n).fill(0),refs=p.map(()=>[]),citedBy=p.map(()=>[]),adj=p.map(()=>new Set()); | |
| for(const edge of e){let a=index.get(edge.source),b=index.get(edge.target);outdeg[a]++;indeg[b]++;refs[a].push(b);citedBy[b].push(a);adj[a].add(b);adj[b].add(a);} | |
| let pr=Array(n).fill(1/n);for(let t=0;t<150;t++){let nx=Array(n).fill((1-.85)/n);let dangling=0;for(let i=0;i<n;i++){if(!outdeg[i])dangling+=pr[i];else for(const j of refs[i])nx[j]+=.85*pr[i]/outdeg[i];}for(let i=0;i<n;i++)nx[i]+=.85*dangling/n;let d=nx.reduce((s,x,i)=>s+Math.abs(x-pr[i]),0);pr=nx;if(d<1e-12)break;} | |
| let components=[];const seen=new Set();for(let i=0;i<n;i++){if(seen.has(i))continue;let queue=[i],members=[];seen.add(i);while(queue.length){let u=queue.pop();members.push(u);for(let j of adj[u])if(!seen.has(j)){seen.add(j);queue.push(j);}}components.push(members);} | |
| const topics=[...new Set(p.map(x=>x.topic))].sort(),topicMap=Object.fromEntries(topics.map(topic=>[topic,{topic,papers:0,citations:0,authors:new Set(),ids:[]} ])); | |
| for(let i=0;i<n;i++){const o=topicMap[p[i].topic];o.papers++;o.citations+=indeg[i];o.ids.push(i);p[i].authors.split(/[;,·]/).map(a=>a.trim()).filter(Boolean).forEach(a=>o.authors.add(a));} | |
| const years=[...new Set(p.map(x=>x.year))].sort((a,b)=>a-b).map(y=>({year:y,papers:p.filter(x=>x.year===y).length,citations:e.filter(x=>p[index.get(x.source)].year===y).length})); | |
| const ranked=p.map((paper,i)=>({...paper,incoming:indeg[i],outgoing:outdeg[i],rank:pr[i],share:pr[i]*100})).sort((a,b)=>b.rank-a.rank||b.incoming-a.incoming); | |
| const ins=indeg.slice().sort((a,b)=>b-a),h=ins.reduce((h,x,i)=>x>=i+1?i+1:h,0); | |
| const pairList=[];for(let i=0;i<n;i++)for(let j=i+1;j<n;j++){const a=new Set(refs[i]),b=new Set(refs[j]);const shared=[...a].filter(v=>b.has(v)).length;if(shared>=2)pairList.push({a:p[i].id,b:p[j].id,shared,jaccard:shared/(a.size+b.size-shared)});} | |
| pairList.sort((a,b)=>b.shared-a.shared||b.jaccard-a.jaccard); | |
| const coCit=[];for(let i=0;i<n;i++)for(let j=i+1;j<n;j++){const a=new Set(citedBy[i]),b=new Set(citedBy[j]);const shared=[...a].filter(v=>b.has(v)).length;if(shared>=2)coCit.push({a:p[i].id,b:p[j].id,shared});}coCit.sort((a,b)=>b.shared-a.shared); | |
| let concentration=0;if(m){const vals=indeg.slice().sort((a,b)=>a-b);let total=vals.reduce((a,b)=>a+b,0);concentration=vals.reduce((acc,x,i)=>acc+(2*(i+1)-n-1)*x,0)/(n*total);} | |
| const audit=[];if(n<15)audit.push('Small graph: rankings may be unstable.');if(m<n)audit.push('Sparse graph: many papers may have no outgoing or incoming citations.');if(p.filter((x,i)=>indeg[i]===0).length/n>.5)audit.push('More than half the papers have no incoming citations in this dataset.');if(components.length>1)audit.push(components.length+' disconnected groups may limit comparisons.');if(Math.max(...indeg)/Math.max(1,m)>.4)audit.push('One publication receives more than 40% of citations.'); | |
| audit.push('Citations count only links within this uploaded corpus, not worldwide influence.');audit.push('Topic clusters are assigned from metadata, not inferred scientifically.'); | |
| return {data,index,indeg,outdeg,refs,citedBy,adj,pr,ranked,topics,topicMap,years,components,pairList,coCit,h,concentration,audit,metrics:{papers:n,citations:m,topics:topics.length,coverage:indeg.filter(x=>x>0).length/n,density:m/(n*(n-1)),largest:Math.max(...components.map(x=>x.length))/n,mean: m/n, maxIn:Math.max(...indeg)}}; | |
| }; | |
| C.focus=(analysis,id)=>{const i=analysis.index.get(id);if(i===undefined)return null;let p=analysis.data.papers[i];return {...p,incoming:analysis.indeg[i],outgoing:analysis.outdeg[i],pageRank:analysis.pr[i],references:analysis.refs[i].map(j=>analysis.data.papers[j]),citedBy:analysis.citedBy[i].map(j=>analysis.data.papers[j])};}; | |
| C.filter=(an,{topic='All topics',from=1950,to=2035}={})=>an.data.papers.filter(p=>(topic==='All topics'||p.topic===topic)&&p.year>=from&&p.year<=to); | |
| C.exportCSV=an=>C.csv([['id','title','year','topic','authors','internal_citations','pagerank'],...an.data.papers.map((p,i)=>[p.id,p.title,p.year,p.topic,p.authors,an.indeg[i],an.pr[i].toFixed(9)])]); | |
| C.citationCSV=an=>C.csv([['source','target'],...an.data.citations.map(e=>[e.source,e.target])]); | |
| C.examples={papers:C.csv([['id','title','year','topic','authors'],['A1','A research paper',2021,'Methods','A. Author'],['A2','Further investigation',2022,'Methods','B. Author'],['A3','New evidence',2023,'Practice','C. Author']]),citations:C.csv([['source','target'],['A2','A1'],['A3','A1'],['A3','A2']])}; | |
| C.parseDataset=(paperCSV,citationCSV)=>C.validate(C.toRecords(paperCSV,['id','title','year','topic','authors']),C.toRecords(citationCSV,['source','target'])); | |
| if(typeof module!=='undefined'&&module.exports)module.exports=C;root.CGCore=C; | |
| })(typeof window!=='undefined'?window:globalThis); | |
| </script><script> | |
| </script> | |
| </body></html> | |