1/* desktop.js v2 - shared behaviors for the desktop language. 2 1. The HUMAN / MACHINE pill. Built here (no markup on the page) and 3 docked into the page flow: the last child of main.desk, or the node 4 after .page on the older pages. CSS makes it sticky, so it floats at 5 the bottom right while the page is longer than the screen and rests 6 in its own slot above the footer once the reader arrives there. One 7 element, no scroll listener. 8 2. MACHINE opens the page's markdown twin, <dir>/index.md, the page as 9 agents read it: one full-width window at the top of the desk, the 10 other windows hidden. HUMAN, H or Escape puts the page back; M 11 toggles. No API, no seam arithmetic: the same window every time. 12 3. App menu, draggable windows, rotator: as before. 13 Progressive: with JS off there is no pill (it does nothing without JS) 14 and the rotator shows its first image (mark it .is-on in markup). */ 15(function () { 16 var LOADING = "loading the roomâ¦"; 17 var FAILED = "couldn't load this one. it'll come back. â»"; 18 var ORIGIN = "https://www.halimmadi.com"; 19 20 function el(tag, cls, text) { 21 var n = document.createElement(tag); 22 if (cls) n.className = cls; 23 if (text != null) n.textContent = text; 24 return n; 25 } 26 function isEditable(t) { 27 if (!t) return false; 28 var tag = t.tagName; 29 return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || t.isContentEditable === true; 30 } 31 32 // ââ the page's twin ââââââââââââââââââââââââââââââââââââââââ 33 // "/about/" -> "/about/index.md"; "/" -> "/index.md"; a path without its 34 // trailing slash resolves the same way. 35 function pagePath() { 36 var p = location.pathname || "/"; 37 p = p.replace(/index\.html$/, ""); 38 if (p.charAt(p.length - 1) !== "/") p += "/"; 39 return p; 40 } 41 function twinPath() { 42 return pagePath() + "index.md"; 43 } 44 45 // ââ the pill âââââââââââââââââââââââââââââââââââââââââââââââ 46 var pill = null; 47 var humanBtn = null; 48 var machineBtn = null; 49 50 function option(mode, on) { 51 var b = el("button", "mode-toggle__option" + (on ? " is-active" : "")); 52 b.type = "button"; 53 b.setAttribute("data-mode", mode); 54 b.setAttribute("aria-pressed", String(on)); 55 var radio = el("span", "mode-toggle__radio"); 56 radio.setAttribute("aria-hidden", "true"); 57 b.appendChild(radio); 58 b.appendChild(el("span", "mode-toggle__label", mode)); 59 b.addEventListener("click", function () { 60 setMode(mode === "machine"); 61 }); 62 return b; 63 } 64 function buildPill() { 65 if (document.querySelector(".mode-toggle")) return; 66 pill = el("div", "mode-toggle"); 67 pill.setAttribute("role", "group"); 68 pill.setAttribute("aria-label", "human or machine"); 69 humanBtn = option("human", true); 70 machineBtn = option("machine", false); 71 pill.appendChild(humanBtn); 72 pill.appendChild(machineBtn); 73 var desk = document.querySelector("main.desk"); 74 var page = document.querySelector(".page"); 75 var footer = document.querySelector("footer.fb"); 76 if (desk) desk.appendChild(pill); 77 else if (page && page.parentNode) page.parentNode.insertBefore(pill, page.nextSibling); 78 else if (footer && footer.parentNode) footer.parentNode.insertBefore(pill, footer); 79 else document.body.appendChild(pill); 80 } 81 function paintPill(machine) { 82 if (humanBtn) { 83 humanBtn.classList.toggle("is-active", !machine); 84 humanBtn.setAttribute("aria-pressed", String(!machine)); 85 } 86 if (machineBtn) { 87 machineBtn.classList.toggle("is-active", machine); 88 machineBtn.setAttribute("aria-pressed", String(machine)); 89 } 90 } 91 92 // ââ the machine window âââââââââââââââââââââââââââââââââââââ 93 var mx = { win: null, host: null, meta: null, slot: null, copy: null, md: "", scrollY: 0 }; 94 95 function tokens(text) { 96 var bytes; 97 try { 98 bytes = new TextEncoder().encode(text).length; 99 } catch (_) { 100 bytes = text.length; 101 } 102 var n = Math.max(100, Math.round(bytes / 4 / 100) * 100); 103 return "~" + String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " tokens"; 104 } 105 106 function buildWindow() { 107 var label = twinPath().replace(/^\//, ""); 108 var win = el("section", "win win--machine"); 109 win.setAttribute("data-machine-window", ""); 110 win.setAttribute("aria-label", "machine"); 111 112 var bar = el("div", "win__bar"); 113 var dots = el("span", "dots"); 114 dots.setAttribute("aria-hidden", "true"); 115 dots.appendChild(el("i")); 116 dots.appendChild(el("i")); 117 dots.appendChild(el("i")); 118 bar.appendChild(dots); 119 bar.appendChild(el("p", "win__t", label)); 120 var meta = el("span", "win__meta", ""); 121 meta.setAttribute("data-machine-meta", ""); 122 bar.appendChild(meta); 123 win.appendChild(bar); 124 125 var body = el("div", "win__b"); 126 body.appendChild(el("p", "note mx__lead", "this page, as agents read it. the same words, none of the furniture.")); 127 128 var cmd = el("div", "mx__cmd"); 129 cmd.appendChild(el("span", "k", "fetch")); 130 cmd.appendChild(el("code", "mx__code", 'curl -H "Accept: text/markdown" ' + ORIGIN + pagePath())); 131 var copy = el("button", "btn", "copy"); 132 copy.type = "button"; 133 copy.setAttribute("data-machine-copy", ""); 134 copy.addEventListener("click", copyTwin); 135 cmd.appendChild(copy); 136 body.appendChild(cmd); 137 138 var links = el("p", "mx__links"); 139 [ 140 ["open raw", twinPath()], 141 ["llms.txt", "/llms.txt"], 142 ["llms-full.txt", "/llms-full.txt"], 143 ].forEach(function (pair) { 144 var a = el("a", "k", pair[0]); 145 a.setAttribute("href", pair[1]); 146 links.appendChild(a); 147 }); 148 body.appendChild(links); 149 150 var slot = el("div", "mx__slot"); 151 slot.setAttribute("data-machine-slot", ""); 152 body.appendChild(slot); 153 win.appendChild(body); 154 155 mx.win = win; 156 mx.meta = meta; 157 mx.slot = slot; 158 mx.copy = copy; 159 return win; 160 } 161 162 function mountWindow(win) { 163 var desk = document.querySelector("main.desk"); 164 if (desk) { 165 desk.insertBefore(win, desk.firstElementChild); 166 mx.host = null; 167 return; 168 } 169 var host = el("main", "desk desk--machine"); 170 host.appendChild(win); 171 var page = document.querySelector(".page"); 172 var footer = document.querySelector("footer.fb"); 173 if (page && page.parentNode) page.parentNode.insertBefore(host, page); 174 else if (footer && footer.parentNode) footer.parentNode.insertBefore(host, footer); 175 else document.body.appendChild(host); 176 mx.host = host; 177 } 178 179 function showNote(text, retry) { 180 if (!mx.slot) return; 181 mx.slot.textContent = ""; 182 var p = el("p", "note mx__state", text); 183 if (retry) { 184 p.setAttribute("role", "button"); 185 p.setAttribute("tabindex", "0"); 186 p.addEventListener("click", loadTwin); 187 p.addEventListener("keydown", function (e) { 188 if (e.key === "Enter" || e.key === " ") { 189 e.preventDefault(); 190 loadTwin(); 191 } 192 }); 193 } 194 mx.slot.appendChild(p); 195 } 196 197 function loadTwin() { 198 if (!mx.slot) return; 199 mx.md = ""; 200 if (mx.meta) mx.meta.textContent = ""; 201 if (mx.copy) mx.copy.disabled = true; 202 showNote(LOADING, false); 203 var want = mx.win; 204 fetch(twinPath(), { headers: { Accept: "text/markdown" }, cache: "no-cache" }) 205 .then(function (r) { 206 if (!r.ok) throw new Error("twin " + r.status); 207 return r.text(); 208 }) 209 .then(function (text) { 210 if (mx.win !== want || !mx.slot) return; 211 mx.md = text; 212 mx.slot.textContent = ""; 213 var pre = el("pre", "mx__md", text); 214 pre.setAttribute("data-machine-md", ""); 215 mx.slot.appendChild(pre); 216 if (mx.meta) mx.meta.textContent = tokens(text); 217 if (mx.copy) mx.copy.disabled = false;
218 }) 219 .catch(function () { 220 if (mx.win !== want) return; 221 showNote(FAILED, true); 222 }); 223 } 224 225 function copyTwin() { 226 if (!mx.md || !mx.copy) return; 227 var btn = mx.copy; 228 var done = function () { 229 btn.textContent = "copied"; 230 setTimeout(function () { 231 btn.textContent = "copy"; 232 }, 1000); 233 }; 234 try { 235 navigator.clipboard.writeText(mx.md).then(done, function () {}); 236 } catch (_) { 237 /* no clipboard here: the raw file is one link away */ 238 } 239 } 240 241 function openMachine() { 242 if (mx.win && mx.win.isConnected) return; 243 mx.scrollY = window.scrollY || 0; 244 var win = buildWindow(); 245 mountWindow(win); 246 document.body.classList.add("machine-mode"); 247 loadTwin(); 248 window.scrollTo(0, 0); 249 } 250 function closeMachine() { 251 var node = mx.host || mx.win; 252 var wasOpen = !!(node && node.parentNode); 253 if (wasOpen) node.parentNode.removeChild(node); 254 mx.win = null; 255 mx.host = null; 256 mx.meta = null; 257 mx.slot = null; 258 mx.copy = null; 259 mx.md = ""; 260 document.body.classList.remove("machine-mode"); 261 if (wasOpen) window.scrollTo(0, mx.scrollY); 262 } 263 264 function setMode(machine) { 265 paintPill(machine); 266 if (machine) openMachine(); 267 else closeMachine(); 268 } 269 function isMachine() { 270 return document.body.classList.contains("machine-mode"); 271 } 272 273 buildPill(); 274 document.addEventListener("keydown", function (e) { 275 if (e.key === "Escape") { 276 if (isMachine()) { 277 e.preventDefault(); 278 setMode(false); 279 } 280 return; 281 } 282 if (isEditable(e.target) || e.metaKey || e.ctrlKey || e.altKey) return; 283 if (e.key === "m" || e.key === "M") { 284 e.preventDefault(); 285 setMode(!isMachine()); 286 } else if (e.key === "h" || e.key === "H") { 287 e.preventDefault(); 288 setMode(false); 289 } 290 }); 291 292 // ââ app menu (the site map behind the wordmark) âââââââââââ 293 var more = document.querySelector("[data-menu-toggle]"); 294 var drop = document.querySelector("[data-menu-drop]"); 295 if (more && drop) { 296 function closeMenu() { 297 drop.hidden = true; 298 more.setAttribute("aria-expanded", "false"); 299 } 300 more.addEventListener("click", function (e) { 301 e.stopPropagation(); 302 var open = drop.hidden; 303 drop.hidden = !open; 304 more.setAttribute("aria-expanded", String(open)); 305 }); 306 document.addEventListener("click", function (e) { 307 if (!drop.hidden && !drop.contains(e.target)) closeMenu(); 308 }); 309 document.addEventListener("keydown", function (e) { 310 if (e.key === "Escape") closeMenu(); 311 }); 312 } 313 314 // ââ draggable windows (desktop only: fine pointer, wide) ââ 315 var canDrag = 316 window.matchMedia && 317 window.matchMedia("(pointer: fine) and (min-width: 760px)").matches; 318 if (canDrag) { 319 var zTop = 10; 320 document.querySelectorAll(".win > .win__bar").forEach(function (bar) { 321 var win = bar.parentElement; 322 var dx = 0, dy = 0, sx = 0, sy = 0, ox = 0, oy = 0, active = false; 323 bar.addEventListener("pointerdown", function (e) { 324 if (e.button !== 0) return; 325 if (e.target.closest("a, button, input, select, textarea")) return; 326 active = true; 327 sx = e.clientX; sy = e.clientY; ox = dx; oy = dy; 328 win.classList.add("is-dragging", "is-moved"); 329 win.style.zIndex = String(++zTop); 330 bar.setPointerCapture(e.pointerId); 331 e.preventDefault(); 332 }); 333 bar.addEventListener("pointermove", function (e) { 334 if (!active) return; 335 dx = ox + (e.clientX - sx); 336 dy = oy + (e.clientY - sy); 337 win.style.transform = "translate(" + dx + "px," + dy + "px)"; 338 }); 339 function stop() { 340 if (!active) return; 341 active = false; 342 win.classList.remove("is-dragging"); 343 } 344 bar.addEventListener("pointerup", stop); 345 bar.addEventListener("pointercancel", stop); 346 bar.addEventListener("dblclick", function () { 347 dx = dy = 0; 348 win.style.transform = ""; 349 win.classList.remove("is-moved"); 350 }); 351 }); 352 } 353 354 // ââ rotator âââââââââââââââââââââââââââââââââââââââââââââââ 355 document.querySelectorAll(".rot").forEach(function (rot) { 356 var imgs = rot.querySelectorAll("img"); 357 if (imgs.length < 2) return; 358 var i = 0;
359 imgs.forEach(function (im, n) { 360 im.classList.toggle("is-on", n === 0); 361 }); 362 setInterval(function () { 363 imgs[i].classList.remove("is-on"); 364 i = (i + 1) % imgs.length; 365 imgs[i].classList.add("is-on"); 366 var cap = rot.querySelector(".rot__cap"); 367 if (cap && imgs[i].dataset.cap) cap.textContent = imgs[i].dataset.cap; 368 }, 5000); 369 }); 370})(); 371 372/* âââââââââââââââââââââââââââââââââââââââââââ 373 Analytics: Umami, privacy-first and cookieless. This used to ride in 374 chrome.js; the desktop language replaced that file, so it lives here. 375 Event names are an API. Never rename them: dashboards and goals 376 depend on the history. Shared across every halimmadi.com surface. 377 âââââââââââââââââââââââââââââââââââââââââââ */ 378(function () { 379 var s = document.createElement("script"); 380 s.defer = true; 381 s.src = "https://umami-three-dun.vercel.app/script.js"; 382 s.setAttribute("data-website-id", "e43d9630-c1e2-4580-abf9-da675306fc6d"); 383 document.head.appendChild(s); 384})(); 385 386/* Funnel events by delegated click. connect-cta is every path into the 387 form; the form itself fires booking-inquiry on success (connect/). */ 388document.addEventListener( 389 "click", 390 function (e) { 391 var a = e.target && e.target.closest ? e.target.closest("a[href]") : null; 392 if (!a || !window.umami) return; 393 var href = a.getAttribute("href") || ""; 394 var from = location.pathname; 395 var label = (a.textContent || "").trim().replace(/\s+/g, " ").slice(0, 40); 396 if (/^\/connect\/?(#.*)?$/.test(href) || /halimmadi\.com\/connect/.test(href)) { 397 window.umami.track("connect-cta", { from: from, label: label }); 398 } else if (/instagram\.com\/yalla_halim/i.test(href)) { 399 window.umami.track("instagram-click", { from: from }); 400 } else if (/halimmadi\.substack\.com/i.test(href)) { 401 window.umami.track("substack-click", { from: from }); 402 } else if (/^mailto:/i.test(href)) { 403 window.umami.track("email-click", { from: from }); 404 } 405 }, 406 true 407);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.