diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-04 13:30:41 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-04 13:30:41 -0700 |
| commit | a4994371c0b0fbea9d2802acc22d9cc3977a0de0 (patch) | |
| tree | 98fd05869028a931f5ec8cf503b5cdf6b6c52e87 /docs/2026_REBASE/app.js | |
| parent | 9149ea7a50c564856bd09633f38d980e8230c34f (diff) | |
| download | Simple-a4994371c0b0fbea9d2802acc22d9cc3977a0de0.tar.gz Simple-a4994371c0b0fbea9d2802acc22d9cc3977a0de0.zip | |
Slides for 2026 REBASEreadme
Diffstat (limited to 'docs/2026_REBASE/app.js')
| -rw-r--r-- | docs/2026_REBASE/app.js | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/docs/2026_REBASE/app.js b/docs/2026_REBASE/app.js new file mode 100644 index 00000000..c5c6aab0 --- /dev/null +++ b/docs/2026_REBASE/app.js @@ -0,0 +1,303 @@ +(function () { + "use strict"; + const R=window.REBASE, slides=R.slides, $=id=>document.getElementById(id); + const presenter=new URLSearchParams(location.search).has("presenter"); + const state={index:0,step:0,demo:null,view:"example",backend:0,library:null,libraryWide:false,libraryScroll:0,methods:false}; + let held=0,started=null,toastTimeout,channel; + if(presenter) document.body.classList.add("presenter"); + try { channel=new BroadcastChannel("simple-rebase-"+location.pathname); } catch (_) {} + const current=()=>slides[state.index]; + const view=()=>current().views?.find(v=>v.id===state.view); + const steps=()=>view()?.steps??current().steps; + const elapsed=()=>held+(started===null?0:Date.now()-started); + const clock=ms=>Math.floor(ms/60000).toString().padStart(2,"0")+":"+Math.floor(ms/1000%60).toString().padStart(2,"0"); + const starts=i=>slides.slice(0,i).reduce((n,s)=>n+s.minutes,0); + function fit() { + const box=$("viewport").getBoundingClientRect(); + document.documentElement.style.setProperty("--scale",Math.min(box.width/1440,box.height/900)); + } + function readHash() { + const [id,step,tab]=location.hash.slice(1).split("/"); + const index=slides.findIndex(s=>s.id===id); + if(index>=0) { + state.index=index; + state.view=slides[index].views?.some(v=>v.id===tab)?tab:"example"; + state.step=Math.max(0,Math.min(steps(),Number(step)||0)); + state.demo=null; + } + } + function broadcast() { + if(!presenter && channel) channel.postMessage({type:"state",state:{...state},elapsed:elapsed(),running:started!==null}); + } + function render(remote=false) { + const s=current(), count=slides.filter(x=>!x.optional).length; + const v=view(), builds=steps(); + const tabs=s.views?'<nav class="view-tabs" aria-label="Slide views">'+ + [{id:"example",label:s.id==="memory"?"Examples":"Example"},...s.views].map(t=> + '<button data-view="'+t.id+'" aria-pressed="'+String(state.view===t.id)+'">'+R.escape(t.label)+'</button>').join("")+'</nav>':""; + const number=s.optional?"BONUS":slides.slice(0,state.index+1).filter(x=>!x.optional).length; + $("slide").className="slide"+(s.still?" still":""); + $("slide").dataset.id=s.id; + $("slide").dataset.step=state.step; + $("slide").dataset.view=state.view; + $("slide").innerHTML='<header class="kicker"><span>'+R.escape(s.section)+'</span>'+tabs+'<span class="tag">'+(s.demo&&s.id!=="sccp"?"LIVE GRAPH":s.optional?"OPTIONAL":"REBASE 2026")+'</span></header>'+ + '<div class="slide-content">'+(v?v.body(state):s.body(state))+'</div><footer class="footer"><span><strong>Simple</strong> / Cliff Click</span>'+ + '<span class="build-dots" aria-label="Build '+state.step+' of '+builds+'">'+Array.from({length:builds+1},(_,i)=>'<i class="'+(i<=state.step?"on":"")+'"></i>').join("")+'</span>'+ + '<span>'+R.escape(s.section)+' <strong> / '+number+'</strong></span></footer>'; + $("slide").querySelectorAll("[data-reveal]").forEach(el=>{ + const hidden=Number(el.dataset.reveal)>state.step; + el.classList.toggle("unrevealed",hidden); + el.setAttribute("aria-hidden",hidden?"true":"false"); + el.inert=hidden; + }); + $("slide").querySelectorAll(".state-card").forEach(card=>{ + const entries=card.querySelectorAll(".state"); + entries.forEach((el,i)=>{ + const active=i===Math.min(state.step,entries.length-1); + el.classList.toggle("active",active); + el.setAttribute("aria-hidden",String(!active)); + }); + }); + $("slide").querySelectorAll("[data-demo]").forEach(button=>{ + const active=button.dataset.demo===(state.demo||s.demo); + button.classList.toggle("active",active); + button.setAttribute("aria-pressed",String(active)); + }); + $("position").textContent=s.optional?"BONUS":number+" / "+count; + $("previous").disabled=state.index===0&&state.step===0; + $("next").disabled=state.index===slides.length-1&&state.step===builds; + $("notes-content").innerHTML=v?v.notes:s.notes; + const source=$("slide").querySelector('[data-library-scroll]'); + if(source) source.scrollTop=state.libraryScroll; + document.title=s.label+" · A Simple Tutorial"; + if(!remote) { + history.replaceState(null,"","#"+s.id+(v?"/"+state.step+"/"+v.id:state.step?"/"+state.step:"")); + broadcast(); + } + tick(); + fit(); + } + function go(id) { + const index=slides.findIndex(s=>s.id===id); + if(index<0) return; + state.index=index;state.step=0;state.demo=null;state.view="example";render(); + } + function move(direction,jump=false) { + const s=current(); + if(!jump && direction>0 && state.step<steps()) {state.step++;render();return;} + if(!jump && direction<0 && state.step>0) {state.step--;render();return;} + let index=state.index+direction; + while(index>=0&&index<slides.length&&slides[index].optional) index+=direction; + if(index<0||index>=slides.length) return; + state.index=index;state.step=direction<0&&!jump?slides[index].steps:0;state.demo=null;state.view="example";render(); + } + function action(name,value,remote=false) { + if(presenter&&!remote&&channel) {channel.postMessage({type:"command",name,value});return;} + if(name==="next") move(1); + else if(name==="previous") move(-1); + else if(name==="next-slide") move(1,true); + else if(name==="previous-slide") move(-1,true); + else if(name==="goto") go(value); + else if(name==="demo") {state.demo=value;render();} + else if(name==="language") { + if(current().id!=="language") return; + if(value==="library-open") {state.library="sys";state.libraryScroll=0;} + else if(value==="library-close") {state.library=null;state.libraryWide=false;state.libraryScroll=0;} + else if(value==="library-width") state.libraryWide=!state.libraryWide; + else if(value==="methods-open" && state.step>=1) state.methods=true; + else if(value==="methods-close") state.methods=false; + else return; + render(); + const focus=value==="library-close"?'[data-language="library-open"]':value==="methods-close"?'[data-language="methods-open"]':value.startsWith("methods")?'[data-language="methods-close"]':'[data-language="library-close"]'; + $("slide").querySelector(focus)?.focus({preventScroll:true}); + } + else if(name==="library-path") { + if(current().id!=="language"||!R.libraryHasPath(value)) return; + state.library=value;state.libraryScroll=0;render(); + $("slide").querySelector('[data-library-scroll],.library-list')?.focus({preventScroll:true}); + } + else if(name==="library-scroll") { + if(current().id!=="language"||!Number.isFinite(value)||value<0) return; + state.libraryScroll=value; + const source=$("slide").querySelector('[data-library-scroll]'); + if(source && Math.abs(source.scrollTop-value)>1) source.scrollTop=value; + broadcast(); + } + else if(name==="backend") { + const index=Number(value); + if(current().id!=="backend"||!Number.isInteger(index)||index<0||index>state.step) return; + state.backend^=1<<index;render(); + $("slide").querySelector('[data-backend="'+index+'"]').focus({preventScroll:true}); + } + else if(name==="view") { + if(value!=="example"&&!current().views?.some(v=>v.id===value)) return; + state.view=value;state.step=0;render(); + $("slide").querySelector('[data-view="'+value+'"]').focus({preventScroll:true}); + } + else if(name==="cycle-view"&¤t().views) { + const ids=["example",...current().views.map(v=>v.id)]; + action("view",ids[(ids.indexOf(state.view)+1)%ids.length],remote); + } + else if(name==="timer") { + if(started===null) started=Date.now(); + else {held=elapsed();started=null;} + tick();broadcast(); + } else if(name==="reset") {held=0;started=null;tick();broadcast();} + } + function tick() { + const s=current(), time=elapsed(), start=starts(state.index), end=start+s.minutes; + $("timer").textContent=(started===null?(held?"Resume ":"Start timer "):"Pause ")+clock(time); + $("pace").textContent=(presenter&&!channel?"Independent notes · ":"")+ + (s.optional?"Optional 2 min · replaces closing discussion":"Planned "+clock(start*60000)+"–"+clock(end*60000))+ + " · elapsed "+clock(time); + $("pace").classList.toggle("over",!s.optional&&time>end*60000); + } + function toast(message) { + clearTimeout(toastTimeout); + $("toast").textContent=message;$("toast").classList.add("show"); + toastTimeout=setTimeout(()=>$("toast").classList.remove("show"),2600); + } + function openDialog(content) { + $("dialog-content").innerHTML=content; + if(!$("dialog").open) $("dialog").showModal(); + } + async function copy(text,label) { + try { + if(!navigator.clipboard) throw new Error("No clipboard API"); + await navigator.clipboard.writeText(text); + toast(label+" copied"); + } catch (_) { + const field=document.createElement("textarea"); + field.value=text;field.style.cssText="position:fixed;left:-9999px"; + document.body.append(field);field.select(); + let ok=false; + try {ok=document.execCommand("copy");} catch (_) {} + field.remove(); + if(ok) toast(label+" copied"); + else { + openDialog('<h2>Copy '+R.escape(label.toLowerCase())+'</h2><p>Select and copy the text below.</p><textarea id="manual-copy" spellcheck="false"></textarea>'); + $("manual-copy").value=text;$("manual-copy").focus();$("manual-copy").select(); + } + } + } + function overview() { + openDialog('<h2>The 30-minute route</h2><div class="overview-grid">'+slides.map(s=> + '<button data-goto="'+s.id+'">'+R.escape(s.label)+'<small>'+ + (s.optional?"Optional · 2 min, outside the default route":s.minutes+" min · "+(s.demo&&s.id!=="sccp"?"slide + viewer":"slides"))+ + '</small></button>').join("")+'</div>'); + } + function help() { + openDialog('<h2>Presentation controls</h2><div class="keys">'+[ + ["← / →","Previous / next build"],["Space","Next build"],["PgUp / PgDn","Previous / next slide"],["Home / End","Title / closing slide"], + ["L","Cycle example / lattice tabs"],["O","Slide overview (includes optional Chapter 25)"],["N","Toggle notes on this screen"],["P","Open synchronized presenter window"],["T","Start / pause the talk timer"],["F","Fullscreen"],["B","Blank the projection"],["Esc","Close a dialog or unblank"],["?","This help"] + ].map(([key,meaning])=>'<span><kbd>'+key+'</kbd></span><span>'+meaning+'</span>').join("")+ + '</div><p>Builds advance only when you ask. The talk timer keeps running while you switch to the graph viewer. For demos: Copy code → switch to viewer → paste → Compile.</p>'); + } + function toggleNotes() { + if(presenter) return; + $("notes").hidden=!$("notes").hidden; + $("notes-toggle").setAttribute("aria-pressed",String(!$("notes").hidden)); + } + function openPresenter() { + const url=new URL(location.href);url.searchParams.set("presenter","1"); + const child=window.open(url,"simple-rebase-presenter","width=1400,height=850"); + if(!child) toast("Allow popups for presenter mode, or press N for notes."); + } + async function fullscreen() { + try {if(document.fullscreenElement) await document.exitFullscreen();else await document.documentElement.requestFullscreen();} + catch (_) {toast("Use your browser’s fullscreen command.");} + } + function blank() {$("blackout").hidden=!$("blackout").hidden;} + $("previous").addEventListener("click",()=>action("previous")); + $("next").addEventListener("click",()=>action("next")); + $("overview").addEventListener("click",overview); + $("help").addEventListener("click",help); + $("notes-toggle").addEventListener("click",toggleNotes); + $("presenter").addEventListener("click",openPresenter); + $("timer").addEventListener("click",()=>action("timer")); + $("timer-reset").addEventListener("click",()=>action("reset")); + $("fullscreen").addEventListener("click",fullscreen); + $("dialog-close").addEventListener("click",()=>$("dialog").close()); + $("dialog").addEventListener("click",e=>{if(e.target===$("dialog")) $("dialog").close();}); + document.addEventListener("click",e=>{ + const b=e.target.closest("[data-copy],[data-command],[data-graph],[data-goto],[data-demo],button[data-view],button[data-backend],[data-language],[data-library-path],[data-language-copy]"); + if(!b) return; + if(b.dataset.goto) {if($("dialog").open) $("dialog").close();action("goto",b.dataset.goto);} + if(b.dataset.demo) action("demo",b.dataset.demo); + if(b.dataset.backend!==undefined) action("backend",b.dataset.backend); + if(b.dataset.language) action("language",b.dataset.language); + if(b.dataset.libraryPath!==undefined) action("library-path",b.dataset.libraryPath); + if(b.dataset.languageCopy) copy(b.dataset.languageCopy==='methods'?R.library.methods:R.library.files[state.library],b.dataset.languageCopy==='methods'?'Methods example':'Source file'); + if(b.dataset.view) action("view",b.dataset.view); + if(b.dataset.copy) copy(R.demos.find(d=>d.id===b.dataset.copy).code,"Demo code"); + if(b.dataset.command) { + const d=R.demos.find(d=>d.id===b.dataset.command); + copy("make -C "+(d.viewerChapter||d.chapter)+" view","Viewer command"); + } + if(b.dataset.graph) { + const d=R.demos.find(d=>d.id===b.dataset.graph); + openDialog('<h2>'+R.escape(d.title)+'</h2><img src="assets/'+d.id+'-final.svg" alt="Captured final compiler graph for '+R.escape(d.title)+'"><p>Captured from '+d.chapter+' after its viewer compilation phases. '+R.escape(d.inputs)+'.</p><p><a href="assets/'+d.id+'-final.svg" target="_blank" rel="noopener">Open full-size graph ↗</a></p>'); + } + }); + document.addEventListener("scroll",e=>{ + if(!e.target.matches?.('[data-library-scroll]')) return; + const top=e.target.scrollTop; + if(Math.abs(top-state.libraryScroll)>1) action("library-scroll",top); + },true); + document.addEventListener("keydown",e=>{ + if(e.ctrlKey||e.altKey||e.metaKey||e.repeat) return; + if(e.target.closest("textarea,input,select,[contenteditable=true]")) return; + if(!$("blackout").hidden) { + if(e.key==="b"||e.key==="B"||e.key==="Escape") {$("blackout").hidden=true;e.preventDefault();} + return; + } + if($("dialog").open) return; + if(e.target.closest('.library-text,.library-list') && ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','PageUp','PageDown','Home','End',' '].includes(e.key)) return; + let handled=true; + switch(e.key) { + case "ArrowRight": action("next");break; + case " ": if(e.target.closest("button,a")) {handled=false;break;} action("next");break; + case "ArrowLeft": action("previous");break; + case "PageDown": action("next-slide");break; + case "PageUp": action("previous-slide");break; + case "Home": action("goto","title");break; + case "End": action("goto","close");break; + case "o": case "O": overview();break; + case "l": case "L": action("cycle-view");break; + case "n": case "N": toggleNotes();break; + case "p": case "P": openPresenter();break; + case "t": case "T": action("timer");break; + case "f": case "F": fullscreen();break; + case "b": case "B": blank();break; + case "?": help();break; + case "Escape": if(!presenter) {$("notes").hidden=true;$("notes-toggle").setAttribute("aria-pressed","false");} break; + default: handled=false; + } + if(handled) e.preventDefault(); + }); + if(channel) channel.onmessage=e=>{ + const m=e.data; + if(m.type==="request"&&!presenter) broadcast(); + if(m.type==="command"&&!presenter) action(m.name,m.value,true); + if(m.type==="state"&&presenter) { + const changed=JSON.stringify(state)!==JSON.stringify(m.state); + const scrollOnly=changed && JSON.stringify({...state,libraryScroll:m.state.libraryScroll})===JSON.stringify(m.state); + Object.assign(state,m.state); + held=m.elapsed;started=m.running?Date.now():null; + if(changed && !scrollOnly) render(true); + else { + const source=$("slide").querySelector('[data-library-scroll]'); + if(scrollOnly && source) source.scrollTop=state.libraryScroll; + tick(); + } + } + }; + window.addEventListener("resize",fit); + window.addEventListener("hashchange",()=>{if(!presenter){readHash();render();}}); + readHash();render(); + if(presenter) {$("notes").hidden=false;if(channel) channel.postMessage({type:"request"});} + setInterval(()=>{tick();if(!presenter&&started!==null) broadcast();},1000); + // Small public surface for rehearsal and offline export tools. + R.go=id=>action("goto",id); + R.state=()=>({...state,elapsed:elapsed(),running:started!==null}); +})(); |
