The LMGTFY part: a link with ?q= types the question into the box, then asks. Everything after the submit is the server's: Datastar morphs what it streams. Only the home page has the box; the code and rules pages do not.
4(() => { 5 const form = document.getElementById("ask"); 6 if (!form) return; 7 const box = document.getElementById("q"); 8 const question = form.dataset.type; 9 const calm = matchMedia("(prefers-reduced-motion: reduce)").matches; 10 const wait = (ms) => new Promise((done) => setTimeout(done, calm ? 0 : ms));
Datastar loads as a module, after this script. When it binds the box it writes its (empty) signal into it, so anything typed before then is lost. This listener is in place before the module runs, so it cannot miss the event.
15 const bound = new Promise((done) => document.addEventListener("datastar-ready", done, { once: true }));
17 async function type() { 18 await bound; 19 box.value = ""; 20 await wait(700); 21 for (const ch of question) { 22 box.value += ch; 23 // Datastar's bind listens for input: the signal follows the box. 24 box.dispatchEvent(new Event("input", { bubbles: true })); 25 await wait(45 + Math.random() * 70); 26 } 27 await wait(500); 28 form.requestSubmit(); 29 } 30 if (question) type();
What this page asked last, so the toast for it is not shown back to it.
/live: the archive tells every open browser how many are open and where,
and sends a toast when someone asks a question or clones the code. The
socket is a SharedWorker's (/live.js), one for all this browser's tabs of
this build; a browser without SharedWorker holds its own, per tab. Either
reconnects, waiting longer each time, if the socket drops.
41(() => { 42 const online = document.getElementById("online"); 43 const toasts = document.getElementById("toasts"); 44 if (!online || !toasts) return; 45 const mine = document.querySelector('meta[name="lmjtfy-build"]')?.content ?? ""; 46 function heard(text) { 47 let live; 48 try { live = JSON.parse(text); } catch { return; } 49 if (typeof live.online === "number") { 50 online.textContent = `${live.online} online`; 51 online.hidden = false; 52 } 53 if (live.closed) online.hidden = true; 54 if (typeof live.toast === "string") toast(live.toast); 55 if (typeof live.patch === "string") patch(live.patch); 56 if (typeof live.build === "string") built(live.build); 57 } 58 function shared() { 59 const worker = new SharedWorker(`/live.js?build=${encodeURIComponent(mine)}`, { name: `lmjtfy-live-${mine}` }); 60 worker.port.addEventListener("message", (event) => heard(event.data)); 61 worker.port.start(); 62 addEventListener("pagehide", () => worker.port.postMessage("bye"), { once: true }); 63 } 64 let wait = 1000; 65 function connect() { 66 // The page's build, so the archive tells only an earlier page what a 67 // deploy changed. 68 const socket = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/live?build=${encodeURIComponent(mine)}`); 69 socket.addEventListener("open", () => { wait = 1000; }); 70 socket.addEventListener("message", (event) => heard(event.data)); 71 socket.addEventListener("close", () => { 72 online.hidden = true; 73 setTimeout(connect, wait); 74 wait = Math.min(wait * 2, 60000); 75 }); 76 } 77 function start() { 78 if (typeof SharedWorker === "function") { 79 try { shared(); return; } catch { /* a browser that refuses it: hold our own */ } 80 } 81 connect(); 82 } 83 // Back from the back-forward cache: the worker forgot this tab when it 84 // said goodbye, so it joins again. 85 addEventListener("pageshow", (event) => { if (event.persisted && typeof SharedWorker === "function") shared(); }); 86 // The site was deployed since this page was rendered: a toast that stays 87 // until it is used or dismissed, once. 88 let offered = false; 89 function built(build) { 90 if (!mine || build === mine || offered) return; 91 offered = true; 92 const shown = document.createElement("div"); 93 shown.className = "toast stays"; 94 shown.innerHTML = 'A new version of the site is live. <button type="button" class="reload">Reload</button> <button type="button" class="ghost dismiss" aria-label="Dismiss">×</button>'; 95 shown.querySelector(".reload").addEventListener("click", () => location.reload()); 96 shown.querySelector(".dismiss").addEventListener("click", () => shown.remove()); 97 toasts.append(shown); 98 } 99 // Each element sent replaces this page's element with the same id, if it 100 // has one: the feeds on the home page, and whatever else is sent this way. 101 // One with `data-prepend` puts its children at the top of the element it 102 // names instead, each taking the place of its own id wherever that was, 103 // so what this page has scrolled in further down stays. 104 function patch(html) { 105 const holder = document.createElement("template"); 106 holder.innerHTML = html; 107 for (const fresh of [...holder.content.children]) { 108 const into = fresh.dataset.prepend && document.getElementById(fresh.dataset.prepend); 109 if (into) { 110 for (const line of [...fresh.children].reverse()) { 111 if (line.id) document.getElementById(line.id)?.remove(); 112 into.prepend(line); 113 } 114 } else if (fresh.id) { 115 document.getElementById(fresh.id)?.replaceWith(fresh); 116 } 117 } 118 } 119 // The markup is the server's, escaped there (view/toast.rs). 120 function toast(html) { 121 const holder = document.createElement("div"); 122 holder.innerHTML = html; 123 const shown = holder.firstElementChild; 124 if (!shown || (shown.dataset.q && shown.dataset.q === window.lmjtfyAsked)) return; 125 toasts.append(shown); 126 // The oldest passing toasts go first; one that stays is never pushed out. 127 const passing = [...toasts.children].filter((toast) => !toast.classList.contains("stays")); 128 while (passing.length > 3) passing.shift().remove(); 129 setTimeout(() => { shown.classList.add("gone"); setTimeout(() => shown.remove(), 400); }, 7000); 130 } 131 start(); 132})();
The copy buttons, on every page: data-copy names the element whose link
or text is copied.
136(() => { 137 document.addEventListener("click", (event) => { 138 const button = event.target.closest("[data-copy]"); 139 if (!button) return; 140 const link = document.getElementById(button.dataset.copy); 141 navigator.clipboard.writeText(link.href ?? link.textContent).then( 142 () => { button.textContent = "Copied"; }, 143 () => { getSelection().selectAllChildren(link); button.textContent = "Selected, press Ctrl+C"; } 144 ); 145 }); 146})();
The dot fields behind the page, after typesafe.ai: soft blobs put through an 8x8 ordered dither, one image pixel per dot, tiled. Drawn once into CSS variables rather than into elements, because the transcript is replaced wholesale and a canvas inside it would be wiped with it.
152(() => { 153 const BAYER = [ 154 [0, 32, 8, 40, 2, 34, 10, 42], [48, 16, 56, 24, 50, 18, 58, 26], 155 [12, 44, 4, 36, 14, 46, 6, 38], [60, 28, 52, 20, 62, 30, 54, 22], 156 [3, 35, 11, 43, 1, 33, 9, 41], [51, 19, 59, 27, 49, 17, 57, 25], 157 [15, 47, 7, 39, 13, 45, 5, 37], [63, 31, 55, 23, 61, 29, 53, 21], 158 ]; 159 const W = 400, H = 300; 160 // A fixed seed: the same field on every load. 161 const random = (seed) => () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296); 162 163 function field(seed, count, strength, [r, g, b]) { 164 const next = random(seed); 165 const blobs = Array.from({ length: count }, () => ({ 166 x: next() * W, y: next() * H, size: 16 + next() * 30, weight: (0.5 + next() * 0.5) * strength, 167 })); 168 const canvas = document.createElement("canvas"); 169 canvas.width = W; canvas.height = H; 170 const context = canvas.getContext("2d"); 171 const image = context.createImageData(W, H); 172 for (let y = 0; y < H; y++) { 173 for (let x = 0; x < W; x++) { 174 let value = 0; 175 for (const blob of blobs) { 176 // Distances wrap, so the tile has no seam. 177 const dx = Math.min(Math.abs(x - blob.x), W - Math.abs(x - blob.x)); 178 const dy = Math.min(Math.abs(y - blob.y), H - Math.abs(y - blob.y)); 179 value += blob.weight * Math.exp(-(dx * dx + dy * dy) / (2 * blob.size * blob.size)); 180 } 181 if (value > (BAYER[y % 8][x % 8] + 0.5) / 64) { 182 const at = (y * W + x) * 4; 183 image.data[at] = r; image.data[at + 1] = g; image.data[at + 2] = b; image.data[at + 3] = 255; 184 } 185 } 186 } 187 context.putImageData(image, 0, 0); 188 return `url(${canvas.toDataURL()})`; 189 } 190 191 const root = document.documentElement.style; 192 root.setProperty("--dither-paper", field(7, 4, 0.55, [196, 196, 196])); 193 root.setProperty("--dither-pink", field(23, 5, 0.9, [30, 30, 30])); 194})();
A diagram or picture in a document opens full size on a click: mermaid's drawings and the rules' SVG are too small to read inline. Escape, the close button or a click on the backdrop closes it.
199(() => { 200 let dialog; 201 function open(figure) { 202 if (!dialog) { 203 dialog = document.createElement("dialog"); 204 dialog.className = "zoom"; 205 dialog.innerHTML = '<button type="button" class="ghost close">Close</button><div class="zoomed"></div>'; 206 dialog.addEventListener("click", (event) => { 207 if (event.target === dialog || event.target.closest(".close")) dialog.close(); 208 }); 209 document.body.append(dialog); 210 } 211 const zoomed = dialog.querySelector(".zoomed"); 212 const copy = figure.cloneNode(true); 213 copy.removeAttribute("style"); 214 copy.removeAttribute("width"); 215 copy.removeAttribute("height"); 216 zoomed.replaceChildren(copy); 217 dialog.showModal(); 218 } 219 document.addEventListener("click", (event) => { 220 const figure = event.target.closest(".md pre.mermaid svg, .md img"); 221 if (figure && !event.target.closest("dialog")) open(figure); 222 }); 223})();
What only the page knows, told to /seen (the archive keeps it as a
read event): how long this page was in view, how far down it was seen,
and the screen. It is said each time the page is put away, as the time in
view since it last said, so a tab left open and come back to adds up. A
link followed off the site is said as it is followed (out).
230(() => { 231 if (!navigator.sendBeacon) return; 232 let since = document.visibilityState === "visible" ? performance.now() : null; 233 let seen = 0; 234 let deepest = 0; 235 // The home page scrolls the window; a code page scrolls its pane. 236 const measure = () => { 237 const pane = document.querySelector("main.pane"); 238 const top = pane ? pane.scrollTop : scrollY; 239 const tall = pane ? pane.scrollHeight : document.documentElement.scrollHeight; 240 const high = pane ? pane.clientHeight : innerHeight; 241 if (tall > 0) deepest = Math.max(deepest, Math.min(1, (top + high) / tall)); 242 }; 243 const say = (more) => navigator.sendBeacon("/seen", JSON.stringify({ 244 path: location.pathname, query: location.search, ms: 0, scroll: deepest, 245 screen: `${screen.width}x${screen.height}`, viewport: `${innerWidth}x${innerHeight}`, ...more, 246 })); 247 addEventListener("scroll", measure, { passive: true, capture: true }); 248 document.addEventListener("visibilitychange", () => { 249 if (document.visibilityState === "visible") { since = performance.now(); return; } 250 if (since === null) return; 251 measure(); 252 seen = performance.now() - since; 253 since = null; 254 // A glance of under a second is not a reading. 255 if (seen >= 1000) say({ ms: Math.round(seen) }); 256 }); 257 document.addEventListener("click", (event) => { 258 const link = event.target.closest && event.target.closest("a[href]"); 259 if (link && link.host && link.host !== location.host && /^https?:$/.test(link.protocol)) say({ out: link.href }); 260 }, { capture: true }); 261 measure(); 262})();