WikiSpeedrun by Henry Bao, MIT. Original scoring in rules.js retained unchanged. Other UI and 59 compact articles written for this static edition. No copied Wikipedia content. Adaptation code and article text are also MIT licensed, Copyright 2026 Linkbound contributors.
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#f4f0e6"><title>Linkbound · Pocket Speedrun</title><link rel="stylesheet" href="./style.css"><script type="module" src="./app.js"></script></head><body><header><a class="brand" href="./index.html"><span class="logo">↗</span> LINKBOUND<span class="edition">POCKET EDITION / 01</span></a><button id="guide">Field guide & credits</button></header><main id="app"><p>Opening the pocket encyclopedia…</p></main><footer>A small encyclopedia. A surprisingly long way round. <span>59 articles · all on your device</span></footer><dialog id="dialog"><button id="closeDialog" class="close" aria-label="Close guide">×</button><h2>Follow your curiosity.</h2><p>Race from an opening article to a destination using only the underlined links in each article. You cannot search or jump straight to a title. Links are numbered: press 1–5 or use the pointer. Tab and Enter also work.</p><p><b>Trail:</b> find one destination. <b>Three stops:</b> visit all three marked destinations in any order. The timer measures active playing time; Pause and returning to the reading room stop it. Unfinished runs resume on this device.</p><p><b>Marathon:</b> chain targets for three minutes. Targets start at least two links away. Original WikiSpeedrun scoring: 25 / 45 / 80 points for targets starting 2 / 3 / 4+ links away, minus one per click on completed targets, minus five per skip, plus 30 / 60 / 100 bonus for 5 / 10 / 15 completions. Unfinished target clicks do not count. Hint runs are marked assisted and never replace unassisted records.</p><p>Hints point out the first link on a shortest route. Route review appears after a finish. Your records stay in this browser; they are not competitive online scores.</p><h3>Source & adaptation</h3><p>Based on Henry Bao’s WikiSpeedrun (MIT, 166 GitHub stars observed October 30, 2026). The original marathon scoring is used unchanged. Classic and Tri retain the hyperlink-navigation rules. The server, online rooms and live Wikipedia calls are replaced by a closed collection of 59 original, short articles. This is a compact knowledge game, not a mirror of Wikipedia. Articles and links were authored for this edition; no Wikipedia article text or images are included.</p><p class="source">https://github.com/HenryBao912/WikiSpeedrun<br>Commit bf65019bf78d4a598bc1b4d376a5930e2464cc9a<br>Upstream last pushed October 26, 2026.</p><p><a href="./LICENSE.txt">MIT license</a> · <a href="./source.html">Readable application source</a> · <a href="./metadata.json">Source metadata</a></p></dialog></body></html>
:root{--ink:#203a39;--muted:#667572;--paper:#f4f0e6;--line:#d8dcd0;--accent:#b7e585;--orange:#bc522d;--white:#fffef8}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:16px/1.6 ui-sans-serif,system-ui,-apple-system,sans-serif}button,select{font:inherit}button,a,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;background:transparent;border:1px solid var(--line);border-radius:7px;padding:10px 17px;color:var(--ink);font-weight:650}button:hover{background:#e7eddf;border-color:#829784}button:disabled{opacity:.45;cursor:default}button:focus-visible,a:focus-visible,select:focus-visible{outline:3px solid var(--orange);outline-offset:4px}.primary{background:var(--ink);color:var(--paper);border-color:var(--ink)}.primary:hover{background:#345954;color:white}header{max-width:1280px;padding:24px 36px;margin:auto;display:flex;align-items:center;justify-content:space-between;gap:18px;border-bottom:1px solid var(--line)}a{color:inherit}.brand{display:flex;align-items:center;gap:12px;font-size:15px;font-weight:850;letter-spacing:2px;text-decoration:none}.logo{font:32px/1 Georgia;background:var(--ink);color:var(--accent);padding:4px 10px;border-radius:50%}.edition{font:10px/1.5 ui-monospace,monospace;letter-spacing:1px;border-left:1px solid #b3bbb0;margin-left:8px;padding-left:18px;color:var(--muted)}main{max-width:1210px;padding:42px 36px 60px;margin:auto;min-height:72vh}.eyebrow{font:11px/1.5 ui-monospace,monospace;letter-spacing:2px;text-transform:uppercase;color:var(--muted)}h1,h2,h3,p{margin-top:0}h1,h2{font-family:Georgia,'Times New Roman',serif;font-weight:400;letter-spacing:-2px}h1{font-size:clamp(48px,6vw,79px);line-height:1.03;margin:16px 0 24px}h2{font-size:43px;line-height:1.1;margin-bottom:20px}.intro{max-width:570px;color:var(--muted);font-size:18px}.hero{display:grid;grid-template-columns:1fr 380px;gap:50px;align-items:center;padding-bottom:40px}.art{height:276px;position:relative;border:1px solid var(--line);border-radius:50%;background:repeating-radial-gradient(ellipse at center,transparent 0,transparent 39px,#dce0d1 40px,#dce0d1 41px,transparent 42px);display:grid;place-content:center}.art .orb{width:104px;height:104px;border-radius:50%;background:var(--accent);display:grid;place-content:center;font:50px Georgia;border:1px solid #9bbf77}.art .tag{position:absolute;background:var(--white);border:1px solid var(--line);padding:4px 12px;font-size:12px;box-shadow:3px 5px 0 #203a3909}.art .tag:nth-child(2){top:20px;left:12px;transform:rotate(-8deg)}.art .tag:nth-child(3){bottom:27px;right:-10px;transform:rotate(7deg)}.art .tag:nth-child(4){bottom:16px;left:10px;transform:rotate(-5deg)}.sectionhead{display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--line);padding-top:24px;margin-bottom:19px}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}.card{background:var(--white);border:1px solid var(--line);padding:25px;border-radius:9px;display:flex;flex-direction:column;align-items:flex-start}.card h3{font:29px Georgia;margin:12px 0}.card p{color:var(--muted);font-size:14px;flex:1}.card label{width:100%;font-size:12px}.card select{margin:6px 0 19px;padding:9px 7px;background:var(--paper);border:1px solid var(--line);border-radius:5px;max-width:100%;width:100%;font-size:13px;color:var(--ink)}.card button{width:100%}.num{font:11px ui-monospace;color:var(--orange)}.resume{background:#e0edcf;padding:15px 20px;border-radius:7px;display:flex;align-items:center;gap:14px;justify-content:space-between;margin-bottom:26px}.runbar{display:flex;justify-content:space-between;gap:10px;align-items:center;margin-bottom:24px}.runbar .tools{display:flex;gap:7px}.runbar button{font-size:12px;padding:7px 11px}.layout{display:grid;grid-template-columns:270px minmax(0,1fr);gap:40px}.mission{border-right:1px solid var(--line);padding-right:26px}.target{font:30px/1.15 Georgia;margin:15px 0 8px;letter-spacing:-.7px}.target.done{color:#6e8960;text-decoration:line-through}.stats{display:flex;gap:25px;margin:24px 0}.stats strong{display:block;font:28px ui-monospace,monospace;letter-spacing:-1px}.stats small{font-size:10px;letter-spacing:2px;color:var(--muted)}.note{color:var(--muted);font-size:12px}.article{background:var(--white);border:1px solid var(--line);border-radius:10px;padding:32px 38px;box-shadow:0 7px 20px #203a3905;min-height:400px}.article h1{font-size:57px;margin:24px 0}.article .prose{font:22px/1.95 Georgia,serif;color:#3b4d48}.article button.link{font:inherit;color:#345f4b;padding:0 2px;border:0;border-bottom:1px solid #6d9778;border-radius:0;line-height:1.5;background:#edf3df}.article button.link:hover{background:var(--accent)}.article .link small{font:10px system-ui;vertical-align:super;color:#6d7f69;margin-left:4px}.article .link.hint{background:#ffe0a3;outline:2px solid var(--orange)}.article-end{border-top:1px solid var(--line);margin-top:24px;padding-top:15px;display:flex;justify-content:space-between;gap:10px;font:10px ui-monospace;color:var(--muted);letter-spacing:1px}.breadcrumbs{margin-top:22px;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.breadcrumbs span{color:var(--orange);padding:0 6px}.status{min-height:27px;color:var(--orange);font-size:13px}.result{max-width:760px;margin:20px auto}.result h1{font-size:68px}.result .big{font:60px ui-monospace;margin:20px 0}.result .route{padding:20px;background:var(--white);border:1px solid var(--line);margin:15px 0;border-radius:8px;overflow-wrap:anywhere}.result .actions{display:flex;gap:10px;margin-top:25px}.record{font-size:13px;color:var(--muted);padding-top:14px}.journal{margin-top:35px;border-top:1px solid var(--line);padding-top:22px}.journal h3{font:23px Georgia}.journal p{font-size:13px;margin-bottom:7px}.pill{background:var(--accent);font:10px ui-monospace;padding:4px 8px;border-radius:20px}.pause{padding:60px 25px;text-align:center;border:1px solid var(--line);border-radius:10px;background:var(--white)}footer{padding:22px 36px;max-width:1280px;margin:auto;border-top:1px solid var(--line);font-size:11px;color:var(--muted);display:flex;justify-content:space-between;gap:15px}dialog{max-width:660px;width:calc(100% - 30px);border:1px solid var(--line);border-radius:12px;background:var(--paper);color:var(--ink);padding:38px;max-height:88vh}dialog::backdrop{background:#122e26a8}dialog p{font-size:14px}.source{overflow-wrap:anywhere;font:11px/1.8 ui-monospace}.close{float:right;font-size:24px;padding:0 12px}.helptext{margin:10px 0 0;font-size:11px;color:var(--muted)}@media(max-width:850px){.hero{grid-template-columns:1fr 240px;gap:25px}.cards{gap:10px}.card{padding:18px}.layout{grid-template-columns:220px 1fr;gap:20px}.article{padding:25px}.article .prose{font-size:20px}}@media(max-width:650px){header{padding:15px 18px}.edition{display:none}header button{font-size:11px;padding:7px}.hero{display:block}.art{display:none}main{padding:28px 18px 40px}.cards{grid-template-columns:1fr}.card{padding:23px}.card p{margin-bottom:12px}.card select{margin-bottom:12px}.layout{display:block}.mission{border-right:0;padding:0;border-bottom:1px solid var(--line);margin-bottom:18px}.target{font-size:24px;display:inline-block;margin:8px 15px 8px 0}.stats{margin:10px 0;gap:28px}.stats strong{font-size:23px}.mission .note{margin-bottom:10px}.article{padding:24px}.article h1{font-size:43px;margin:18px 0}.article .prose{font-size:21px}.runbar{flex-wrap:wrap}.runbar .tools{gap:5px}footer{padding:18px;display:block}footer span{display:block}.resume{flex-wrap:wrap}.result h1{font-size:51px}.result .big{font-size:42px}.sectionhead .eyebrow{font-size:9px}.article-end{font-size:8px}}
.mission>.eyebrow{display:block}
import {computeMarathonScore} from './rules.js';
const A=await fetch('./articles.json').then(r=>{if(!r.ok)throw Error('Article collection unavailable');return r.json()});
const $=s=>document.querySelector(s),app=$('#app'),KEY='linkbound-pocket-v1';
const missions=[['Coffee','Galaxy'],['Pottery','Satellite'],['Bird','Computer'],['Battery','Museum'],['Coral reef','Printing'],['Orchestra','Rocket'],['Fossil','Solar panel'],['Textile','Telescope'],['Prism','Coffee'],['Robot','Fungus'],['Paper','Gravity'],['Star','Pottery']];
const trios=[['Coffee',['Fossil','Robot','Orchestra']],['Paper',['Galaxy','Battery','Ocean']],['Bird',['Printing','Prism','Pottery']],['Camera',['Fungus','Rocket','Acoustics']]];
let saved={records:{},journal:[],run:null};try{const x=JSON.parse(localStorage.getItem(KEY));if(x&&typeof x==='object'&&x.records&&Array.isArray(x.journal))saved=x}catch{}
let run=saved.run;if(run&&(!A[run.current]||!Array.isArray(run.path)||!['classic','tri','marathon'].includes(run.mode)))run=null;
let screen='home',last=performance.now(),notice='',hintTitle='';
function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
function store(){saved.run=run;try{localStorage.setItem(KEY,JSON.stringify(saved))}catch{notice='Device storage unavailable; this session still works.'}}
export function shortest(from,to){let q=[[from]],seen=new Set([from]);while(q.length){const p=q.shift(),v=p.at(-1);if(v===to)return p;for(const n of A[v].links)if(!seen.has(n)){seen.add(n);q.push([...p,n])}}return null}
const fmt=ms=>`${Math.floor(ms/60000)}:${String(Math.floor(ms/1000)%60).padStart(2,'0')}`;
const clocks=()=>run.mode==='marathon'?Math.max(0,180000-run.elapsed):run.elapsed;
function records(id){const r=saved.records[id];return r?`${r.clicks} links · ${fmt(r.elapsed)} best`:'No unassisted record yet'}
function home(){screen='home';hintTitle='';store();app.innerHTML=`<section class="hero"><div><div class="eyebrow">A hyperlink adventure / inspired by WikiSpeedrun</div><h1>One link leads<br>to another.</h1><p class="intro">Coffee to a galaxy. Pottery to a satellite.<br>Find your way through a pocket encyclopedia, one article link at a time.</p><span class="pill">NO SEARCH. JUST CURIOSITY.</span></div><div class="art" aria-hidden="true"><div class="orb">↗</div><span class="tag">Coffee → Chemistry</span><span class="tag">Atom → Star → Galaxy</span><span class="tag">Where will you turn?</span></div></section>${run?`<div class="resume"><div><b>A trail is waiting.</b> ${esc(run.current)} · ${run.clicks} links so far</div><button class="primary" id="resume">Resume your run →</button></div>`:''}<div class="sectionhead"><span class="eyebrow">Choose your expedition</span><span class="eyebrow">59 articles / 239 links</span></div><section class="cards"><div class="card"><span class="num">01 / FIND A WAY</span><h3>Trail</h3><p>One beginning, one destination. Follow the connections and find an elegant route.</p><label>Choose a route<select id="mission">${missions.map((p,i)=>`<option value="${i}">${p[0]} → ${p[1]}</option>`).join('')}</select></label><button class="primary" id="startClassic">Open a trail ↗</button><div class="record" id="classicRecord">${records('c0')}</div></div><div class="card"><span class="num">02 / TAKE THE SCENIC ROUTE</span><h3>Three stops</h3><p>Reach three destinations in any order. Choose a route that connects them all.</p><label>Choose an itinerary<select id="tri">${trios.map((p,i)=>`<option value="${i}">From ${p[0]} · ${p[1].join(', ')}</option>`).join('')}</select></label><button id="startTri">Visit three stops ↗</button><div class="record" id="triRecord">${records('t0')}</div></div><div class="card"><span class="num">03 / FOLLOW THE MOMENTUM</span><h3>Marathon</h3><p>Three minutes. A fresh destination after every arrival. Chase points through a chain of articles.</p><p class="note">Harder routes earn more. Clicks cost points. Skip a target for −5. Your clock starts on launch.</p><button id="startMarathon">Start three minutes ↗</button><div class="record">Best: ${saved.marathonBest??'—'} points</div></div></section>${saved.journal.length?`<section class="journal"><h3>Your field notes</h3>${saved.journal.slice(0,5).map(j=>`<p>${esc(j.label)} · ${j.mode==='marathon'?j.score+' pts':j.clicks+' links'} · ${fmt(j.elapsed)}${j.assisted?' · assisted':''}</p>`).join('')}</section>`:''}`;
$('#startClassic').onclick=()=>start('classic',+$('#mission').value);$('#startTri').onclick=()=>start('tri',+$('#tri').value);$('#startMarathon').onclick=()=>start('marathon',0);$('#mission').onchange=()=>$('#classicRecord').textContent=records('c'+$('#mission').value);$('#tri').onchange=()=>$('#triRecord').textContent=records('t'+$('#tri').value);if(run)$('#resume').onclick=()=>{screen='play';last=performance.now();renderPlay()}}
function start(mode,index){const item=mode==='classic'?missions[index]:mode==='tri'?trios[index]:['Coffee',[]];run={mode,index,start:item[0],current:item[0],targets:mode==='classic'?[item[1]]:mode==='tri'?[...item[1]]:[],visited:[],path:[item[0]],clicks:0,elapsed:0,assisted:false,events:[],leg:[],used:[],legClicks:0,done:false};notice='';hintTitle='';if(mode==='marathon')nextTarget();screen='play';last=performance.now();store();renderPlay()}
function nextTarget(){const options=Object.keys(A).map(t=>({t,p:shortest(run.current,t)})).filter(x=>x.p&&x.p.length>=3&&!run.used.includes(x.t));if(!options.length){run.used=[];return nextTarget()}const pick=options[Math.floor(Math.random()*options.length)];run.targets=[pick.t];run.hops=pick.p.length-1;run.legStart=run.current;run.legClicks=0;run.leg=[run.current];run.used.push(pick.t)}
function score(){return computeMarathonScore(run.events)}
function label(){return run.mode==='classic'?`${run.start} → ${run.targets[0]}`:run.mode==='tri'?`${run.start} · three stops`:'Three-minute marathon'}
function tick(){const now=performance.now();if(screen==='play'&&run&&!run.done){run.elapsed+=now-last;if(run.mode==='marathon'&&run.elapsed>=180000){run.elapsed=180000;finish()}else{const c=$('#clock');if(c)c.textContent=fmt(clocks())}}last=now}
setInterval(tick,250);setInterval(()=>{if(screen==='play')store()},2000);
function renderPlay(){if(run.done)return result();screen='play';const a=A[run.current],pending=run.targets.filter(t=>!run.visited.includes(t));const targets=run.targets.map(t=>`<div class="target ${run.visited.includes(t)?'done':''}">${run.visited.includes(t)?'✓ ':''}${esc(t)}</div>`).join('');let li=0;const prose=esc(a.text).replace(/\[([^\]]+)\]/g,(_,t)=>{const idx=li++;return `<button class="link ${hintTitle===t?'hint':''}" data-link="${idx}">${t}<small>${idx+1}</small></button>`});app.innerHTML=`<div class="runbar"><span class="eyebrow">${run.mode==='classic'?'Trail':run.mode==='tri'?'Three stops':'Marathon'} / ${run.assisted?'ASSISTED':'UNASSISTED'}</span><div class="tools"><button id="home">Reading room</button><button id="pause">Pause</button><button id="hint">Hint</button>${run.mode==='marathon'?'<button id="skip">Skip −5</button>':''}</div></div><div class="layout"><aside class="mission"><span class="eyebrow">${run.mode==='tri'?'Your destinations':'Your destination'}</span>${targets}<div class="stats"><div><strong id="clock">${fmt(clocks())}</strong><small>${run.mode==='marathon'?'REMAINING':'ACTIVE TIME'}</small></div><div><strong>${run.mode==='marathon'?score().total:run.clicks}</strong><small>${run.mode==='marathon'?'POINTS':'LINKS'}</small></div></div><p class="note">${run.mode==='marathon'?`${run.hops}-link starting distance · ${run.events.filter(e=>e.kind==='hit').length} arrivals`:'Every underlined phrase opens another article. Which connection gets you closer?'}</p><div class="status" role="status">${esc(notice)}</div><p class="note">Keys 1–${a.links.length}: follow a link.<br>Tab + Enter: choose a link.</p></aside><section><article class="article"><div class="eyebrow">The pocket encyclopedia / ${esc(a.category)}</div><h1>${esc(run.current)}</h1><p class="prose">${prose}</p><div class="article-end"><span>LOCAL COLLECTION / ${String(Object.keys(A).indexOf(run.current)+1).padStart(2,'0')}</span><span>FOLLOW THE UNDERLINED LINKS ↗</span></div></article><div class="breadcrumbs" aria-label="Your route">${run.path.map(esc).join('<span>›</span>')}</div></section></div>`;
app.querySelectorAll('[data-link]').forEach(b=>b.onclick=()=>move(a.links[+b.dataset.link]));$('#home').onclick=()=>{tick();home()};$('#pause').onclick=pause;$('#hint').onclick=hint;if($('#skip'))$('#skip').onclick=()=>{tick();if(run.done)return;run.events.push({kind:'skip'});notice='Target skipped. Five points deducted.';hintTitle='';nextTarget();store();renderPlay()}}
function move(title){if(screen!=='play'||run.done||!A[run.current].links.includes(title))return;tick();if(run.done)return;run.current=title;run.path.push(title);run.clicks++;run.legClicks++;run.leg.push(title);notice='';hintTitle='';if(run.targets.includes(title)){if(run.mode==='marathon'){run.events.push({kind:'hit',hops:run.hops,clicks:run.legClicks,from:run.legStart,to:title,path:[...run.leg]});notice='Arrival logged. Your next destination is ready.';nextTarget()}else{if(!run.visited.includes(title))run.visited.push(title);if(run.visited.length===run.targets.length){finish();return}notice='Destination visited. Keep going for the other stops.'}}store();renderPlay()}
function hint(){tick();if(run.done)return;run.assisted=true;const paths=run.targets.filter(t=>!run.visited.includes(t)).map(t=>shortest(run.current,t)).filter(Boolean).sort((a,b)=>a.length-b.length);hintTitle=paths[0]?.[1]||'';notice=`Try ${hintTitle}. This run is now assisted.`;store();renderPlay()}
function pause(){tick();if(run.done)return;screen='pause';store();app.innerHTML='<section class="pause"><div class="eyebrow">Your clock is stopped</div><h1>Take a little detour.</h1><p>Your route is saved. Come back when you’re ready.</p><button class="primary" id="continue">Continue the trail →</button> <button id="room">Reading room</button></section>';$('#continue').onclick=()=>{screen='play';last=performance.now();renderPlay()};$('#room').onclick=home}
function finish(){if(run.done)return;run.done=true;run.finishedAt=Date.now();const entry={mode:run.mode,label:label(),clicks:run.clicks,elapsed:run.elapsed,assisted:run.assisted,score:score().total};saved.journal.unshift(entry);saved.journal=saved.journal.slice(0,20);if(!run.assisted){if(run.mode==='marathon')saved.marathonBest=Math.max(saved.marathonBest??-Infinity,entry.score);else{const id=(run.mode==='classic'?'c':'t')+run.index,old=saved.records[id];if(!old||run.clicks<old.clicks||(run.clicks===old.clicks&&run.elapsed<old.elapsed))saved.records[id]={clicks:run.clicks,elapsed:run.elapsed}}}store();result()}
function result(){screen='result';const marathon=run.mode==='marathon',s=score();const optimal=run.mode==='classic'?shortest(run.start,run.targets[0]):null;app.innerHTML=`<section class="result"><span class="eyebrow">${run.assisted?'Assisted exploration':'Expedition complete'}</span><h1>${marathon?'Time well wandered.':'A connection made.'}</h1><p>${esc(label())}</p><div class="big">${marathon?s.total+' points':run.clicks+' links'}</div><p>${fmt(run.elapsed)} active time${optimal?' · shortest possible: '+(optimal.length-1)+' links':''}${run.assisted?' · hints used; unassisted records preserved':''}</p>${marathon?`<p class="note">${s.raw} route points − ${s.clicks} completed-route clicks − ${s.skips} skip points + ${s.bonus} completion bonus.</p>${run.events.filter(e=>e.kind==='hit').map(e=>`<div class="route"><b>${esc(e.from)} → ${esc(e.to)}</b><br>${e.path.map(esc).join(' › ')}</div>`).join('')||'<p>No targets reached this time. Try a Trail to learn the connections.</p>'}`:`<div class="route"><span class="eyebrow">Your route</span><p>${run.path.map(esc).join(' → ')}</p></div>${optimal?`<details class="route"><summary>Review one shortest route</summary><p>${optimal.map(esc).join(' → ')}</p></details>`:''}`}<div class="actions"><button class="primary" id="again">${marathon?'Run again':'Try this route again'}</button><button id="new">Reading room →</button></div></section>`;$('#again').onclick=()=>start(run.mode,run.index);$('#new').onclick=()=>{run=null;home()}}
$('#guide').onclick=()=>{if(screen==='play')pause();$('#dialog').showModal()};$('#closeDialog').onclick=()=>$('#dialog').close();document.addEventListener('keydown',e=>{if($('#dialog').open||screen!=='play'||e.altKey||e.ctrlKey||e.metaKey)return;if(/^[1-5]$/.test(e.key)&&!['INPUT','SELECT','TEXTAREA'].includes(e.target.tagName)){const t=A[run.current].links[+e.key-1];if(t){e.preventDefault();move(t)}}});
window.addEventListener('pagehide',()=>{tick();store()});home();
// WikiSpeedrun, MIT, Henry Bao 2026. Original marathon scoring.
const MARATHON_TIER_POINTS = { 2: 25, 3: 45, 4: 80 };
const MARATHON_CLICK_COST = 1;
const MARATHON_SKIP_COST = 5;
// Completion bonuses reward breadth of play — attempt lots of targets, not
// just cherry-pick the hardest. Tiers chosen so a speedrunner playing mostly
// 2-hop targets stays within ~10% of a mixed-strategy player.
const MARATHON_COMPLETION_BONUSES = [
{ threshold: 15, bonus: 100 },
{ threshold: 10, bonus: 60 },
{ threshold: 5, bonus: 30 },
];
// Round durations (ms) the client may request.
// Hard cap on how long the round clock may be deferred while the first target
// warms (degraded 429 path, see startMarathonClock). Past this the clock starts
// regardless, so a room can never hang waiting on a candidate that never warms.
function marathonBasePoints(hops) {
// Clamp hops into the tier table: anything 4+ maps to the 4-tier reward.
const tier = hops <= 2 ? 2 : hops === 3 ? 3 : 4;
return MARATHON_TIER_POINTS[tier];
}
function marathonCompletionBonus(completedCount) {
for (const { threshold, bonus } of MARATHON_COMPLETION_BONUSES) {
if (completedCount >= threshold) return bonus;
}
return 0;
}
// Compute a player's final marathon score. `events` is an ordered log of
// per-target outcomes: { kind: 'hit'|'skip', hops?, clicks?, skipCost? }.
// Returning a breakdown (not just a number) so the end-of-round UI can show
// "raw points − clicks − skips + bonus" without re-deriving the pieces.
function computeMarathonScore(events) {
let raw = 0, clicks = 0, skips = 0, completed = 0;
for (const ev of events) {
if (ev.kind === 'hit') {
raw += marathonBasePoints(ev.hops);
clicks += ev.clicks || 0;
completed += 1;
} else if (ev.kind === 'skip') {
skips += MARATHON_SKIP_COST;
}
}
const bonus = marathonCompletionBonus(completed);
const total = raw - clicks * MARATHON_CLICK_COST - skips + bonus;
return { raw, clicks, skips, completed, bonus, total };
}
export {marathonBasePoints, marathonCompletionBonus, computeMarathonScore};
{
"Coffee": {
"category": "Everyday",
"text": "A coffee bean is the seed of a tropical plant. Roasting changes its aroma through reactions also encountered in [Chemistry]. Growing the crop depends on [Climate], while a morning cup often accompanies [Music]. Ships carried coffee across the [Ocean].",
"links": [
"Chemistry",
"Climate",
"Music",
"Ocean"
]
},
"Chemistry": {
"category": "Science",
"text": "Chemistry studies matter and its transformations. Bonds among [Atom]s help explain why substances behave differently. Reactions can release [Energy], turn sunlight into food through [Photosynthesis], or produce pigments used in [Painting].",
"links": [
"Atom",
"Energy",
"Photosynthesis",
"Painting"
]
},
"Climate": {
"category": "Earth",
"text": "Climate describes patterns of weather over long periods. The [Ocean] stores and moves heat. Changes in [Energy] arriving from the Sun affect the system, while living [Forest]s exchange water and carbon with the atmosphere. Observations are gathered by [Satellite]s.",
"links": [
"Ocean",
"Energy",
"Forest",
"Satellite"
]
},
"Music": {
"category": "Culture",
"text": "Music organizes sound over time. Rhythm often uses repeating [Mathematics] patterns, while pitch comes from [Sound] vibrations. An [Orchestra] combines instruments into a shared performance. Digital recordings can be stored by a [Computer].",
"links": [
"Mathematics",
"Sound",
"Orchestra",
"Computer"
]
},
"Ocean": {
"category": "Earth",
"text": "Most of Earth’s surface is covered by ocean water. Moving [Wave]s carry energy, and currents influence [Climate]. A [Coral reef] supports rich marine life. Humans navigate the water using [Ship]s.",
"links": [
"Wave",
"Climate",
"Coral reef",
"Ship"
]
},
"Atom": {
"category": "Science",
"text": "An atom contains a small nucleus surrounded by electrons. The arrangement of electrons underlies [Chemistry]. Light emitted by excited atoms produces a [Spectrum], revealing what a distant [Star] contains. Atomic processes also connect to [Energy].",
"links": [
"Chemistry",
"Spectrum",
"Star",
"Energy"
]
},
"Energy": {
"category": "Science",
"text": "Energy can be transferred and transformed. A [Solar panel] converts light into electricity. Moving objects follow the rules of [Physics], and a [Battery] stores energy chemically. Plants capture light through [Photosynthesis].",
"links": [
"Solar panel",
"Physics",
"Battery",
"Photosynthesis"
]
},
"Photosynthesis": {
"category": "Nature",
"text": "Photosynthesis uses light to build chemical compounds. It takes place in plants and many [Algae]. Its products support much of the food web in a [Forest]. The process connects [Chemistry] with the flow of [Energy].",
"links": [
"Algae",
"Forest",
"Chemistry",
"Energy"
]
},
"Painting": {
"category": "Culture",
"text": "Painting puts pigment onto a surface to form an image. Artists arrange [Color] and shape, sometimes using [Perspective] to suggest depth. A [Museum] can preserve a work, while [Photography] offers another way to make images.",
"links": [
"Color",
"Perspective",
"Museum",
"Photography"
]
},
"Forest": {
"category": "Nature",
"text": "A forest is an ecosystem dominated by trees. [Fungus] decomposes organic material, while [Bird]s move seeds between places. Forests interact with [Climate] and are shaped by the [Evolution] of their many species.",
"links": [
"Fungus",
"Bird",
"Climate",
"Evolution"
]
},
"Satellite": {
"category": "Space",
"text": "A satellite moves around a larger body. Artificial satellites use [Orbit]s for communications and Earth observation. Some carry a [Telescope]; others support [Navigation]. A spacecraft often gets electricity from a [Solar panel].",
"links": [
"Orbit",
"Telescope",
"Navigation",
"Solar panel"
]
},
"Mathematics": {
"category": "Science",
"text": "Mathematics studies structures, quantities and relationships. [Geometry] describes shapes and space. [Probability] describes uncertainty. A [Computer] can carry out long calculations, and patterns in [Music] offer a familiar application.",
"links": [
"Geometry",
"Probability",
"Computer",
"Music"
]
},
"Sound": {
"category": "Science",
"text": "Sound is a disturbance moving through a material. It travels as a [Wave] and can be described by [Physics]. A [Bird] uses calls to communicate. Musicians shape sound with instruments in an [Orchestra].",
"links": [
"Wave",
"Physics",
"Bird",
"Orchestra"
]
},
"Orchestra": {
"category": "Culture",
"text": "An orchestra combines strings, woodwinds, brass and percussion. A conductor coordinates the flow of [Music]. Players read symbols much as a reader interprets [Writing]. Instruments generate [Sound], and performances may take place inside an architectural space designed for [Acoustics].",
"links": [
"Music",
"Writing",
"Sound",
"Acoustics"
]
},
"Computer": {
"category": "Technology",
"text": "A computer follows encoded instructions. A [Program] organizes those instructions, while an [Algorithm] describes a method for solving a problem. Machines exchange information through the [Internet]. Their physical parts include [Semiconductor] devices.",
"links": [
"Program",
"Algorithm",
"Internet",
"Semiconductor"
]
},
"Wave": {
"category": "Science",
"text": "A wave is a propagating disturbance. [Sound] needs a medium, while light can cross empty space. [Ocean] waves are familiar at the shore. [Physics] uses [Mathematics] to describe wavelength, speed and interference.",
"links": [
"Sound",
"Ocean",
"Physics",
"Mathematics"
]
},
"Coral reef": {
"category": "Nature",
"text": "A coral reef is built by colonies of small animals. Many reef corals live with microscopic [Algae] that provide nutrients. Reef health depends on the surrounding [Ocean] and [Climate]. The variety of species reflects [Evolution].",
"links": [
"Algae",
"Ocean",
"Climate",
"Evolution"
]
},
"Ship": {
"category": "Technology",
"text": "A ship floats by displacing water. Its hull is shaped using ideas from [Geometry] and [Physics]. Long voyages require [Navigation]. Modern trade routes cross the [Ocean], carrying goods such as [Coffee].",
"links": [
"Geometry",
"Physics",
"Navigation",
"Ocean",
"Coffee"
]
},
"Spectrum": {
"category": "Science",
"text": "A spectrum spreads a signal into components. Visible light forms a range of [Color]s. A [Prism] can separate them, and a [Telescope] can measure a star’s light. This reveals the signatures of an [Atom].",
"links": [
"Color",
"Prism",
"Telescope",
"Atom"
]
},
"Star": {
"category": "Space",
"text": "A star shines through nuclear reactions deep within it. The Sun provides the light that powers [Photosynthesis]. Stars gather into a [Galaxy]. A [Telescope] studies them through their [Spectrum].",
"links": [
"Photosynthesis",
"Galaxy",
"Telescope",
"Spectrum"
]
},
"Solar panel": {
"category": "Technology",
"text": "A solar panel turns light into electrical energy using [Semiconductor] materials. Output changes with [Climate] and the position of the Sun. A [Battery] can store the energy, while panels on a [Satellite] work above the atmosphere.",
"links": [
"Semiconductor",
"Climate",
"Battery",
"Satellite"
]
},
"Physics": {
"category": "Science",
"text": "Physics studies matter, motion and interactions. It describes [Gravity], the transport of [Energy], and the behavior of a [Wave]. Its explanations are written using [Mathematics].",
"links": [
"Gravity",
"Energy",
"Wave",
"Mathematics"
]
},
"Battery": {
"category": "Technology",
"text": "A battery supplies electrical energy through chemical reactions. It connects [Chemistry] to portable technology such as a [Computer]. Batteries help power [Robot]s and store electricity from a [Solar panel].",
"links": [
"Chemistry",
"Computer",
"Robot",
"Solar panel"
]
},
"Algae": {
"category": "Nature",
"text": "Algae include many organisms that use [Photosynthesis]. They live in freshwater and the [Ocean]. Some partner with a [Fungus] to form lichens. Other microscopic algae live alongside animals in a [Coral reef].",
"links": [
"Photosynthesis",
"Ocean",
"Fungus",
"Coral reef"
]
},
"Color": {
"category": "Culture",
"text": "Color perception depends on light, the eye and the brain. A [Spectrum] describes light by wavelength. Pigments mix differently from emitted light, a distinction useful in [Painting] and [Photography]. Color arrangements can form a [Pattern].",
"links": [
"Spectrum",
"Painting",
"Photography",
"Pattern"
]
},
"Perspective": {
"category": "Culture",
"text": "Perspective creates an impression of depth on a flat surface. It connects [Geometry] with [Painting]. A [Camera] projects a scene through a lens. Architects also use perspective to communicate a [Building] design.",
"links": [
"Geometry",
"Painting",
"Camera",
"Building"
]
},
"Museum": {
"category": "Culture",
"text": "A museum collects and interprets objects. A collection may include [Painting], early [Computer]s, or objects from [Archaeology]. Exhibits combine images and [Writing] to tell a story.",
"links": [
"Painting",
"Computer",
"Archaeology",
"Writing"
]
},
"Photography": {
"category": "Culture",
"text": "Photography records light. A [Camera] focuses an image onto a sensitive surface. [Color] and [Perspective] shape the result. Photographs of a distant [Galaxy] help astronomers study the universe.",
"links": [
"Camera",
"Color",
"Perspective",
"Galaxy"
]
},
"Fungus": {
"category": "Nature",
"text": "Fungi absorb nutrients from their surroundings. A mushroom is only part of a larger network. In a [Forest], fungi recycle matter through [Chemistry]. Some form partnerships with [Algae], and their history is studied through [Evolution].",
"links": [
"Forest",
"Chemistry",
"Algae",
"Evolution"
]
},
"Bird": {
"category": "Nature",
"text": "Birds are feathered animals descended from dinosaurs. Their calls are forms of [Sound]. Many use seasonal [Navigation] across great distances. Adaptations in a [Forest] or coastline reflect [Evolution].",
"links": [
"Sound",
"Navigation",
"Forest",
"Evolution"
]
},
"Evolution": {
"category": "Nature",
"text": "Evolution describes changes in inherited traits across generations. [Probability] plays a part in genetic variation. Evidence comes from living species and [Fossil]s. The process explains the diversity of a [Bird] or a [Coral reef].",
"links": [
"Probability",
"Fossil",
"Bird",
"Coral reef"
]
},
"Orbit": {
"category": "Space",
"text": "An orbit is a path shaped by [Gravity]. A [Satellite] may circle Earth while Earth circles a [Star]. Predicting the motion uses [Mathematics]. A spacecraft can change its path using a [Rocket].",
"links": [
"Gravity",
"Satellite",
"Star",
"Mathematics",
"Rocket"
]
},
"Telescope": {
"category": "Space",
"text": "A telescope collects light or other radiation. Mirrors and lenses use [Geometry] to form images. A telescope can reveal a [Star], a distant [Galaxy], or a planet’s [Orbit].",
"links": [
"Geometry",
"Star",
"Galaxy",
"Orbit"
]
},
"Navigation": {
"category": "Technology",
"text": "Navigation determines where something is and how it should travel. A [Map] represents places. [Satellite] signals can help locate a receiver, while sailors once used a [Star] to guide a [Ship].",
"links": [
"Map",
"Satellite",
"Star",
"Ship"
]
},
"Geometry": {
"category": "Science",
"text": "Geometry studies shapes and spatial relationships. It explains a [Prism], the projections of [Perspective], and paths drawn on a [Map]. A [Pattern] can be made from repeated geometric forms.",
"links": [
"Prism",
"Perspective",
"Map",
"Pattern"
]
},
"Probability": {
"category": "Science",
"text": "Probability measures uncertainty in a model. It underlies many [Game]s and helps analyze [Evolution]. A [Computer] can simulate uncertain events with an [Algorithm].",
"links": [
"Game",
"Evolution",
"Computer",
"Algorithm"
]
},
"Writing": {
"category": "Culture",
"text": "Writing encodes language in visible marks. [Paper] became a widely used surface for it. The spread of [Printing] changed how texts traveled. Today information also moves through the [Internet] and appears in a [Museum].",
"links": [
"Paper",
"Printing",
"Internet",
"Museum"
]
},
"Acoustics": {
"category": "Science",
"text": "Acoustics studies how [Sound] is produced and transmitted. An [Orchestra] benefits from a hall with useful reflections. A [Building] can absorb or scatter sound, effects modeled through [Physics].",
"links": [
"Sound",
"Orchestra",
"Building",
"Physics"
]
},
"Program": {
"category": "Technology",
"text": "A program expresses instructions in a language a machine can process. An [Algorithm] guides its logic. Programs run on a [Computer], control a [Robot], and can create a [Game].",
"links": [
"Algorithm",
"Computer",
"Robot",
"Game"
]
},
"Algorithm": {
"category": "Technology",
"text": "An algorithm is a sequence of steps for a task. Some find a route through a [Map]; others order data on a [Computer]. Algorithms can use [Probability] or generate a visual [Pattern].",
"links": [
"Map",
"Computer",
"Probability",
"Pattern"
]
},
"Internet": {
"category": "Technology",
"text": "The Internet connects networks that exchange data. A [Computer] sends information along linked paths. [Writing], [Photography] and [Music] can all be represented digitally and transmitted through it.",
"links": [
"Computer",
"Writing",
"Photography",
"Music"
]
},
"Semiconductor": {
"category": "Technology",
"text": "A semiconductor has electrical properties that can be controlled. Its behavior depends on the structure of an [Atom]. It is used in a [Computer], a [Solar panel] and sensors inside a [Camera].",
"links": [
"Atom",
"Computer",
"Solar panel",
"Camera"
]
},
"Prism": {
"category": "Science",
"text": "A prism bends light as it enters and leaves the material. Different wavelengths bend by different amounts, producing a [Spectrum]. Its shape is studied in [Geometry]. This connection between light and [Color] has inspired [Painting].",
"links": [
"Spectrum",
"Geometry",
"Color",
"Painting"
]
},
"Galaxy": {
"category": "Space",
"text": "A galaxy contains stars, gas, dust and other matter held together by [Gravity]. Each [Star] belongs to a much larger system. Observations with a [Telescope] and [Photography] reveal its shape and history.",
"links": [
"Gravity",
"Star",
"Telescope",
"Photography"
]
},
"Gravity": {
"category": "Space",
"text": "Gravity shapes motion on large scales. It keeps a planet in [Orbit] and binds a [Galaxy]. A [Rocket] must gain enough speed to leave Earth. The study of gravity is part of [Physics].",
"links": [
"Orbit",
"Galaxy",
"Rocket",
"Physics"
]
},
"Robot": {
"category": "Technology",
"text": "A robot senses its surroundings and acts through a [Program]. Its movements follow [Physics]. It may use a [Camera] for vision and a [Battery] for energy. Some robots explore beyond Earth on a [Rocket].",
"links": [
"Program",
"Physics",
"Camera",
"Battery",
"Rocket"
]
},
"Pattern": {
"category": "Culture",
"text": "A pattern repeats or organizes elements. It may appear in [Music], [Geometry] or a woven [Textile]. An [Algorithm] can create repeating arrangements or analyze them.",
"links": [
"Music",
"Geometry",
"Textile",
"Algorithm"
]
},
"Camera": {
"category": "Technology",
"text": "A camera forms an image using light. [Photography] records that image. A camera carried by a [Satellite] can make a [Map]. Its electronic sensor is built using [Semiconductor] technology.",
"links": [
"Photography",
"Satellite",
"Map",
"Semiconductor"
]
},
"Building": {
"category": "Culture",
"text": "A building creates an enclosed place for human activity. Designers use [Geometry], [Perspective] and knowledge of [Acoustics]. The structure’s materials and forces are studied through [Physics].",
"links": [
"Geometry",
"Perspective",
"Acoustics",
"Physics"
]
},
"Archaeology": {
"category": "Culture",
"text": "Archaeology studies human activity through material evidence. [Writing] may preserve names and stories, while a fragment of [Pottery] reveals practical choices. Finds can be displayed in a [Museum] and compared through a [Map].",
"links": [
"Writing",
"Pottery",
"Museum",
"Map"
]
},
"Fossil": {
"category": "Earth",
"text": "A fossil preserves evidence of past life. It helps reconstruct [Evolution]. Fossils occur in rocks studied through [Geology], and a [Museum] may exhibit them alongside explanations in [Writing].",
"links": [
"Evolution",
"Geology",
"Museum",
"Writing"
]
},
"Rocket": {
"category": "Space",
"text": "A rocket accelerates by expelling material. Its motion follows [Physics] and works even in empty space. Rockets place a [Satellite] in [Orbit] and carry instruments that study a [Star].",
"links": [
"Physics",
"Satellite",
"Orbit",
"Star"
]
},
"Map": {
"category": "Earth",
"text": "A map represents selected features of a place. [Geometry] helps project a curved surface onto a flat sheet. Maps support [Navigation] and the work of [Archaeology]. An [Algorithm] can find a route across them.",
"links": [
"Geometry",
"Navigation",
"Archaeology",
"Algorithm"
]
},
"Game": {
"category": "Culture",
"text": "A game defines actions and goals through rules. [Probability] can create uncertainty, while [Mathematics] helps describe strategy. A digital game is a [Program]; a board game may use tokens made from [Paper].",
"links": [
"Probability",
"Mathematics",
"Program",
"Paper"
]
},
"Paper": {
"category": "Culture",
"text": "Paper forms from a sheet of fibers. It provides a surface for [Writing], [Painting] and [Printing]. Much modern paper begins with wood grown in a [Forest].",
"links": [
"Writing",
"Painting",
"Printing",
"Forest"
]
},
"Printing": {
"category": "Technology",
"text": "Printing reproduces marks on a surface. Movable type helped distribute [Writing] on [Paper]. Modern methods can reproduce [Photography] and [Color] with precisely placed ink.",
"links": [
"Writing",
"Paper",
"Photography",
"Color"
]
},
"Textile": {
"category": "Culture",
"text": "A textile is made by joining fibers. Weaving creates a repeated [Pattern] through interlacing threads. Cloth can carry [Color], and making fibers involves [Chemistry]. Old fragments are studied in [Archaeology].",
"links": [
"Pattern",
"Color",
"Chemistry",
"Archaeology"
]
},
"Pottery": {
"category": "Culture",
"text": "Pottery begins with clay shaped and fired. Heat changes the material through [Chemistry]. Decoration may combine [Painting] and [Pattern]. Durable fragments are important evidence in [Archaeology].",
"links": [
"Chemistry",
"Painting",
"Pattern",
"Archaeology"
]
},
"Geology": {
"category": "Earth",
"text": "Geology studies Earth’s materials and history. Rocks may preserve a [Fossil]. Water in the [Ocean] and changes in [Climate] shape landscapes. Physical processes connect geology to [Physics].",
"links": [
"Fossil",
"Ocean",
"Climate",
"Physics"
]
}
}{
"title": "Linkbound \u00b7 Pocket Speedrun",
"slug": "linkbound-pocket-speedrun",
"repository": "https://github.com/HenryBao912/WikiSpeedrun",
"source": "https://github.com/HenryBao912/WikiSpeedrun/tree/bf65019bf78d4a598bc1b4d376a5930e2464cc9a",
"commit": "bf65019bf78d4a598bc1b4d376a5930e2464cc9a",
"stars_observed": 166,
"license": "MIT",
"last_push": "2026-10-26",
"tags": [
"github-source",
"game"
],
"adaptations": [
"Static local replacement for server/SSE and live Wikipedia REST",
"59 original compact articles and closed link graph; not full Wikipedia",
"Original marathon scoring unchanged; classic and tri link-navigation rules",
"Local records, keyboard link shortcuts, hints and route review; no online multiplayer"
]
}MIT License Copyright (c) 2026 Henry Bao Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.