diff --git a/public/index.html b/public/index.html
index ac9701b..d0030ca 100644
--- a/public/index.html
+++ b/public/index.html
@@ -108,6 +108,10 @@
.pill{padding:0.4rem 0.85rem;border:1px solid var(--border);border-radius:100px;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:0.75rem;font-weight:500;font-family:'Inter',sans-serif;transition:all 0.15s;white-space:nowrap}
.pill:hover{border-color:var(--border-light);color:var(--text)}
.pill.active{background:var(--text);color:var(--bg);border-color:var(--text)}
+.scorecard-actions{display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;justify-content:flex-end}
+.view-link-button{padding:0.42rem 0.85rem;border:1px solid rgba(247,147,26,0.25);border-radius:var(--radius-xs);background:rgba(247,147,26,0.08);color:var(--accent);cursor:pointer;font-size:0.75rem;font-weight:600;font-family:'Inter',sans-serif;transition:all 0.15s;white-space:nowrap}
+.view-link-button:hover{border-color:rgba(247,147,26,0.45);background:rgba(247,147,26,0.14);color:#fbbf24}
+.copy-status{min-width:3.5rem;font-size:0.72rem;color:var(--text-muted)}
/* Table */
.table-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
@@ -176,6 +180,7 @@
th,td{padding:0.5rem 0.6rem}
.td-summary{max-width:200px;font-size:0.75rem}
.method-grid{grid-template-columns:1fr}
+ .scorecard-actions{justify-content:flex-start;width:100%}
}
/* Search */
@@ -318,7 +323,11 @@
@@ -460,11 +469,43 @@
Ecosystem
const total=data.metadata.total_assessed;
const qri=data.metadata.quantum_readiness_index;
+function levelForScore(value){
+if(value<20)return 'Asleep';
+if(value<40)return 'Sleepwalking';
+if(value<60)return 'Awakening';
+if(value<80)return 'Mobilizing';
+return 'Ready';
+}
+
+function normalizeReadiness(raw){
+if(raw&&typeof raw==='object'&&raw.composite_readiness&&raw.voiced_urgency&&raw.coverage)return raw;
+const silent=dist['1_no_known_view']||0;
+const voicedCount=Math.max(0,total-silent);
+const weighted=(dist['5_urgent']||0)*5+(dist['4_proactive']||0)*4+(dist['3_cautious']||0)*3+(dist['2_dismissive']||0)*2;
+const voicedScore=voicedCount?Math.round(weighted/(voicedCount*5)*100):0;
+const coverageScore=total?Math.round(voicedCount/total*100):0;
+let compositeScore=Math.round(voicedScore*coverageScore/100);
+if(typeof raw==='number'&&Number.isFinite(raw))compositeScore=Math.round(raw<=1?raw*100:raw);
+const currentLevel=levelForScore(compositeScore);
+return {
+composite_readiness:{
+score:compositeScore,
+current_level:currentLevel,
+formula:typeof raw==='number'
+?'Index loaded from metadata. Voiced urgency and coverage panels are derived from the current score distribution.'
+:'Voiced urgency multiplied by coverage. Derived from the current score distribution because no structured index object was present.'
+},
+voiced_urgency:{score:voicedScore,voices_count:voicedCount},
+coverage:{score:coverageScore,silent,total}
+};
+}
+
// Readiness Index
const indexEl=document.getElementById('index-card');
-const comp=qri.composite_readiness;
-const voiced=qri.voiced_urgency;
-const coverage=qri.coverage;
+const normalizedQri=normalizeReadiness(qri);
+const comp=normalizedQri.composite_readiness;
+const voiced=normalizedQri.voiced_urgency;
+const coverage=normalizedQri.coverage;
const score=comp.score;
const circumference=2*Math.PI*65;
const offset=circumference-(score/100)*circumference;
@@ -530,10 +571,12 @@ Ecosystem
});
// State
-let activeScore='all';
-let activeAffiliation='all';
-let searchQuery='';
-let activeDevName=null;
+const urlState=new URLSearchParams(window.location.search);
+const validScores=new Set(scores.map(s=>String(s.score)));
+let activeScore=validScores.has(urlState.get('score'))?urlState.get('score'):'all';
+let activeAffiliation=urlState.get('affiliation')||'all';
+let searchQuery=urlState.get('q')||'';
+let activeDevName=urlState.get('dev')||null;
// Affiliation dropdown — populate from data
const affilSelect=document.getElementById('affil-select');
@@ -545,6 +588,45 @@ Ecosystem
opt.textContent=`${a} (${affilCounts[a]})`;
affilSelect.appendChild(opt);
});
+if(activeAffiliation!=='all'&&!affilCounts[activeAffiliation])activeAffiliation='all';
+
+function findDevByName(name){
+if(!name)return null;
+const needle=String(name).toLowerCase();
+return devs.find(d=>d.name&&d.name.toLowerCase()===needle)||null;
+}
+
+function updateUrlState(){
+const params=new URLSearchParams();
+if(activeScore!=='all')params.set('score',activeScore);
+if(activeAffiliation!=='all')params.set('affiliation',activeAffiliation);
+if(searchQuery.trim())params.set('q',searchQuery.trim());
+if(activeDevName)params.set('dev',activeDevName);
+const query=params.toString();
+const nextUrl=query?`${window.location.pathname}?${query}`:window.location.pathname;
+window.history.replaceState(null,'',nextUrl);
+}
+
+function copyText(text){
+if(navigator.clipboard&&window.isSecureContext)return navigator.clipboard.writeText(text);
+const textarea=document.createElement('textarea');
+textarea.value=text;
+textarea.setAttribute('readonly','');
+textarea.style.position='fixed';
+textarea.style.left='-9999px';
+document.body.appendChild(textarea);
+textarea.select();
+const ok=document.execCommand('copy');
+document.body.removeChild(textarea);
+return ok?Promise.resolve():Promise.reject(new Error('copy failed'));
+}
+
+function syncControlsFromState(){
+document.querySelectorAll('.pill').forEach(btn=>btn.classList.toggle('active',btn.dataset.score===activeScore));
+document.querySelectorAll('.stat-cell').forEach(cell=>cell.classList.toggle('active',activeScore!=='all'&&cell.dataset.score===activeScore));
+document.getElementById('search').value=searchQuery;
+affilSelect.value=activeAffiliation;
+}
function escapeHtml(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);}
@@ -585,14 +667,9 @@ Ecosystem
// Filter clicks (pills)
document.querySelectorAll('.pill').forEach(btn=>{
btn.addEventListener('click',()=>{
-document.querySelectorAll('.pill').forEach(b=>b.classList.remove('active'));
-btn.classList.add('active');
activeScore=btn.dataset.score;
-// sync stat cells
-document.querySelectorAll('.stat-cell').forEach(c=>c.classList.remove('active'));
-if(activeScore!=='all'){
-document.querySelector(`.stat-cell[data-score="${activeScore}"]`)?.classList.add('active');
-}
+syncControlsFromState();
+updateUrlState();
render();
});
});
@@ -601,9 +678,9 @@ Ecosystem
document.querySelectorAll('.stat-cell').forEach(cell=>{
cell.addEventListener('click',()=>{
const score=cell.dataset.score;
-if(activeScore===score){activeScore='all';cell.classList.remove('active');}
-else{activeScore=score;document.querySelectorAll('.stat-cell').forEach(c=>c.classList.remove('active'));cell.classList.add('active');}
-document.querySelectorAll('.pill').forEach(b=>{b.classList.remove('active');if(b.dataset.score===activeScore)b.classList.add('active');});
+activeScore=activeScore===score?'all':score;
+syncControlsFromState();
+updateUrlState();
render();
});
});
@@ -611,15 +688,31 @@ Ecosystem
// Search
document.getElementById('search').addEventListener('input',e=>{
searchQuery=e.target.value;
+updateUrlState();
render();
});
// Affiliation filter
affilSelect.addEventListener('change',e=>{
activeAffiliation=e.target.value;
+updateUrlState();
render();
});
+const copyViewBtn=document.getElementById('copy-view');
+const copyStatus=document.getElementById('copy-status');
+copyViewBtn.addEventListener('click',async()=>{
+updateUrlState();
+try{
+await copyText(window.location.href);
+copyStatus.textContent='Copied';
+setTimeout(()=>{copyStatus.textContent='';},1800);
+}catch{
+copyStatus.textContent='Copy failed';
+setTimeout(()=>{copyStatus.textContent='';},2200);
+}
+});
+
// === DETAIL DRAWER ===
const drawer=document.getElementById('drawer');
const drawerBackdrop=document.getElementById('drawer-backdrop');
@@ -628,6 +721,7 @@ Ecosystem
function openDrawer(dev){
activeDevName=dev.name;
+updateUrlState();
const scoreLabelMap={5:'Urgent',4:'Proactive',3:'Cautious',2:'Dismissive',1:'No View'};
document.getElementById('drawer-meta').textContent=dev.notable?'Notable Contributor':`Influence Rank #${dev.rank}`;
document.getElementById('drawer-name').textContent=dev.name;
@@ -667,6 +761,7 @@ Ecosystem
function closeDrawer(){
activeDevName=null;
+updateUrlState();
drawer.classList.remove('is-open');
drawerBackdrop.classList.remove('is-open');
drawer.setAttribute('aria-hidden','true');
@@ -683,7 +778,10 @@ Ecosystem
drawer.setAttribute('aria-hidden','true');
drawerBackdrop.setAttribute('aria-hidden','true');
+syncControlsFromState();
render();
+const initialDev=findDevByName(activeDevName);
+if(initialDev)openDrawer(initialDev);
// === BUBBLE CHART ===
const canvas = document.getElementById('bubble-chart');