javascript:(async()=>{const t=location.href.match(/^https:\/\/www\.rollspel\.nu\/threads\/([^/?#]+?\.\d+)(?:[/?#]|$)/i);if(!t)return alert("Öppna bookmarkleten i en tråd på rollspel.nu.");const e=`https://www.rollspel.nu/threads/${t[1]}/`,a=/\bröst\s*:\s*/i,l=new DOMParser,n=[],r=[],o=t=>document.querySelector(t),i=t=>[...document.querySelectorAll(t)],s=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),c=t=>t?new Date(t).toLocaleString("sv-SE",{dateStyle:"short",timeStyle:"short"}):"–",d=t=>t.slice().sort((t,e)=>+new Date(t.ts)-+new Date(e.ts)||Number(t.post||0)-Number(e.post||0)),u=t=>{const e=new Map;return d(t).forEach(t=>e.set(t.from,t)),[...e.values()]},m=t=>`"${String(t??"").replace(/"/g,'""')}"`;try{for(let t=1;t<=500;t++){const a=await fetch(1===t?e:`${e}page-${t}`,{credentials:"include",cache:"no-store"});if(!a.ok)throw Error(`Sida ${t} gav HTTP ${a.status}`);const n=l.parseFromString(await a.text(),"text/html");r.push([t,n]);if(t>=Number(n.querySelector("input.js-pageJumpPage")?.max||1)&&!n.querySelector(".pageNav-jump--next"))break}}catch(t){return alert("Kunde inte läsa tråden: "+t.message)}const p=t=>t.replace(/<br\s*\/?\s*>|<\/p\s*>|<\/li\s*>/gi,"\n").split("\n").map(t=>t.trim()).filter(Boolean),f=t=>{const e=document.createElement("div");e.innerHTML=t;const l=document.createTreeWalker(e,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT);let n=!1,r="";for(;l.nextNode();){const t=l.currentNode;if(3===t.nodeType)r+=t.nodeValue||"",!n&&a.test(r)&&(n=!0),r.length>300&&(r=r.slice(-300));else if(n&&1===t.nodeType){const e=t.getAttribute?.("data-username");if(e)return e.replace(/^@/,"").trim()}}return""};for(const[t,e]of r)[...e.querySelectorAll("article[data-author]")].forEach((e,l)=>{if(1===t&&0===l)return;const r=(e.getAttribute("data-author")||"").trim(),o=(e.id||"").replace("js-post-","").trim(),i=e.querySelector("time.u-dt")?.getAttribute("datetime")||"";let s=e.querySelector(".message-content");if(r&&o&&s){s=s.cloneNode(!0),s.querySelectorAll("blockquote,.bbCodeBlock--quote,.bbCodeBlock--unfurl,[data-unfurl='true']").forEach(t=>t.remove());for(const e of p(s.innerHTML)){const l=document.createElement("div");if(l.innerHTML=e,!a.test((l.textContent||"").replace(/\s+/g," ").trim()))continue;const s=f(e);s&&n.push({from:r,to:s,ts:i,post:o,page:t})}}});n.splice(0,n.length,...d(n)),document.title="Varulv Rösträknare",document.head.innerHTML='<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>body{font-family:Arial,sans-serif;margin:2em;background:#f9f9f9;color:#333}label,button,select,input{margin:8px 10px 8px 0}select,button,input{padding:6px}button{cursor:pointer}table{border-collapse:collapse;width:100%;margin-top:12px;background:#fff;border:1px solid #ccc}th,td{padding:8px 10px;border:1px solid #ccc;text-align:center;vertical-align:top}th{background:#3c8dbc;color:#fff;cursor:pointer}.summary{font-weight:bold;font-size:18px;margin:12px 0}.row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:8px 0}.small{font-size:12px;opacity:.8}details{margin:12px 0;background:#fff;border:1px solid #ccc;padding:10px}summary{font-weight:bold;cursor:pointer}.manual{display:flex;flex-wrap:wrap;gap:8px 16px}.manual label{display:inline-flex;align-items:center;gap:6px;margin:0}.manual input{margin:0}#chart{max-width:620px;width:100%;height:auto}#slider{padding:0;min-width:260px}@media(max-width:700px){body{margin:10px}th,td{padding:5px;font-size:12px}}</style>',document.body.innerHTML=`<h1>🐺 Varulv Rösträknare</h1><div class="small">Källa: <a target="_blank" rel="noopener" href="${s(e)}">${s(e)}</a></div><div id="status" class="small"></div><button id="exportBtn">Exportera CSV</button><details open><summary>Inställningar</summary><label><input type="radio" name="view" value="latest" checked> Endast senaste röst</label><label><input type="radio" name="view" value="all"> Alla röster</label><br><label>Filter per spelare <select id="filter"></select></label><label>Färger <select id="colorMode"><option value="auto">Auto</option><option value="bw">Svartvitt</option><option value="manual">Manuellt</option></select></label><div id="manual" class="manual" hidden></div><div class="row"><button id="play">▶ Animera röster</button><label>Hastighet <input id="delay" type="number" value="200" min="0" style="width:65px"> ms</label></div><div class="row"><label for="slider">Visa röster fram till:</label><input id="slider" type="range" min="0" max="0" value="0" list="sliderTicks"><datalist id="sliderTicks"></datalist><span id="sliderLabel">–</span></div></details><div id="summary" class="summary"></div><canvas id="chart" width="620" height="260"></canvas><h2>Röster</h2><table id="table"><thead><tr><th data-sort="from">Röstgivare</th><th>Röst</th><th data-sort="ts">Tidpunkt</th><th>Riskerar att åka ut</th><th>Därefter</th></tr></thead><tbody></tbody></table>`;const h={votes:n,players:[],colors:{},manual:{},colorMode:"auto",filter:"",sort:"",index:null,limit:null,timeline:[],timer:null},g={status:o("#status"),exp:o("#exportBtn"),views:i('input[name="view"]'),filter:o("#filter"),colorMode:o("#colorMode"),manual:o("#manual"),play:o("#play"),delay:o("#delay"),slider:o("#slider"),ticks:o("#sliderTicks"),sliderLabel:o("#sliderLabel"),summary:o("#summary"),tbody:o("#table tbody"),ths:i("#table th"),canvas:o("#chart")},b=()=>g.views.find(t=>t.checked)?.value||"latest",x=t=>"bw"===h.colorMode?"#000":"manual"===h.colorMode?h.manual[t]||h.colors[t]||"#000":h.colors[t]||"#000";function y(){h.players=[...new Set(h.votes.flatMap(t=>[t.from,t.to]))];const t=h.players.slice().sort((t,e)=>t.localeCompare(e,"sv"));h.colors={},t.forEach((e,a)=>h.colors[e]=`hsl(${Math.round(360*a/Math.max(1,t.length))},70%,42%)`),g.filter.innerHTML='<option value="">Alla</option>',t.forEach(t=>{const e=document.createElement("option");e.value=e.textContent=t,e.style.color=x(t),e.style.fontWeight="bold",g.filter.append(e)}),h.players.includes(h.filter)||(h.filter=""),g.filter.value=h.filter,g.manual.hidden="manual"!==h.colorMode,g.manual.innerHTML="","manual"===h.colorMode&&t.forEach(t=>{h.manual[t]||=h.colors[t];const e=document.createElement("label"),a=document.createElement("span"),l=document.createElement("input");a.textContent=t,l.type="color",l.dataset.player=t,l.value=h.manual[t],e.append(a,l),g.manual.append(e)}),g.status.textContent=`${n.length} taggröster, ${r.length} sidor lästa.`}function v(){h.timeline=d(h.votes.filter(t=>t.ts&&!isNaN(+new Date(t.ts)))).map(t=>new Date(t.ts)),g.ticks.innerHTML="",h.timeline.forEach((t,e)=>{const a=document.createElement("option");a.value=String(e),g.ticks.append(a)});const t=Math.max(0,h.timeline.length-1);g.slider.max=String(t),(null==h.index||h.index>t)&&(h.index=t),h.index<0&&(h.index=0),g.slider.value=String(h.index),h.limit=h.timeline.length?h.timeline[h.index]:null,g.sliderLabel.textContent=h.timeline.length?`${h.index+1}/${h.timeline.length} röster (${c(h.limit)})`:"–",$()}function w(){let t=h.votes;return h.limit&&(t=t.filter(t=>+new Date(t.ts)<=+h.limit)),"latest"===b()&&(t=u(t)),h.filter&&(t=t.filter(t=>t.from===h.filter)),t}function M(t){const e=g.canvas.getContext("2d"),a=g.canvas.width,l=g.canvas.height;if(e.clearRect(0,0,a,l),!t.length)return;const n=180,r=Math.max(14,Math.floor((l-20)/t.length)-3),o=Math.max(...t.map(t=>t[1]));e.font="16px Arial",t.forEach((t,l)=>{const i=10+l*(r+3),s=Math.floor((a-n-10)*t[1]/o),c=String(t[1]);e.fillStyle=x(t[0]),e.fillText(t[0],10,i+r-2),e.fillRect(n,i,s,r),e.fillStyle="#fff",e.fillText(c,Math.max(184,n+s-e.measureText(c).width-4),i+r-2)})}function $(t=null,a=null){const l=d(null!==a?a:h.votes.filter(t=>!h.limit||+new Date(t.ts)<=+h.limit)),n=null!==t?t:w(),r=u(l),o=new Map,m=new Map;d(r).forEach(t=>{o.set(t.to,(o.get(t.to)||0)+1),(!m.has(t.to)||+new Date(t.ts)<+new Date(m.get(t.to)))&&m.set(t.to,t.ts)});const p=[...o.entries()].sort((t,e)=>e[1]-t[1]||+new Date(m.get(t[0])||0)-+new Date(m.get(e[0])||0));if(!p.length)return g.summary.textContent="Inga röster att visa.",g.tbody.innerHTML="",void M([]);const[f,b]=p[0],y=r.reduce((t,e)=>!t||+new Date(e.ts)>+new Date(t)?e.ts:t,null);g.summary.textContent=`⚠️ Risk för utröstning: ${f} (${b} röster, sedan ${c(m.get(f))}). Senast röst lagd ${c(y)}.`,g.tbody.innerHTML="";const v=new Map;d(n).forEach(t=>{const a=function(t,e){const a=new Map,l=new Map,n=new Map;return t.filter(t=>{const a=+new Date(t.ts),l=+new Date(e.ts);return a<l||a===l&&Number(t.post||0)<=Number(e.post||0)}).forEach(t=>{const e=a.get(t.from);if(e){const a=(l.get(e)||0)-1;a<=0?(l.delete(e),n.delete(e)):(l.set(e,a),n.set(e,{ts:t.ts,post:Number(t.post||0)}))}a.set(t.from,t.to),l.set(t.to,(l.get(t.to)||0)+1),n.set(t.to,{ts:t.ts,post:Number(t.post||0)})}),[...l.entries()].sort((t,e)=>e[1]-t[1]||+new Date(n.get(t[0])?.ts||0)-+new Date(n.get(e[0])?.ts||0)||(n.get(t[0])?.post||0)-(n.get(e[0])?.post||0))}(l,t),n=a[0]?`${a[0][0]} (${a[0][1]})`:"–",r=a[1]?`${a[1][0]} (${a[1][1]})`:"–",o=v.get(t.from)||[];o.at(-1)!==t.to&&o.push(t.to),v.set(t.from,o);const i=o.map((a,l,n)=>l===n.length-1?`<a target="_blank" rel="noopener" href="${s(e+"post-"+t.post)}" style="color:${x(a)};font-weight:bold">${s(a)}</a>`:`<span style="color:${x(a)}">${s(a)}</span>`).join(" → "),d=document.createElement("tr");d.dataset.ts=t.ts||"",d.innerHTML=`<td style="color:${x(t.from)};font-weight:bold">${s(t.from)}</td><td>${i}</td><td>${c(t.ts)}</td><td>${s(n)}</td><td>${s(r)}</td>`,g.tbody.append(d)}),function(){if(!h.sort)return;const[t,e]=h.sort.split("-"),a=i("#table tbody tr");a.sort((a,l)=>{const n="from"===t?a.children[0].textContent.trim():a.dataset.ts||"",r="from"===t?l.children[0].textContent.trim():l.dataset.ts||"",o="from"===t?n.localeCompare(r,"sv"):n.localeCompare(r);return"desc"===e?-o:o}),g.tbody.replaceChildren(...a)}(),M(p)}function k(){h.timer&&(clearTimeout(h.timer),h.timer=null),g.play.disabled=!1}g.exp.onclick=()=>{const t=document.createElement("a"),e=["Röstgivare,Röst,Tidpunkt,Post,Page",...w().map(t=>[t.from,t.to,t.ts,t.post,t.page].map(m).join(","))].join("\n"),a=URL.createObjectURL(new Blob(["\ufeff"+e],{type:"text/csv;charset=utf-8"}));t.href=a,t.download="rostdata.csv",t.click(),setTimeout(()=>URL.revokeObjectURL(a),1e3)},g.views.forEach(t=>t.onchange=()=>{k(),h.index=null,v()}),g.filter.onchange=()=>{k(),h.filter=g.filter.value,h.index=null,v()},g.colorMode.onchange=()=>{h.colorMode=g.colorMode.value,y(),$()},g.manual.oninput=t=>{"color"===t.target.type&&(h.manual[t.target.dataset.player]=t.target.value,y(),$())},g.slider.oninput=()=>{k(),h.index=Number(g.slider.value),h.limit=h.timeline[h.index]||null,g.sliderLabel.textContent=h.timeline.length?`${h.index+1}/${h.timeline.length} röster (${c(h.limit)})`:"–",$()},g.play.onclick=()=>{k(),g.play.disabled=!0;const t=d(h.votes.filter(t=>!h.limit||+new Date(t.ts)<=+h.limit));let e=0;const a=Math.max(0,Number(g.delay.value)||200);!function l(){if(e>t.length)return k(),void $();const n=t.slice(0,e);let r="all"===b()?n:u(n);h.filter&&(r=r.filter(t=>t.from===h.filter)),$(r,n),e++,h.timer=setTimeout(l,a)}()},g.ths.forEach(t=>{const e=t.dataset.sort;e&&(t.onclick=()=>{h.sort=e+"-"+(h.sort===e+"-asc"?"desc":"asc"),$()})}),y(),v()})();