Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
15 lines (15 loc) · 5.89 KB
/
Copy pathapp.js
File metadata and controls
15 lines (15 loc) · 5.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const lessons = [
['01','Cron','lesson','lessons/01-cron.md'],['02','Job','lesson','lessons/02-job.md'],['03','Worker','lesson','lessons/03-worker.md'],['04','Lane','lesson','lessons/04-lane.md'],['05','Run','lesson','lessons/05-run.md'],['06','Handoff','lesson','lessons/06-handoff.md'],['07','Subagent','lesson','lessons/07-subagent.md'],['08','Memory','lesson','lessons/08-memory.md'],['09','Skills + connectors','lesson','lessons/09-skills-and-connectors.md'],['10','The browser','lesson','lessons/10-the-browser.md'],['11','The VM machine','lesson','lessons/11-the-vm-machine.md'],['12','Reboots + lane deaths','lesson','lessons/12-reboots-and-lane-deaths.md'],['13','Verification','lesson','lessons/13-verification.md'],['14','Posting rules','lesson','lessons/14-posting-rules.md'],['R1','Glossary','reference','docs/GLOSSARY.md'],['R2','How a cron runs','reference','docs/HOW-A-CRON-RUNS.md'],['R3','VM overview','reference','docs/VM-OVERVIEW.md']
];
const nav = document.querySelector('#lessonNav'), reader = document.querySelector('#reader'), search = document.querySelector('#searchInput');
const complete = JSON.parse(localStorage.getItem('muse-complete') || '{}');
function renderNav(list=lessons){ nav.innerHTML = list.map((x,i)=>`<a class="nav-item ${complete[x[0]]?'done':''}" href="${x[2]==='lesson'?'./lesson-'+x[0]+'-'+x[1].toLowerCase().replaceAll(' + ','-').replaceAll(' ','-')+'.html':'./docs.html'}" data-id="${x[0]}" data-type="${x[2]}"><span class="num">${x[0]}</span><small>${x[1]}</small></a>`).join(''); nav.querySelectorAll('.nav-item').forEach(b=>b.onclick=(e)=>{if(location.protocol==='file:')return; e.preventDefault(); openLesson(b.dataset.id)}); }
function find(id){return lessons.find(x=>x[0]===id)}
function openLesson(id){ const item=find(id); if(!item)return; document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('selected',b.dataset.id===id)); reader.innerHTML=`<div class="reader-kicker">${item[2]==='lesson'?'LESSON':'REFERENCE'} / ${item[0]}</div><h3>${item[1]}</h3><div id="lessonContent"><p>Loading field notes…</p></div>`; fetch(item[3]).then(r=>r.text()).then(md=>{const lines=md.split('\n').filter(Boolean); const body=lines.slice(2).filter(x=>!x.startsWith('#')&&!x.startsWith('>')&&!x.startsWith('-')); const signal=lines.findIndex(x=>x.toLowerCase().includes('signal to remember')); const sig=signal>-1?lines[signal+2]:''; const checks=lines.slice(-3).filter(x=>x.startsWith('-')).join(' '); document.querySelector('#lessonContent').innerHTML=`<p class="body-copy">${format(body.slice(0,2).join(' '))}</p>${sig?`<div class="signal-box">${format(sig)}</div>`:''}<div class="check">${format(checks)}</div><button class="complete ${complete[id]?'is-done':''}" id="completeBtn">${complete[id]?'✓ Completed':'Mark as complete'}</button>`; document.querySelector('#completeBtn').onclick=()=>{complete[id]=!complete[id];localStorage.setItem('muse-complete',JSON.stringify(complete));openLesson(id);updateProgress();renderNav(currentList)} }).catch(()=>{document.querySelector('#lessonContent').innerHTML='<p>Field notes are unavailable. Open the source file directly from the package.</p>'}); }
function format(s){return s.replace(/`([^`]+)`/g,'<code>$1</code>').replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>')}
let currentList=lessons;
function updateProgress(){const n=Object.keys(complete).filter(k=>lessons.some(x=>x[0]===k)).length;document.querySelector('#progressText').textContent=`${n} / ${lessons.length} complete`;document.querySelector('#progressBar').style.width=`${n/lessons.length*100}%`}
function filter(type){const q=search.value.toLowerCase();currentList=lessons.filter(x=>(type==='all'||x[2]===type)&&x[1].toLowerCase().includes(q));renderNav(currentList)}
document.querySelectorAll('.filter').forEach(b=>b.onclick=()=>{document.querySelectorAll('.filter').forEach(x=>x.classList.remove('active'));b.classList.add('active');filter(b.dataset.filter)});search.oninput=()=>filter(document.querySelector('.filter.active').dataset.filter);document.addEventListener('keydown',e=>{if(e.key==='/'&&document.activeElement!==search){e.preventDefault();search.focus()}});const randomLesson=document.querySelector('#randomLesson'); if(randomLesson) randomLesson.onclick=(e)=>{e.preventDefault();const x=lessons[Math.floor(Math.random()*lessons.length)];openLesson(x[0]);document.querySelector('#curriculum').scrollIntoView({behavior:'smooth'})};document.querySelector('#themePulse').onclick=()=>{document.body.classList.add('pulse');setTimeout(()=>document.body.classList.remove('pulse'),700)};setInterval(()=>document.querySelector('#clock').textContent=new Date().toLocaleTimeString([], {hour12:false}),1000);
renderNav();updateProgress();
const canvas=document.querySelector('#signalCanvas'),ctx=canvas.getContext('2d');let w,h,particles=[],pulse=0;function resize(){w=canvas.width=innerWidth*devicePixelRatio;h=canvas.height=innerHeight*devicePixelRatio;canvas.style.width=innerWidth+'px';canvas.style.height=innerHeight+'px';particles=Array.from({length:Math.min(90,Math.floor(innerWidth/14))},()=>({x:Math.random()*w,y:Math.random()*h,vx:(Math.random()-.5)*.22*devicePixelRatio,vy:(Math.random()-.5)*.22*devicePixelRatio,r:Math.random()*1.4*devicePixelRatio}))}function draw(){ctx.clearRect(0,0,w,h);const grad=ctx.createRadialGradient(w*.72,h*.25,0,w*.72,h*.25,w*.75);grad.addColorStop(0,'rgba(79,52,130,.18)');grad.addColorStop(1,'rgba(7,8,13,0)');ctx.fillStyle=grad;ctx.fillRect(0,0,w,h);particles.forEach((p,i)=>{p.x=(p.x+p.vx+w)%w;p.y=(p.y+p.vy+h)%h;ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle=i%5===0?'rgba(199,243,106,.7)':'rgba(143,124,255,.35)';ctx.fill();for(let j=i+1;j<particles.length;j++){const q=particles[j],d=Math.hypot(p.x-q.x,p.y-q.y);if(d<100*devicePixelRatio){ctx.strokeStyle=`rgba(143,124,255,${.11*(1-d/(100*devicePixelRatio))})`;ctx.lineWidth=devicePixelRatio;ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke()}}});requestAnimationFrame(draw)}addEventListener('resize',resize);resize();draw();