/* * Swarm Rebuild * * Snapshots the current page into a blueprint (boxes, text blocks, media, * borders, repeated components), blanks the page, then lets a swarm of * multiplayer cursors rebuild it on a canvas: crosshair-drawn rects, typed * text, pen-tool lines, ruler guides with snapping, alt-drag duplicates. * * Injected by background.js; injecting again toggles it off. */ (() => { "use strict"; const KEY = "__swarmRebuild"; if (window[KEY]) { window[KEY].toggle(); return; } // ---------------------------------------------------------------- constants const NAMES = [ "Ada", "Linus", "Grace", "Alan", "Margaret", "Ken", "Barbara", "Dennis", "Radia", "Guido", "Frances", "Bjarne", "Hedy", "Tim", "Katherine", "Edsger", "Joan", "Donald", "Sophie", "Niklaus", "Anita", "Brian", "Mary", "Rob", "Ida", "Yukihiro", "Shafi", "Leslie", "Jean", "Vint", "Lynn", "Bram", ]; const COLORS = [ "#F24E1E", "#A259FF", "#1ABCFE", "#0ACF83", "#FF7262", "#FFC700", "#9747FF", "#E8467C", "#14AE5C", "#0D99FF", "#FF8A00", "#00B5CE", ]; const BLUE = "#0D99FF"; const RED = "#F24822"; const RULER = 20; const MIN_AGENTS = 8; const MAX_AGENTS = 48; const FLOCK_AGENTS = 36; const INTRO_AGENTS = 18; const MAX_ITEMS = 4000; const MAX_WORLD_H = 40000; const SVGNS = "http://www.w3.org/2000/svg"; const rand = (a, b) => a + Math.random() * (b - a); const clamp = (v, a, b) => Math.max(a, Math.min(b, v)); const easeInOut = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2); const easeOut = (t) => 1 - Math.pow(1 - t, 3); const bump = (it) => { it.v++; }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const nextFrame = () => new Promise((r) => requestAnimationFrame(() => r())); const f1 = (n) => Math.round(n * 10) / 10; function alphaOf(c) { if (!c || c === "transparent") return 0; const slash = c.match(/\/\s*([\d.]+%?)\s*\)/); if (slash) return slash[1].endsWith("%") ? parseFloat(slash[1]) / 100 : parseFloat(slash[1]); const m = c.match(/^rgba\(([^)]+)\)/); if (m) { const p = m[1].split(","); return p.length >= 4 ? parseFloat(p[3]) : 1; } return 1; } function intersect(a, b) { if (!a || !b) return null; const l = Math.max(a.l, b.l); const t = Math.max(a.t, b.t); const r = Math.min(a.r, b.r); const bt = Math.min(a.b, b.b); return r > l && bt > t ? { l, t, r, b: bt } : null; } const ltrb = (r) => ({ l: r.left, t: r.top, r: r.right, b: r.bottom }); // ----------------------------------------------------------- cursor shapes const ARROW_D = "M4.6 3.9 C4.2 3.1 4.9 2.4 5.7 2.8 L20.6 9.4 C21.5 9.8 21.4 11.1 20.4 11.3 L13.9 12.9 C13.4 13 13 13.4 12.9 13.9 L11.3 20.4 C11.1 21.4 9.8 21.5 9.4 20.6 Z"; const HOT = { arrow: [5, 3.5], dup: [5, 3.5], pen: [3, 3], "pen-add": [3, 3], "pen-close": [3, 3], cross: [12, 12], ibeam: [12, 12], ew: [12, 12], ns: [12, 12], }; function cursorSVG(shape, c) { const st = 'stroke="#fff" stroke-width="1.5" stroke-linejoin="round"'; switch (shape) { case "arrow": return ``; case "dup": return ( `` + `` ); case "pen": case "pen-add": case "pen-close": { let s = `` + `` + `` + ``; if (shape === "pen-add") s += `` + ``; if (shape === "pen-close") s += `` + ``; return s; } case "cross": return ( `` + `` ); case "ibeam": { const d = "M8.5 4.5 H10.5 Q12 4.5 12 6 Q12 4.5 13.5 4.5 H15.5 M12 6 V18 M8.5 19.5 H10.5 Q12 19.5 12 18 Q12 19.5 13.5 19.5 H15.5"; return ( `` + `` ); } case "ew": case "ns": return ``; } return ""; } const IMG_ICON = ''; // ------------------------------------------------------------------ capture const SKIP = new Set([ "script", "style", "noscript", "template", "head", "meta", "link", "title", "base", "datalist", "area", "map", "source", "track", "param", "swarm-rebuild", ]); const DROP_IN_CLONE = new Set(["script", "style", "noscript", "template", "iframe", "object", "embed", "link", "meta"]); const REPLACED = new Set(["img", "svg", "video", "canvas", "iframe", "embed", "object", "picture"]); const FORMS = new Set(["input", "textarea", "select"]); const ATOMIC = new Set(["img", "svg", "video", "canvas", "input", "select", "textarea", "iframe", "picture", "button"]); const TEXT_PROPS = [ "color", "font-family", "font-size", "font-weight", "font-style", "font-variant", "font-stretch", "font-feature-settings", "font-variation-settings", "font-kerning", "font-optical-sizing", "line-height", "letter-spacing", "word-spacing", "text-transform", "text-align", "text-indent", "text-decoration-line", "text-decoration-color", "text-decoration-style", "text-decoration-thickness", "text-underline-offset", "text-shadow", "white-space", "white-space-collapse", "text-wrap-mode", "text-wrap-style", "direction", "overflow-wrap", "word-break", "hyphens", "tab-size", "-webkit-text-fill-color", "-webkit-text-stroke-width", "-webkit-text-stroke-color", "text-rendering", "-webkit-font-smoothing", "list-style-type", "list-style-position", "list-style-image", "writing-mode", ]; const BOX_PROPS = [ "display", "box-sizing", "width", "height", "min-width", "min-height", "max-width", "max-height", "padding-top", "padding-right", "padding-bottom", "padding-left", "margin-top", "margin-right", "margin-bottom", "margin-left", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", "border-top-style", "border-right-style", "border-bottom-style", "border-left-style", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "background-color", "background-image", "background-size", "background-position", "background-repeat", "background-clip", "-webkit-background-clip", "box-shadow", "opacity", "vertical-align", "position", "top", "right", "bottom", "left", "flex-direction", "flex-wrap", "align-items", "justify-content", "gap", "flex-grow", "flex-shrink", "flex-basis", "align-self", "object-fit", "object-position", "overflow-x", "overflow-y", "text-overflow", "-webkit-line-clamp", "-webkit-box-orient", "visibility", "transform", "filter", "float", "fill", "stroke", "clip-path", "mix-blend-mode", "-webkit-mask-image", "-webkit-mask-size", "-webkit-mask-position", "-webkit-mask-repeat", ]; const ELEM_PROPS = [...TEXT_PROPS, ...BOX_PROPS]; const SVG_PROPS = [ "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "stroke-dasharray", "stroke-dashoffset", "stroke-miterlimit", "fill-opacity", "stroke-opacity", "fill-rule", "clip-rule", "opacity", "color", "stop-color", "stop-opacity", "display", "visibility", "font-family", "font-size", "font-weight", "text-anchor", "dominant-baseline", "letter-spacing", "mix-blend-mode", "filter", ]; const SVG_OUTER_PROPS = [ "margin-top", "margin-right", "margin-bottom", "margin-left", "vertical-align", "display", "position", "top", "right", "bottom", "left", "transform", "transform-origin", "flex-shrink", "flex-grow", "align-self", "order", "overflow", ]; const RECT_PROPS = [ "background-color", "background-image", "background-size", "background-position", "background-repeat", "background-origin", "background-clip", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", "border-top-style", "border-right-style", "border-bottom-style", "border-left-style", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "box-shadow", "backdrop-filter", "filter", "clip-path", "-webkit-mask-image", "-webkit-mask-size", "-webkit-mask-position", "-webkit-mask-repeat", ]; function styleString(cs, props) { let s = ""; for (const p of props) { const v = cs.getPropertyValue(p); if (v !== "") s += `${p}:${v};`; } return s; } const lhPx = (cs) => { const v = parseFloat(cs.lineHeight); return isNaN(v) ? (parseFloat(cs.fontSize) || 16) * 1.2 : v; }; function kidsOf(el) { if (el.shadowRoot) return [...el.shadowRoot.childNodes]; if (el.localName === "slot") { const a = el.assignedNodes({ flatten: true }); return a.length ? a : [...el.childNodes]; } return [...el.childNodes]; } // Copy one element's computed style onto its clone, fix up media sources. // ::before / ::after become real spans so icons, chevrons and separators survive. function pseudoClone(src, which) { const ps = getComputedStyle(src, which); const c = ps.content; if (!c || c === "none" || c === "normal" || ps.display === "none") return null; const sp = document.createElement("span"); const str = c.match(/^"((?:[^"\\]|\\.)*)"/); if (str) sp.textContent = str[1].replace(/\\(.)/g, "$1"); else if (/^url\(/.test(c)) { const u = c.match(/^url\(\s*["']?([^"')]+)["']?\s*\)/); if (u) { const im = document.createElement("img"); im.src = u[1]; im.setAttribute("style", "display:inline-block;vertical-align:middle;"); sp.appendChild(im); } } sp.setAttribute("style", styleString(ps, ELEM_PROPS)); return sp; } function addPseudos(src, dst) { const b = pseudoClone(src, "::before"); if (b) dst.insertBefore(b, dst.firstChild); const a = pseudoClone(src, "::after"); if (a) dst.appendChild(a); } function prepClone(src, dst) { const tag = src.localName; if (DROP_IN_CLONE.has(tag)) { dst.remove(); return null; } const cs = getComputedStyle(src); if (cs.display === "none") { dst.remove(); return null; } const isSvg = src instanceof SVGElement; let st = styleString(cs, isSvg ? SVG_PROPS : ELEM_PROPS); if (isSvg && tag === "svg" && !src.ownerSVGElement) { dst.setAttribute("width", src.clientWidth || src.getBoundingClientRect().width); dst.setAttribute("height", src.clientHeight || src.getBoundingClientRect().height); st += styleString(cs, SVG_OUTER_PROPS); } dst.setAttribute("style", st); for (const a of [...dst.attributes]) { const n = a.name; if (n.startsWith("on") || n === "contenteditable" || n === "tabindex" || n === "autofocus" || n === "srcset" || n === "sizes" || n === "loading") dst.removeAttribute(n); } if (tag === "img") { dst.setAttribute("src", imgSrc(src)); dst.setAttribute("decoding", "async"); } if (tag === "svg") fixSvgRefs(src, dst); // Clones live in the page's document: custom elements would be upgraded by the // site's own JS (and often wipe their content), media would play. Swap in inert stand-ins. if (tag === "audio") { dst.remove(); return null; } if (tag === "video" || tag === "canvas" || (!isSvg && tag.includes("-"))) { let rep; const vurl = tag === "video" ? videoUrl(src) : null; if (vurl) { rep = document.createElement("video"); for (const [k, v] of [["src", vurl], ["muted", ""], ["playsinline", ""], ["preload", "auto"]]) rep.setAttribute(k, v); if (!src.paused || src.autoplay) rep.setAttribute("autoplay", ""); if (src.loop || src.autoplay || !src.paused) rep.setAttribute("loop", ""); if (src.poster) rep.setAttribute("poster", src.poster); } else if (tag === "video" || tag === "canvas") { let url = tag === "video" ? src.poster : canvasPixels(src); rep = document.createElement(url ? "img" : "span"); if (url) rep.setAttribute("src", url); else st += "background:#1b1b1f;"; } else { rep = document.createElement("span"); while (dst.firstChild) rep.appendChild(dst.firstChild); } rep.setAttribute("style", st); if (dst.parentNode) dst.replaceWith(rep); dst = rep; } if (!isSvg && !REPLACED.has(tag) && !FORMS.has(tag) && tag !== "img") addPseudos(src, dst); return dst; } // Clones are made in a document without a custom-element registry so the // site's component code never runs on them. let inertDoc = null; const inert = () => inertDoc || (inertDoc = document.implementation.createHTMLDocument("")); function cloneStyled(src) { const dst = inert().importNode(src, true); const a = [...src.querySelectorAll("*")]; const b = [...dst.querySelectorAll("*")]; const root = prepClone(src, dst) || document.createElement("span"); for (let i = 0; i < a.length && i < b.length; i++) prepClone(a[i], b[i]); return root; } function cloneDescendantsStyled(src, dst) { const a = src.querySelectorAll("*"); const b = dst.querySelectorAll("*"); for (let i = 0; i < a.length && i < b.length; i++) prepClone(a[i], b[i]); } function imgSrc(img) { let s = img.currentSrc || img.src || ""; const lazy = img.getAttribute("data-src") || img.getAttribute("data-lazy-src") || img.getAttribute("data-original"); if (lazy && (!s || s.startsWith("data:") || !img.complete || !img.naturalWidth)) { try { s = new URL(lazy, location.href).href; } catch (_) {} } return s; } // Shadow trees can't see document-level / ids, so // inline anything the clone references. function fixSvgRefs(src, dst) { for (const u of dst.querySelectorAll("use")) { const href = u.getAttribute("href") || u.getAttribute("xlink:href"); if (!href || href[0] !== "#") continue; const ref = document.getElementById(href.slice(1)); if (!ref) continue; const isSym = ref.localName === "symbol"; const g = document.createElementNS(SVGNS, isSym ? "svg" : "g"); if (isSym) { for (const at of ["viewBox", "preserveAspectRatio"]) if (ref.getAttribute(at)) g.setAttribute(at, ref.getAttribute(at)); g.setAttribute("width", u.getAttribute("width") || "100%"); g.setAttribute("height", u.getAttribute("height") || "100%"); g.setAttribute("x", u.getAttribute("x") || 0); g.setAttribute("y", u.getAttribute("y") || 0); g.setAttribute("overflow", "visible"); } else if (u.getAttribute("x") || u.getAttribute("y")) { g.setAttribute("transform", `translate(${u.getAttribute("x") || 0} ${u.getAttribute("y") || 0})`); } const us = u.getAttribute("style"); if (us) g.setAttribute("style", us); for (const ch of ref.childNodes) { if (ch.nodeType !== 1) { g.appendChild(ch.cloneNode(true)); continue; } g.appendChild(cloneStyled(ch)); } u.replaceWith(g); } const html = dst.outerHTML; const ids = new Set(); html.replace(/url\(\s*["']?#([^"')\s]+)["']?\s*\)/g, (_, id) => ids.add(id)); if (!ids.size) return; let defs = null; for (const id of ids) { if (dst.querySelector(`[id="${CSS.escape(id)}"]`)) continue; const ref = document.getElementById(id); if (!ref) continue; if (!defs) { defs = document.createElementNS(SVGNS, "defs"); dst.insertBefore(defs, dst.firstChild); } defs.appendChild(ref.cloneNode(true)); } } // Pixels for things the DOM can't give back (WebGL canvases, playing video, // cross-origin iframes) come from a screenshot taken when the user clicked. function cropShot(shot, r) { if (!shot || !shot.valid) return null; if (r.height > innerHeight * 3 || r.width > innerWidth * 2) return null; const vis = intersect(ltrb(r), { l: 0, t: 0, r: innerWidth, b: innerHeight }); if (!vis) return null; const k = shot.scale; const c = document.createElement("canvas"); c.width = Math.max(1, Math.round(r.width * k)); c.height = Math.max(1, Math.round(r.height * k)); const w = (vis.r - vis.l) * k; const h = (vis.b - vis.t) * k; c.getContext("2d").drawImage(shot.img, vis.l * k, vis.t * k, w, h, (vis.l - r.left) * k, (vis.t - r.top) * k, w, h); const full = vis.l <= r.left + 1 && vis.t <= r.top + 1 && vis.r >= r.right - 1 && vis.b >= r.bottom - 1; try { return c.toDataURL(full ? "image/jpeg" : "image/webp", 0.9); } catch (_) { return null; } } // A URL another