@dloizides/ui-motion 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/{constants-BrJ7VJYu.d.mts → constants-DXSyrmvQ.d.mts} +9 -2
- package/dist/{constants-BrJ7VJYu.d.ts → constants-DXSyrmvQ.d.ts} +9 -2
- package/dist/hairline/native.d.mts +2 -2
- package/dist/hairline/native.d.ts +2 -2
- package/dist/hairline/native.js +62 -30
- package/dist/hairline/native.js.map +1 -1
- package/dist/hairline/native.mjs +63 -32
- package/dist/hairline/native.mjs.map +1 -1
- package/dist/hairline/web.d.mts +2 -2
- package/dist/hairline/web.d.ts +2 -2
- package/dist/hairline/web.js +16 -5
- package/dist/hairline/web.js.map +1 -1
- package/dist/hairline/web.mjs +16 -6
- package/dist/hairline/web.mjs.map +1 -1
- package/package.json +1 -1
package/dist/hairline/native.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useRef, useMemo, useEffect } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import { WebView } from 'react-native-webview';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -9,35 +9,46 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
9
9
|
var HAIRLINE_TEST_ID = "motion-hairline-figure";
|
|
10
10
|
var HAIRLINE_ASPECT_RATIO = 5 / 4;
|
|
11
11
|
var DEFAULT_HAIRLINE_INTENSITY = 0.5;
|
|
12
|
+
var HAIRLINE_HANDLE_GLOBAL = "__dloizidesHairline";
|
|
12
13
|
|
|
13
14
|
// src/hairline/data/hairlineBundle.ts
|
|
14
15
|
var HAIRLINE_BUNDLE = '// src/core/styles.ts\nvar LIGHT = { plate: "#ffffff", hi: "#232327", edge: "#a4a4ac", mid: "#c3c3c9", lo: "#e0e0e4" };\nvar DARK = { plate: "#08090a", hi: "#d0d6e0", edge: "#5b5d64", mid: "#3e3e44", lo: "#29292d" };\nvar KEYS = ["plate", "hi", "edge", "mid", "lo"];\nvar vars = (p) => KEYS.map((k) => `--hl-${k}:var(--hairline-${k},${p[k]});`).join("");\nvar EASE = "cubic-bezier(0.5,0,0.1,1)";\nvar SVG = ":where([data-hairline]>svg)";\nfunction css(lightDark) {\n const both = Object.fromEntries(KEYS.map((k) => [k, `light-dark(${LIGHT[k]},${DARK[k]})`]));\n return [\n // the box, and the palette: light unless something below says otherwise\n `:where([data-hairline]){display:block;position:relative;aspect-ratio:5/4;touch-action:pan-y;user-select:none;-webkit-user-select:none;--hl-sw:var(--hairline-stroke,0.9);${vars(LIGHT)}}`,\n // the page\'s color-scheme\n lightDark ? `:where([data-hairline]){${vars(both)}}` : "",\n // an ancestor that says dark\n `:where(.dark,[data-theme="dark"]) :where([data-hairline]){${vars(DARK)}}`,\n // the figure\'s own theme option\n `:where([data-hairline][data-hairline-theme="light"]){${vars(LIGHT)}}`,\n `:where([data-hairline][data-hairline-theme="dark"]){${vars(DARK)}}`,\n `:where([data-hairline]:focus-visible){outline:1.5px solid var(--hl-hi);outline-offset:2px}`,\n `${SVG}{position:absolute;inset:0;width:100%;height:100%;display:block}`,\n // Riffle\'s live region: read, not seen\n `:where([data-hairline]>[data-hairline-live]){position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}`,\n // the drawing: plates are filled with the plate colour and painted back to front\n `${SVG} :where(path,polygon,ellipse,line){fill:var(--hl-plate);stroke:var(--hl-mid);stroke-width:var(--hl-sw);vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round;transition:stroke 260ms ${EASE}}`,\n `${SVG} :where(.nf){fill:none}`,\n `${SVG} :where(.fo){stroke:none}`,\n `${SVG} :where(.sil){stroke:var(--hl-edge)}`,\n `${SVG} :where(.hi){stroke:var(--hl-hi)}`,\n `${SVG} :where(.lo){stroke:var(--hl-lo)}`,\n `${SVG} :where(.dash){stroke-dasharray:1 3}`,\n `${SVG} :where(.dot){stroke:none;fill:var(--hl-hi);transition:fill 260ms ${EASE}}`,\n `${SVG} :where(.dot.m){fill:var(--hl-edge)}`,\n `${SVG} :where(.dot.off){fill:var(--hl-lo)}`,\n `${SVG} :where(.ghost path){fill:none;stroke:var(--hl-mid)}`\n ].join("");\n}\nvar done = /* @__PURE__ */ new WeakSet();\nfunction inject(root) {\n if (done.has(root)) return;\n done.add(root);\n const doc = root.nodeType === 9 ? root : root.ownerDocument;\n const win = doc.defaultView;\n const text = css(!!win?.CSS?.supports?.("color", "light-dark(#000,#fff)"));\n if (win && "adoptedStyleSheets" in root) {\n try {\n const sheet = new win.CSSStyleSheet();\n sheet.replaceSync(text);\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];\n return;\n } catch {\n }\n }\n const style = doc.createElement("style");\n style.setAttribute("data-hairline-style", "");\n style.textContent = text;\n (root.nodeType === 9 ? doc.head ?? doc.documentElement : root).appendChild(style);\n}\n\n// src/intensity.ts\nvar TABLE = {\n riffle: [0, 40, 90],\n // stagger, ms\n terrain: [1.5, 3, 5],\n // radius, cells\n exploded: [12, 28, 40],\n // gap, viewBox units\n phosphor: [150, 520, 1500],\n // afterglow, ms\n slow: [0.6, 0.2, 0.05],\n // rate, \xD7 normal speed\n turntable: [200, 650, 1500],\n // coast, ms\n keyboard: [1, 2, 3.5],\n // radius, keys\n elevator: [40, 100, 220],\n // stiffness, spring units\n phone: [16, 28, 40],\n // gap, viewBox units\n laptop: [100, 125, 150],\n // lid, degrees\n terminal: [1, 2, 3.5],\n // spread, lines\n cabinet: [1.5, 3, 5],\n // reach, blades\n branches: [1, 3, 6],\n // reach, commits\n vault: [250, 600, 1500],\n // coast, ms\n lockers: [55, 90, 120],\n // opening, degrees\n padlock: [45, 90, 100],\n // swing, degrees\n patch: [1, 2.5, 5],\n // radius, ports\n dish: [30, 50, 70],\n // reach, degrees\n router: [0.5, 1.5, 3],\n // spread, antennas\n loupe: [1.3, 1.8, 2.6],\n // magnification, \xD7\n sieve: [6, 14, 24],\n // gap, viewBox units\n rail: [1, 2, 3.5],\n // radius, hangers\n plug: [0.35, 0.6, 0.9],\n // pull, fraction of the way to the socket\n query: [20, 40, 55],\n // turn, degrees; past 55 the hook folds over itself and stops reading as one\n drawer: [12, 22, 34],\n // pull, viewBox units\n basket: [8, 16, 28],\n // tilt, degrees\n plot: [3, 6, 12]\n // lift, viewBox units\n};\nvar DEFAULT = 0.5;\nfunction intensity(value) {\n const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;\n if (typeof n !== "number" || !Number.isFinite(n)) return DEFAULT;\n return Math.min(1, Math.max(0, n));\n}\nfunction parameter(figure, value) {\n const [lo, mid, hi] = TABLE[figure];\n const i = intensity(value);\n const v = i <= 0.5 ? lo + i / 0.5 * (mid - lo) : mid + (i - 0.5) / 0.5 * (hi - mid);\n return Math.round(v * 1e3) / 1e3;\n}\n\n// src/mount.ts\nvar NS = "http://www.w3.org/2000/svg";\nvar mounted = /* @__PURE__ */ new WeakMap();\nvar report = (err) => {\n if (typeof reportError === "function") reportError(err);\n else setTimeout(() => {\n throw err;\n });\n};\nfunction create(spec, el, options) {\n if (typeof document === "undefined") {\n throw new Error(`hairline: ${spec.id}() needs a DOM. Call it in the browser, once the element exists: in an effect, in onMount, or in a script after the element.`);\n }\n if (!el || el.nodeType !== 1) {\n throw new TypeError(`hairline: ${spec.id}() takes an element as its first argument, and got ${el === null ? "null" : typeof el}.`);\n }\n mounted.get(el)?.();\n const opts = { ...options };\n const doc = el.ownerDocument;\n const root = el.getRootNode();\n inject(root.nodeType === 9 || "host" in root ? root : doc);\n const owned = /* @__PURE__ */ new Set();\n const attr = (name, value2) => {\n if (!owned.has(name) && el.hasAttribute(name)) return;\n if (value2 === null) {\n el.removeAttribute(name);\n owned.delete(name);\n } else {\n el.setAttribute(name, value2);\n owned.add(name);\n }\n };\n const dress = () => {\n attr("data-hairline-theme", opts.theme === "light" || opts.theme === "dark" ? opts.theme : null);\n attr("aria-label", el.hasAttribute("aria-labelledby") ? null : typeof opts.label === "string" ? opts.label : spec.label);\n };\n attr("data-hairline", spec.id);\n attr("role", spec.focusable ? "group" : "img");\n if (spec.focusable) attr("tabindex", "0");\n dress();\n const svg = doc.createElementNS(NS, "svg");\n svg.setAttribute("viewBox", "0 0 400 320");\n svg.setAttribute("aria-hidden", "true");\n el.appendChild(svg);\n let live = null;\n if (spec.focusable) {\n live = doc.createElement("span");\n live.setAttribute("data-hairline-live", "");\n live.setAttribute("aria-live", "polite");\n el.appendChild(live);\n }\n let text = null;\n const read = {\n get textContent() {\n return text;\n },\n set textContent(value2) {\n const next = value2 ?? "";\n if (next === text) return;\n text = next;\n if (live) live.textContent = next;\n const fn = opts.onRead;\n if (typeof fn === "function") try {\n fn(next);\n } catch (err) {\n report(err);\n }\n }\n };\n let value = parameter(spec.id, opts.intensity);\n const engine = spec.engine({ stage: el, svg, read }, value);\n if (text === null) read.textContent = spec.rest;\n let dead = false;\n const destroy = () => {\n if (dead) return;\n dead = true;\n if (mounted.get(el) === destroy) mounted.delete(el);\n engine.destroy();\n svg.remove();\n live?.remove();\n for (const name of owned) el.removeAttribute(name);\n owned.clear();\n };\n mounted.set(el, destroy);\n return {\n update(next) {\n if (dead || !next) return;\n const own = opts, given = next;\n for (const k in given) {\n if (given[k] === void 0) delete own[k];\n else own[k] = given[k];\n }\n const v = parameter(spec.id, opts.intensity);\n if (v !== value) {\n value = v;\n engine.set(v);\n }\n dress();\n },\n destroy\n };\n}\n\n// src/core/iso.ts\nvar clamp = (v, a, b) => Math.max(a, Math.min(b, v));\nvar lerp = (a, b, t) => a + (b - a) * t;\nvar rad = (d) => d * Math.PI / 180;\nvar r2 = (n) => Math.round(n * 100) / 100;\nvar poly = (pts) => "M" + pts.map((p) => r2(p[0]) + " " + r2(p[1])).join("L") + "Z";\nvar seg = (a, b) => `M${r2(a[0])} ${r2(a[1])}L${r2(b[0])} ${r2(b[1])}`;\nvar open = (pts) => pts.length < 2 ? "" : "M" + pts.map((p) => r2(p[0]) + " " + r2(p[1])).join("L");\nvar Cam = (azDeg, k, S9) => ({ az: rad(azDeg), k, S: S9, ox: 0, oy: 0 });\nfunction proj(C) {\n const c = Math.cos(C.az), s = Math.sin(C.az), zf = Math.sqrt(1 - C.k * C.k);\n return (x, y, z) => {\n const X = x * c - y * s, Y = x * s + y * c;\n return [C.ox + C.S * X, C.oy + C.S * (Y * C.k - z * zf)];\n };\n}\nfunction unproj(C, sx, sy, z) {\n const c = Math.cos(C.az), s = Math.sin(C.az), zf = Math.sqrt(1 - C.k * C.k);\n const X = (sx - C.ox) / C.S, Y = ((sy - C.oy) / C.S + z * zf) / C.k;\n return [X * c + Y * s, -X * s + Y * c];\n}\nfunction fit(C, pts, cx, cy) {\n C.ox = 0;\n C.oy = 0;\n const P = proj(C);\n let a = 1e9, b = -1e9, c = 1e9, d = -1e9;\n for (const p of pts) {\n const q = P(p[0], p[1], p[2]);\n a = Math.min(a, q[0]);\n b = Math.max(b, q[0]);\n c = Math.min(c, q[1]);\n d = Math.max(d, q[1]);\n }\n C.ox = cx - (a + b) / 2;\n C.oy = cy - (c + d) / 2;\n}\nfunction rrect(u0, v0, u1, v1, r, n = 4) {\n r = Math.max(0, Math.min(r, (u1 - u0) / 2, (v1 - v0) / 2));\n const out = [];\n for (const [cu, cv, a0] of [[u1 - r, v1 - r, 0], [u0 + r, v1 - r, 90], [u0 + r, v0 + r, 180], [u1 - r, v0 + r, 270]])\n for (let k = 0; k <= n; k++) {\n const a = rad(a0 + 90 * k / n), ca = Math.cos(a), sa = Math.sin(a);\n out.push({ u: cu + r * ca, v: cv + r * sa, nu: ca, nv: sa });\n }\n return out;\n}\nfunction circ(R9, n = 96) {\n const out = [];\n for (let k = 0; k < n; k++) {\n const a = k / n * Math.PI * 2, ca = Math.cos(a), sa = Math.sin(a);\n out.push({ u: R9 * ca, v: R9 * sa, nu: ca, nv: sa });\n }\n return out;\n}\nfunction hull(input) {\n const pts = input.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n const x = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);\n const lo = [], up = [];\n for (const p of pts) {\n while (lo.length > 1 && x(lo[lo.length - 2], lo[lo.length - 1], p) <= 0) lo.pop();\n lo.push(p);\n }\n for (let i = pts.length - 1; i >= 0; i--) {\n const p = pts[i];\n while (up.length > 1 && x(up[up.length - 2], up[up.length - 1], p) <= 0) up.pop();\n up.push(p);\n }\n lo.pop();\n up.pop();\n return lo.concat(up);\n}\nvar ringAt = (P, ring, z) => ring.map((q) => P(q.u, q.v, z));\nvar facing = (C) => {\n const s = Math.sin(C.az), c = Math.cos(C.az);\n return (q) => q.nu * s + q.nv * c >= -1e-6;\n};\nfunction run(ring, keep) {\n const n = ring.length;\n let s = -1;\n for (let i = 0; i < n; i++) if (keep(ring[i]) && !keep(ring[(i + n - 1) % n])) {\n s = i;\n break;\n }\n if (s < 0) return keep(ring[0]) ? ring.slice() : [];\n const out = [];\n for (let k = 0; k < n && keep(ring[(s + k) % n]); k++) out.push(ring[(s + k) % n]);\n return out;\n}\nfunction prism(P, front2, ring, inner3, z0, z1) {\n return {\n sil: poly(hull(ringAt(P, ring, z1).concat(ringAt(P, ring, z0)))),\n crease: inner3 ? open(ringAt(P, run(inner3, front2), z1)) : ""\n };\n}\nvar rings = (x0, y0, x1, y1, r, b) => [\n rrect(x0, y0, x1, y1, r),\n rrect(x0 + b, y0 + b, x1 - b, y1 - b, Math.max(0.3, r - b))\n];\nfunction extremes(P, ring) {\n const pr = ring.map((q) => P(q.u, q.v, 0));\n let a = 0, b = 0, c = 0;\n pr.forEach((p, k) => {\n if (p[0] < pr[a][0]) a = k;\n if (p[0] > pr[b][0]) b = k;\n if (p[1] > pr[c][1]) c = k;\n });\n return [ring[a], ring[b], ring[c]];\n}\nfunction fillet(pts, rs, n = 4) {\n const m = pts.length, out = [];\n for (let i = 0; i < m; i++) {\n const a = pts[(i + m - 1) % m], p = pts[i], b = pts[(i + 1) % m];\n const la = Math.hypot(a[0] - p[0], a[1] - p[1]), lb = Math.hypot(b[0] - p[0], b[1] - p[1]);\n const t = Math.min(rs[i], la / 2, lb / 2);\n const p1 = [p[0] + (a[0] - p[0]) / la * t, p[1] + (a[1] - p[1]) / la * t];\n const p2 = [p[0] + (b[0] - p[0]) / lb * t, p[1] + (b[1] - p[1]) / lb * t];\n for (let k = 0; k <= n; k++) {\n const s = k / n, w = 1 - s;\n out.push([w * w * p1[0] + 2 * w * s * p[0] + s * s * p2[0], w * w * p1[1] + 2 * w * s * p[1] + s * s * p2[1]]);\n }\n }\n return out;\n}\nfunction ghost(P, front2, ring, z0, depth2) {\n const f = run(ring, front2), lowP = ringAt(P, f, z0 - depth2);\n return {\n d: open(lowP) + [f[0], f[f.length - 1]].map((q) => seg(P(q.u, q.v, z0), P(q.u, q.v, z0 - depth2))).join(""),\n y0: Math.min(...ringAt(P, f, z0).map((p) => p[1])),\n y1: Math.max(...lowP.map((p) => p[1])) + 2\n };\n}\n\n// src/core/motion.ts\nvar reduced = false;\nvar setReducedMotion = (on) => {\n reduced = on;\n};\nvar reducedMotion = () => reduced;\nfunction spring(x, o = {}) {\n return { x, v: 0, t: x, k: o.k ?? 100, c: o.c ?? 18, m: o.m ?? 1, eps: o.eps ?? 0.01 };\n}\nfunction stepS(sp, dt) {\n if (reduced) {\n sp.x = sp.t;\n sp.v = 0;\n return false;\n }\n const n = Math.max(1, Math.ceil(dt * 240)), h = dt / n;\n for (let i = 0; i < n; i++) {\n const a = (-sp.k * (sp.x - sp.t) - sp.c * sp.v) / sp.m;\n sp.v += a * h;\n sp.x += sp.v * h;\n }\n if (Math.abs(sp.x - sp.t) < sp.eps && Math.abs(sp.v) < sp.eps * 10) {\n sp.x = sp.t;\n sp.v = 0;\n return false;\n }\n return true;\n}\nfunction bezier(x1, y1, x2, y2) {\n const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax2 = 1 - cx - bx;\n const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;\n const X = (u) => ((ax2 * u + bx) * u + cx) * u;\n const Y = (u) => ((ay * u + by) * u + cy) * u;\n const dX = (u) => (3 * ax2 * u + 2 * bx) * u + cx;\n return (t) => {\n if (t <= 0) return 0;\n if (t >= 1) return 1;\n let u = t;\n for (let i = 0; i < 8; i++) {\n const e = X(u) - t;\n if (Math.abs(e) < 1e-5) break;\n const d = dX(u);\n if (Math.abs(d) < 1e-6) break;\n u -= e / d;\n }\n if (!(u >= 0 && u <= 1) || Math.abs(X(u) - t) > 1e-4) {\n let lo = 0, hi = 1;\n u = t;\n for (let i = 0; i < 24; i++) {\n if (X(u) < t) lo = u;\n else hi = u;\n u = (lo + hi) / 2;\n }\n }\n return Y(u);\n };\n}\nvar EASE_LIFT = bezier(0.32, 0.72, 0, 1);\nvar tween = (v, dur = 700) => ({ from: v, to: v, t0: -1e9, dur });\nvar tval = (tw, now) => {\n const p = clamp((now - tw.t0) / tw.dur, 0, 1);\n return tw.from + (tw.to - tw.from) * (reduced ? 1 : EASE_LIFT(p));\n};\nvar tset = (tw, to, now, delay) => {\n if (tw.to === to) return;\n tw.from = tval(tw, now);\n tw.to = to;\n tw.t0 = now + delay;\n};\nvar tdone = (tw, now) => reduced || now >= tw.t0 + tw.dur;\n\n// src/core/stage.ts\nvar NS2 = "http://www.w3.org/2000/svg";\nfunction mk(tag, attrs, parent) {\n const e = document.createElementNS(NS2, tag);\n if (attrs) for (const k in attrs) e.setAttribute(k, String(attrs[k]));\n if (parent) parent.appendChild(e);\n return e;\n}\nfunction solid(parent) {\n const g = mk("g", {}, parent);\n return { g, sil: mk("path", { class: "sil" }, g), cr: mk("path", { class: "nf lo" }, g) };\n}\nvar put = (el, s) => {\n el.sil.setAttribute("d", s.sil);\n el.cr.setAttribute("d", s.crease);\n};\nvar flatDot = (parent, C, r, cls) => mk("ellipse", { rx: r2(r * C.S), ry: r2(r * C.S * C.k), class: cls }, parent);\nvar place = (el, q) => {\n el.setAttribute("cx", String(r2(q[0])));\n el.setAttribute("cy", String(r2(q[1])));\n};\nvar fid = 0;\nfunction fade(svg, y0, y1, a0 = 0.7) {\n const id = "hl-fd" + ++fid, defs = mk("defs", {}, svg);\n const lg = mk("linearGradient", { id: id + "g", gradientUnits: "userSpaceOnUse", x1: 0, y1: r2(y0), x2: 0, y2: r2(y1) }, defs);\n mk("stop", { offset: 0, "stop-color": "#fff", "stop-opacity": a0 }, lg);\n mk("stop", { offset: 1, "stop-color": "#fff", "stop-opacity": 0 }, lg);\n const m = mk("mask", { id, maskUnits: "userSpaceOnUse", x: 0, y: 0, width: 400, height: 320 }, defs);\n mk("rect", { x: 0, y: 0, width: 400, height: 320, fill: `url(#${id}g)` }, m);\n return `url(#${id})`;\n}\nfunction reflect(svg, parent, P, front2, ring, z0, depth2) {\n const r = ghost(P, front2, ring, z0, depth2);\n const gh = mk("g", { class: "ghost", mask: fade(svg, r.y0, r.y1) }, parent);\n mk("path", { d: r.d }, gh);\n}\nvar boards = [];\nvar byStage = /* @__PURE__ */ new Map();\nvar raf = 0;\nvar last = 0;\nvar io = null;\nvar rm = null;\nfunction frame(now) {\n const dt = Math.min(0.05, Math.max(0, (now - last) / 1e3));\n last = now;\n let any = false;\n for (const b of boards.slice()) if (b.vis && b.awake) {\n b.awake = !!b.tick(dt, now);\n any = any || b.awake;\n }\n raf = any ? requestAnimationFrame(frame) : 0;\n}\nfunction wake(b) {\n b.awake = true;\n if (!raf) {\n last = performance.now();\n raf = requestAnimationFrame(frame);\n }\n}\nvar onMotion = () => {\n setReducedMotion(!!rm?.matches);\n boards.forEach(wake);\n};\nfunction start() {\n if (io) return;\n io = new IntersectionObserver((es) => {\n for (const e of es) {\n const b = byStage.get(e.target);\n if (!b) continue;\n b.vis = e.isIntersecting;\n if (b.vis) wake(b);\n }\n }, { rootMargin: "80px" });\n rm = matchMedia("(prefers-reduced-motion: reduce)");\n setReducedMotion(rm.matches);\n rm.addEventListener("change", onMotion);\n}\nfunction stop() {\n if (raf) cancelAnimationFrame(raf);\n raf = 0;\n io?.disconnect();\n io = null;\n rm?.removeEventListener("change", onMotion);\n rm = null;\n}\nfunction register(stage, tick) {\n start();\n const b = { stage, tick, vis: false, awake: true };\n boards.push(b);\n byStage.set(stage, b);\n io.observe(stage);\n tick(0, performance.now());\n let gone = false;\n return {\n wake: () => {\n if (!gone) wake(b);\n },\n unregister: () => {\n if (gone) return;\n gone = true;\n boards = boards.filter((x) => x !== b);\n if (byStage.get(stage) === b) {\n byStage.delete(stage);\n io?.unobserve(stage);\n }\n if (!boards.length) stop();\n }\n };\n}\nfunction pointer(stage, on) {\n let tm = 0;\n const pt = (e) => {\n const r = stage.getBoundingClientRect();\n return [(e.clientX - r.left) / r.width * 400, (e.clientY - r.top) / r.height * 320];\n };\n const move = (e) => {\n clearTimeout(tm);\n on.move(pt(e), e);\n };\n const down = (e) => {\n clearTimeout(tm);\n if (e.pointerType !== "mouse") stage.releasePointerCapture?.(e.pointerId);\n if (on.down) on.down(pt(e), e);\n else on.move(pt(e), e);\n };\n const leave = (e) => {\n clearTimeout(tm);\n tm = window.setTimeout(() => on.leave(e), e.pointerType === "mouse" ? 0 : 1400);\n };\n stage.addEventListener("pointermove", move);\n stage.addEventListener("pointerdown", down);\n stage.addEventListener("pointerleave", leave);\n return () => {\n clearTimeout(tm);\n stage.removeEventListener("pointermove", move);\n stage.removeEventListener("pointerdown", down);\n stage.removeEventListener("pointerleave", leave);\n };\n}\nfunction disposer() {\n let fns = [];\n return {\n add: (fn) => {\n fns.push(fn);\n },\n on: (target, type, fn, opts) => {\n const h = fn;\n target.addEventListener(type, h, opts);\n fns.push(() => target.removeEventListener(type, h, opts));\n },\n dispose: () => {\n const run2 = fns;\n fns = [];\n for (let i = run2.length - 1; i >= 0; i--) run2[i]();\n }\n };\n}\n\n// src/figures/basket.ts\nvar A = 40;\nvar B = 25;\nvar RR = 8;\nvar RT = 1.5;\nvar H = 30;\nvar F = 0.8;\nvar FT = 3;\nvar FB = 1.4;\nvar N = 44;\nvar BANDS = [0.45];\nvar HH = 30;\nvar HR = 8;\nvar HT = 1.3;\nvar GRIP = 9;\nvar GT = 2.6;\nvar REST_H = -46;\nvar LIFT = 0.6;\nvar REACH = 70;\nvar S = 3;\nfunction aim(dx, dy, m) {\n const a = Math.min(0, (dx + dy) * Math.SQRT1_2) / 2;\n return [m * (dx - a * Math.SQRT1_2), m * (dy - a * Math.SQRT1_2)];\n}\nfunction tilter(tx, ty) {\n const m = Math.hypot(tx, ty), kx = m ? -ty / m : 0, ky = m ? tx / m : 0, c = Math.cos(rad(m)), s = Math.sin(rad(m));\n return (x, y, z) => {\n const d = (kx * x + ky * y) * (1 - c);\n return [x * c + ky * z * s + kx * d, y * c - kx * z * s + ky * d, z * c + (kx * y - ky * x) * s];\n };\n}\nfunction stations(ring, n) {\n const L7 = [0];\n for (let i = 1; i <= ring.length; i++) {\n const p = ring[i - 1], q = ring[i % ring.length];\n L7.push(L7[i - 1] + Math.hypot(q.u - p.u, q.v - p.v));\n }\n return Array.from({ length: n }, (_, k) => {\n const s = k / n * L7[ring.length];\n let i = 0;\n while (L7[i + 1] < s) i++;\n const p = ring[i], q = ring[(i + 1) % ring.length], f = (s - L7[i]) / (L7[i + 1] - L7[i] || 1);\n return { u: p.u + (q.u - p.u) * f, v: p.v + (q.v - p.v) * f, nu: p.nu, nv: p.nv };\n });\n}\nfunction tube(Q, rho, closed = false) {\n const n = Q.length, L7 = [], R9 = [], T12 = [];\n for (let i = 0; i < n; i++) {\n const a = Q[closed ? (i + n - 1) % n : Math.max(i - 1, 0)], b = Q[closed ? (i + 1) % n : Math.min(i + 1, n - 1)];\n const l = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1, tx = (b[0] - a[0]) / l, ty = (b[1] - a[1]) / l;\n T12.push([tx, ty]);\n L7.push([Q[i][0] - ty * rho, Q[i][1] + tx * rho]);\n R9.push([Q[i][0] + ty * rho, Q[i][1] - tx * rho]);\n }\n if (closed) return poly(L7) + poly(R9.reverse());\n const cap = (c, t, d) => Array.from({ length: 7 }, (_, k) => {\n const th = Math.PI * (k + 1) / 8, co = Math.cos(th) * d, si = Math.sin(th) * d;\n return [c[0] + rho * (-t[1] * co + t[0] * si), c[1] + rho * (t[0] * co + t[1] * si)];\n });\n return poly([...L7, ...cap(Q[n - 1], T12[n - 1], 1), ...R9.reverse(), ...cap(Q[0], T12[0], -1)]);\n}\nvar ARM = /* @__PURE__ */ (() => [\n [-B, 0],\n ...Array.from({ length: 9 }, (_, k) => {\n const t = Math.PI - Math.PI / 2 * (k / 8);\n return [-B + HR + HR * Math.cos(t), HH - HR + HR * Math.sin(t)];\n }),\n ...Array.from({ length: 9 }, (_, k) => {\n const t = Math.PI / 2 * (1 - k / 8);\n return [B - HR + HR * Math.cos(t), HH - HR + HR * Math.sin(t)];\n }),\n [B, 0]\n])();\nvar GRIPS = [[-GRIP, HH], [GRIP, HH]];\nvar swing = (phi) => {\n const c = Math.cos(rad(phi)), s = Math.sin(rad(phi));\n return (p) => [p[1] * s, p[0], H + p[1] * c];\n};\nvar mount = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, S);\n const MAXT = TABLE.basket[2];\n const HMAX2 = REST_H + LIFT * MAXT;\n const rim = rrect(-A, -B, A, B, RR, 14), wires = stations(rim, N);\n const floor = rim.map((q) => ({ ...q, u: q.u * F, v: q.v * F }));\n const lid = rrect(-A * F + FB, -B * F + FB, A * F - FB, B * F - FB, RR * F - FB, 14);\n const ext = [];\n for (let k = -1; k < 8; k++) {\n const R10 = tilter(...aim(Math.cos(k * Math.PI / 4), Math.sin(k * Math.PI / 4), k < 0 ? 0 : MAXT));\n for (const q of rim) ext.push(R10(q.u, q.v, H + RT), R10(q.u * F, q.v * F, 0));\n for (const phi of [REST_H, HMAX2]) for (const p of ARM) ext.push(R10(...swing(phi)(p)));\n }\n fit(C, ext, 200, 166);\n const P0 = proj(C), sw = Math.sqrt(1 - C.k * C.k), cam = [Math.sin(C.az) * sw, Math.cos(C.az) * sw, C.k];\n const g = mk("g", {}, svg);\n const far = mk("path", { class: "nf lo" }, g);\n const base = solid(g);\n const near = mk("path", { class: "nf lo" }, g);\n const ring = mk("path", { class: "sil" }, g);\n const bar2 = mk("path", { class: "sil" }, g);\n const grip = mk("path", { class: "sil" }, g);\n const tx = spring(0), ty = spring(0), hp = spring(REST_H, { k: 60, c: 6, eps: 0.02 });\n let dX = NaN, dY = NaN, dH = NaN, R9 = tilter(0, 0);\n const at2 = (p) => P0(...R9(...p));\n const facing2 = (q) => {\n const n = R9(q.nu, q.nv, 0);\n return n[0] * cam[0] + n[1] * cam[1] + n[2] * cam[2] >= 0;\n };\n function draw() {\n const moved = tx.x !== dX || ty.x !== dY;\n if (moved) {\n dX = tx.x;\n dY = ty.x;\n R9 = tilter(dX, dY);\n put(base, {\n sil: poly(hull(floor.map((q) => at2([q.u, q.v, 0])).concat(floor.map((q) => at2([q.u, q.v, FT]))))),\n crease: open(run(lid, facing2).map((q) => at2([q.u, q.v, FT])))\n });\n const d = ["", ""];\n for (const q of wires) d[+facing2(q)] += seg(at2([q.u * F - q.nu, q.v * F - q.nv, FT]), at2([q.u, q.v, H]));\n for (const b of BANDS) {\n const z = FT + (H - FT) * b, k = F + (1 - F) * b;\n rim.forEach((q, i) => {\n const r = rim[(i + 1) % rim.length];\n d[+facing2(q)] += seg(at2([q.u * k, q.v * k, z]), at2([r.u * k, r.v * k, z]));\n });\n }\n far.setAttribute("d", d[0]);\n near.setAttribute("d", d[1]);\n ring.setAttribute("d", tube(rim.map((q) => at2([q.u, q.v, H])), RT * S, true));\n }\n if (moved || hp.x !== dH) {\n dH = hp.x;\n const s = swing(dH);\n bar2.setAttribute("d", tube(ARM.map((p) => at2(s(p))), HT * S));\n grip.setAttribute("d", tube(GRIPS.map((p) => at2(s(p))), GT * S));\n }\n }\n const loop = register(stage, (dt) => {\n const a = stepS(tx, dt), b = stepS(ty, dt);\n hp.t = Math.min(REST_H + LIFT * Math.hypot(tx.x, ty.x), HMAX2);\n const c = stepS(hp, dt);\n draw();\n return a || b || c;\n });\n bag.add(loop.unregister);\n let tmax = value, over = false, gx = 0, gy = 0;\n function retarget() {\n const r = Math.hypot(gx, gy), m = over && r ? clamp(tmax, 0, MAXT) * clamp(r / REACH, 0, 1) : 0;\n [tx.t, ty.t] = m ? aim(gx / r, gy / r, m) : [0, 0];\n read.textContent = over ? `tilt ${Math.round(Math.hypot(tx.t, ty.t))}\\xB0 \\xB7 0` : "rest";\n ring.classList.toggle("hi", !over);\n base.sil.classList.toggle("hi", over);\n loop.wake();\n }\n retarget();\n bag.add(pointer(stage, {\n // the pointer on a plane through the basket\'s middle, which never moves\n move: (p) => {\n over = true;\n [gx, gy] = unproj(C, p[0], p[1], H / 2);\n retarget();\n },\n leave: () => {\n over = false;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n tmax = v;\n retarget();\n }, destroy: bag.dispose };\n};\n\n// src/figures/branches.ts\nvar D = 30;\nvar FY = 58;\nvar RW = 7;\nvar RT2 = 2.6;\nvar PR = 8;\nvar PH = 2.4;\nvar CR = 6.6;\nvar CH = 6;\nvar LIFT2 = 36;\nvar STEP = 45;\nvar BX0 = -15;\nvar BX1 = 7 * D + 15;\nvar BY0 = -16;\nvar BY1 = FY + 14;\nvar PB = 6;\nvar ZTOP = RT2 + PH + CH;\nfunction history() {\n const cs = [], at2 = (lane, n, x, y, parent) => (cs.push({ lane, n, x, y, parent }), cs.length - 1);\n const m = [];\n for (let i = 0; i < 8; i++) m.push(at2("main", i + 1, i * D, 0, i ? m[i - 1] : -1));\n const f1 = at2("feature", 1, 2.5 * D, FY, m[1]), f2 = at2("feature", 2, 3.5 * D, FY, f1), f3 = at2("feature", 3, 4.5 * D, FY, f2);\n return { cs, m, f: [f1, f2, f3] };\n}\nvar bez = (a, b, ox, oy, ix, n) => {\n const out = [];\n for (let k = 0; k <= n; k++) {\n const t = k / n, u = 1 - t, w = [u * u * u, 3 * u * u * t, 3 * u * t * t, t * t * t];\n const px = [a[0], a[0] + ox, b[0] - ix, b[0]], py = [a[1], a[1] + oy, b[1], b[1]];\n out.push([w[0] * px[0] + w[1] * px[1] + w[2] * px[2] + w[3] * px[3], w[0] * py[0] + w[1] * py[1] + w[2] * py[2] + w[3] * py[3]]);\n }\n return out;\n};\nfunction strip(c) {\n const L7 = [], R9 = [];\n c.forEach((p, i) => {\n const a = c[Math.max(0, i - 1)], b = c[Math.min(c.length - 1, i + 1)];\n const dx = b[0] - a[0], dy = b[1] - a[1], l = Math.hypot(dx, dy), nx = -dy / l, ny = dx / l;\n L7.push([p[0] + nx * RW / 2, p[1] + ny * RW / 2]);\n R9.push([p[0] - nx * RW / 2, p[1] - ny * RW / 2]);\n });\n return L7.concat(R9.reverse());\n}\nvar mount2 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let reach = value;\n const C = Cam(45, 0.5, 1.4);\n fit(C, [[BX0, BY0, -PB], [BX1, BY1, -PB], [BX1, BY0, -PB], [BX0, BY1, -PB], [BX0, BY0, ZTOP + LIFT2], [BX1, BY0, ZTOP + LIFT2]], 200, 166);\n const P = proj(C), front2 = facing(C);\n const { cs: seeds, m, f } = history();\n const xy = (i) => [seeds[i].x, seeds[i].y];\n const g = mk("g", {}, svg);\n const [br, bi] = rings(BX0, BY0, BX1, BY1, 12, 2.2);\n put(solid(g), prism(P, front2, br, bi, -PB, 0));\n const fork = (a, b) => bez(a, b, (b[0] - a[0]) * 0.3, (b[1] - a[1]) * 0.5, (b[0] - a[0]) * 0.45, 14);\n const merge = (a, b) => bez(b, a, (a[0] - b[0]) * 0.3, (a[1] - b[1]) * 0.5, (a[0] - b[0]) * 0.45, 14).reverse();\n const lines = [\n [xy(m[0]), xy(m[7])],\n [...fork(xy(m[1]), xy(f[0])), ...merge(xy(f[2]), xy(m[6])).slice(1)]\n ];\n for (const c of lines) {\n const s = strip(c);\n mk("path", { d: poly(s.map((p) => P(p[0], p[1], 0))), class: "lo" }, g);\n mk("path", { d: poly(s.map((p) => P(p[0], p[1], RT2))), class: "" }, g);\n }\n const [pr, pi] = [circ(PR, 24), circ(PR - 1.4, 24)], [cr, ci] = [circ(CR, 24), circ(CR - 1.2, 24)];\n const at2 = (ring, x, y) => ring.map((q) => ({ ...q, u: q.u + x, v: q.v + y }));\n const order2 = seeds.map((_, i) => i).sort((a, b) => seeds[a].x + seeds[a].y - (seeds[b].x + seeds[b].y));\n const cs = [];\n for (const i of order2) {\n const c = seeds[i];\n put(solid(g), prism(P, front2, at2(pr, c.x, c.y), at2(pi, c.x, c.y), RT2, PH + RT2));\n const drop = mk("path", { class: "dash nf" }, g);\n cs[i] = { ...c, drop, el: solid(g), ring: at2(cr, c.x, c.y), inner: at2(ci, c.x, c.y), z: tween(0), drawn: NaN };\n }\n const chain = (a) => {\n const out = [];\n for (let i = a, k = 0; i >= 0; i = cs[i].parent, k++) out.push([i, k]);\n return out;\n };\n const REST16 = f[2], REST_REACH = 2.2, REST_DEPTH = 0.5;\n function draw(c, z) {\n if (z === c.drawn) return;\n c.drawn = z;\n const b = PH + RT2 + z;\n put(c.el, prism(P, front2, c.ring, c.inner, b, b + CH));\n c.drop.setAttribute("d", seg(P(c.x, c.y, PH + RT2), P(c.x, c.y, b)));\n }\n const B6 = register(stage, (_dt, now) => {\n let moving = false;\n for (const c of cs) {\n draw(c, tval(c.z, now));\n if (!tdone(c.z, now)) moving = true;\n }\n return moving;\n });\n bag.add(B6.unregister);\n let act = null, lit2 = null, held = /* @__PURE__ */ new Map();\n function lift(a, r, depth2, instant) {\n const now = performance.now(), want = /* @__PURE__ */ new Map();\n for (const [i, k] of chain(a)) if (k <= r) want.set(i, [LIFT2 * depth2 * clamp(1 - k / (r + 1), 0, 1), k]);\n cs.forEach((c, i) => {\n const [to, k] = want.get(i) || [0, (held.get(i) || [0, 0])[1]];\n if (instant) c.z = tween(to);\n else tset(c.z, to, now, k * STEP);\n });\n held = want;\n if (lit2 !== a) {\n if (lit2 !== null) cs[lit2].el.sil.classList.remove("hi");\n lit2 = a;\n cs[a].el.sil.classList.add("hi");\n }\n B6.wake();\n }\n lift(REST16, REST_REACH, REST_DEPTH, true);\n function choose(a) {\n if (a === act) return;\n act = a;\n if (a === null) {\n lift(REST16, REST_REACH, REST_DEPTH);\n read.textContent = "rest";\n } else {\n lift(a, reach, 1);\n read.textContent = `${cs[a].lane} \\xB7 ${cs[a].n}`;\n }\n }\n function hit(p) {\n const q = unproj(C, p[0], p[1], ZTOP);\n if (q[0] < BX0 || q[0] > BX1 || q[1] < BY0 || q[1] > BY1) return null;\n let best = null, bd = Infinity;\n cs.forEach((c, i) => {\n const d = Math.hypot(c.x - q[0], c.y - q[1]);\n if (d < bd) {\n bd = d;\n best = i;\n }\n });\n return best;\n }\n bag.add(pointer(stage, { move: (p) => choose(hit(p)), leave: () => choose(null) }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n reach = v;\n if (act !== null) lift(act, reach, 1);\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/cabinet.ts\nvar N2 = 12;\nvar U = 10.4;\nvar BH = 9.6;\nvar W = 84;\nvar EAR = 4;\nvar FT2 = 2;\nvar D2 = 44;\nvar OUT = 26;\nvar X0 = -10;\nvar X1 = W + 10;\nvar Z0 = 6;\nvar ZB = 11;\nvar ZT = ZB + (N2 - 1) * U + BH;\nvar H2 = ZT + 6;\nvar REST = [0, 0, 0.2, 0, 0, 0.3, 0.58, 0.26, 0, 0, 0.13, 0];\nvar LIT = 6;\nvar falloff = (u) => u >= 1 ? 0 : (1 - u) * (1 - u);\nvar inner = (q) => 0.375 * q.nu + 0.307 * q.nv > 0;\nfunction slab(P, ring, inset, y0, y1) {\n const at2 = (r, y) => r.map((q) => P(q.u, y, q.v));\n return { sil: poly(hull(at2(ring, y0).concat(at2(ring, y1)))), crease: inset ? open(at2(run(inset, inner), y1)) : "" };\n}\nvar mount3 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.5);\n const yo = D2 + FT2 + OUT;\n fit(C, [[X0, 0, 0], [X1, 0, H2], [X1, D2, 0], [X0, 0, H2], [-EAR, yo, ZB], [-EAR, yo, ZT], [W + EAR, yo, ZB]], 200, 166);\n const P = proj(C), front2 = facing(C);\n let R9 = value, over = null, lit2 = null;\n const g = mk("g", {}, svg);\n const [pr, pi] = rings(X0 + 3, 3, X1 - 3, D2 - 3, 3, 1.2);\n put(solid(g), prism(P, front2, pr, pi, 0, Z0));\n const [br, bi] = rings(X0, 0, X1, D2, 3.5, 1.6);\n put(solid(g), prism(P, front2, br, bi, Z0, H2));\n const onFront = (r) => r.map((q) => P(q.u, D2, q.v)), onSide = (r) => r.map((q) => P(X1, q.u, q.v));\n mk("path", { d: poly(onFront(rrect(-7.6, ZB - 2.2, W + 7.6, ZT + 2.2, 2, 4))) + poly(onSide(rrect(5, Z0 + 7, D2 - 5, H2 - 7, 3, 4))), class: "lo nf" }, g);\n for (let i = 0; i < N2; i++) for (const x of [-5.8, W + 5.8]) {\n place(mk("circle", { r: 0.75, class: "dot off" }, g), P(x, D2, ZB + i * U + BH / 2));\n }\n const blades = [];\n for (let i = 0; i < N2; i++) {\n const z = ZB + i * U, gi = mk("g", {}, g);\n blades.push({\n i,\n z,\n body: rrect(0, z + 0.4, W, z + BH - 0.4, 1.6, 4),\n face: rrect(-EAR, z, W + EAR, z + BH, 2, 4),\n faceIn: rrect(-EAR + 0.8, z + 0.8, W + EAR - 0.8, z + BH - 0.8, 1.2, 4),\n chassis: solid(gi),\n plate: solid(gi),\n marks: mk("path", { class: "lo nf" }, gi),\n lamp: mk("circle", { r: 1.1, class: "dot off" }, gi),\n sp: spring(OUT * REST[i], { eps: 0.02 }),\n drawn: NaN\n });\n }\n function marks(b, y) {\n const z = b.z, F2 = (r) => poly(r.map((q) => P(q.u, y, q.v)));\n let d = F2(rrect(-3.4, z + 2, -1.2, z + BH - 2, 1, 3)) + F2(rrect(W + 1.2, z + 2, W + 3.4, z + BH - 2, 1, 3));\n for (let k = 0; k < 4; k++) d += F2(rrect(4 + k * 10.6, z + 2, 13.8 + k * 10.6, z + BH - 2, 1, 3));\n for (let k = 0; k < 8; k++) d += seg(P(50 + k * 2.6, y, z + 2.5), P(50 + k * 2.6, y, z + BH - 2.5));\n return d;\n }\n function drawBlade(b) {\n const o = b.sp.x;\n if (o === b.drawn) return;\n b.drawn = o;\n const yf = D2 + o + FT2, zm = b.z + BH / 2;\n const ch = slab(P, b.body, null, D2, D2 + o + 0.5);\n put(b.chassis, { sil: ch.sil, crease: seg(P(W, D2, zm), P(W, D2 + o, zm)) });\n put(b.plate, slab(P, b.face, b.faceIn, D2 + o, yf));\n b.marks.setAttribute("d", marks(b, yf));\n place(b.lamp, P(W - 5, yf, zm));\n }\n function light(b) {\n if (b === lit2) return;\n if (lit2) {\n lit2.plate.sil.classList.remove("hi");\n lit2.lamp.setAttribute("class", "dot off");\n }\n lit2 = b;\n lit2.plate.sil.classList.add("hi");\n lit2.lamp.setAttribute("class", "dot");\n }\n const B6 = register(stage, (dt) => {\n let m = false;\n for (const b of blades) {\n if (stepS(b.sp, dt)) m = true;\n drawBlade(b);\n }\n return m;\n });\n bag.add(B6.unregister);\n const o0 = P(0, 0, 0), E2 = [P(1, 0, 0), P(0, 1, 0), P(0, 0, 1)].map((p) => [p[0] - o0[0], p[1] - o0[1]]);\n function onPlane(p, axis, c) {\n const [a, k] = [0, 1, 2].filter((n) => n !== axis), ea = E2[a], ek = E2[k];\n const rx = p[0] - o0[0] - c * E2[axis][0], ry = p[1] - o0[1] - c * E2[axis][1], det = ea[0] * ek[1] - ea[1] * ek[0];\n const w = [0, 0, 0];\n w[axis] = c;\n w[a] = (rx * ek[1] - ry * ek[0]) / det;\n w[k] = (ea[0] * ry - ea[1] * rx) / det;\n return w;\n }\n function hit(p) {\n for (let i = N2 - 1; i >= 0; i--) {\n const q = onPlane(p, 1, D2 + FT2 + OUT * REST[i]), z2 = (q[2] - ZB - BH / 2) / U;\n if (q[0] >= -EAR && q[0] <= W + EAR && Math.abs(z2 - i) <= 0.5) return z2;\n }\n const f = onPlane(p, 1, D2 + FT2), s = onPlane(p, 0, X1);\n let z = null;\n if (f[0] >= X0 - OUT - 4 && f[0] <= X1 && f[2] >= -OUT * 0.8 && f[2] <= H2) z = f[2];\n else if (s[1] >= 0 && s[1] <= D2 && s[2] >= 0 && s[2] <= H2 + 4) z = s[2];\n return z === null ? null : clamp((z - ZB - BH / 2) / U, 0, N2 - 1);\n }\n function retarget() {\n if (over === null) {\n for (const b of blades) b.sp.t = OUT * REST[b.i];\n light(blades[LIT]);\n read.textContent = "rest";\n } else {\n for (const b of blades) b.sp.t = OUT * falloff(Math.max(0, Math.abs(b.i - over) - 0.5) / R9);\n const a = Math.round(over);\n light(blades[a]);\n read.textContent = "blade " + (a + 1);\n }\n B6.wake();\n }\n light(blades[LIT]);\n bag.add(pointer(stage, {\n move: (p) => {\n over = hit(p);\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v;\n if (over !== null) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/dish.ts\nvar R = 26;\nvar FOC = 17;\nvar SK = 1.4;\nvar DEP = R * R / (4 * FOC);\nvar ZE = 46;\nvar W2 = 31;\nvar T = 5;\nvar ZB2 = 10;\nvar ZY = 16;\nvar AZ0 = 45;\nvar EL0 = 40;\nvar REST2 = [-20, 30];\nvar PIV = [0, 0, ZE];\nvar ax = (p, q, s) => [p[0] + q[0] * s, p[1] + q[1] * s, p[2] + q[2] * s];\nvar dot3 = (p, q) => p[0] * q[0] + p[1] * q[1] + p[2] * q[2];\nfunction frame2(th, ph) {\n const t = rad(th), f = rad(ph), c = Math.cos(f), s = Math.sin(f);\n const h = [Math.cos(t), Math.sin(t), 0], e = [-Math.sin(t), Math.cos(t), 0];\n return { th, h, e, a: [h[0] * c, h[1] * c, s], u: [-h[0] * s, -h[1] * s, c] };\n}\nvar onDish = (F2, along2, rho, psi) => ax(ax(ax(PIV, F2.a, along2), F2.e, rho * Math.cos(psi)), F2.u, rho * Math.sin(psi));\nvar disc = (c, x, y, r, n) => Array.from({ length: n }, (_, i) => ax(ax(c, x, r * Math.cos(2 * Math.PI * i / n)), y, r * Math.sin(2 * Math.PI * i / n)));\nvar turn = (ring, F2) => ring.map((q) => ({\n u: q.u * F2.h[0] + q.v * F2.e[0],\n v: q.u * F2.h[1] + q.v * F2.e[1],\n nu: q.nu * F2.h[0] + q.nv * F2.e[0],\n nv: q.nu * F2.h[1] + q.nv * F2.e[1]\n}));\nvar mount4 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let reach = value, over = null;\n const C = Cam(45, 0.5, 2.5), pts = [[-30, -30, 0], [30, 30, 0], [30, -30, 0], [-30, 30, 0]];\n for (const [th, ph] of [[-25, 5], [-25, 75], [115, 5], [115, 75], REST2]) {\n const F2 = frame2(th, ph);\n pts.push(...disc(PIV, F2.e, F2.u, R, 16), onDish(F2, FOC - DEP + 3, 0, 0));\n for (const s of [-1, 1]) pts.push(ax(ax(PIV, F2.e, s * (W2 + T / 2)), [0, 0, 1], 6));\n }\n fit(C, pts, 200, 166);\n const P = proj(C), front2 = facing(C), Pv = (q) => P(q[0], q[1], q[2]);\n const o = P(0, 0, 0), cx = P(1, 0, 0), cy = P(0, 1, 0), cz = P(0, 0, 1);\n const r1 = [cx[0] - o[0], cy[0] - o[0], cz[0] - o[0]], r22 = [cx[1] - o[1], cy[1] - o[1], cz[1] - o[1]];\n const cross3 = [r1[1] * r22[2] - r1[2] * r22[1], r1[2] * r22[0] - r1[0] * r22[2], r1[0] * r22[1] - r1[1] * r22[0]];\n const vl = Math.hypot(...cross3) * Math.sign(cross3[2]);\n const VD = cross3.map((x) => x / vl);\n const g = mk("g", {}, svg);\n const [br, bi] = rings(-30, -30, 30, 30, 9, 2);\n put(solid(g), prism(P, front2, br, bi, 0, 5));\n for (let i = 0; i < 36; i++) {\n const t = i / 36 * 2 * Math.PI, r = i % 9 === 0 ? 26.5 : 25.5;\n place(flatDot(g, C, i % 9 === 0 ? 0.75 : 0.5, "dot off"), P(r * Math.cos(t), r * Math.sin(t), 5));\n }\n put(solid(g), prism(P, front2, circ(20, 48), circ(18.8, 48), 5, ZB2));\n const index = flatDot(g, C, 0.8, "dot m");\n const beam = solid(g);\n const yb = W2 + T / 2 + 1, [bmr, bmi] = rings(-9, -yb, 9, yb, 4, 1.4);\n const dishG = mk("g", {}, g);\n const sides = [-1, 1].map((s) => {\n const sg = mk("g", {}, g);\n return {\n s,\n g: sg,\n near: null,\n ring: mk("path", { class: "nf" }, sg),\n arm: solid(sg),\n trun: mk("path", { class: "sil" }, sg),\n foot: rrect(-8, s * W2 - T / 2, 8, s * W2 + T / 2, 2.5, 4),\n top: rrect(-5.5, s * W2 - T / 2, 5.5, s * W2 + T / 2, 2.5, 4),\n inner: rrect(-4.5, s * W2 - T / 2 + 1, 4.5, s * W2 + T / 2 - 1, 1.5, 4)\n };\n });\n const outline = mk("path", { class: "sil" }, dishG), seam = mk("path", { class: "nf lo" }, dishG), hubR = mk("path", { class: "nf lo" }, dishG);\n const rim = mk("path", { class: "nf hi" }, dishG), struts = mk("path", { class: "nf" }, dishG);\n const feed = solid(dishG);\n const BACK2 = [0, 0.35, 0.6, 0.78, 0.9, 0.97, 1];\n function seen(F2, rho, n) {\n const along2 = rho * rho / (4 * FOC) - DEP, t = -along2 / dot3(F2.a, VD), q = [], s = [];\n for (let i2 = 0; i2 < n; i2++) {\n q.push(onDish(F2, along2, rho, 2 * Math.PI * i2 / n));\n const hit = ax(q[i2], VD, t);\n s.push(R - Math.hypot(hit[0] - PIV[0], hit[1] - PIV[1], hit[2] - PIV[2]));\n }\n if (s.every((x) => x > 0)) return poly(q.map(Pv));\n const i0 = s.findIndex((x, i2) => x > 0 && s[(i2 + n - 1) % n] <= 0);\n if (i0 < 0) return "";\n const cut2 = (i2, j) => Pv(ax(q[i2], [q[j][0] - q[i2][0], q[j][1] - q[i2][1], q[j][2] - q[i2][2]], s[i2] / (s[i2] - s[j])));\n const out = [cut2((i0 + n - 1) % n, i0)];\n let i = i0;\n while (s[i] > 0) {\n out.push(Pv(q[i]));\n i = (i + 1) % n;\n }\n out.push(cut2((i + n - 1) % n, i));\n return open(out);\n }\n let drawn = "";\n function draw(th, ph) {\n const key = th.toFixed(3) + "," + ph.toFixed(3);\n if (key === drawn) return;\n drawn = key;\n const F2 = frame2(th, ph);\n place(index, P(18 * F2.h[0], 18 * F2.h[1], ZB2));\n put(beam, prism(P, front2, turn(bmr, F2), turn(bmi, F2), ZB2, ZY));\n for (const sd of sides) {\n const near = sd.s * dot3(F2.e, VD) > 0;\n if (near !== sd.near) {\n sd.near = near;\n if (near) {\n sd.g.append(sd.trun, sd.arm.g, sd.ring);\n dishG.after(sd.g);\n } else {\n sd.g.append(sd.ring, sd.arm.g, sd.trun);\n dishG.before(sd.g);\n }\n }\n put(sd.arm, {\n sil: poly(hull(ringAt(P, turn(sd.foot, F2), ZY).concat(ringAt(P, turn(sd.top, F2), ZE + 6)))),\n crease: open(ringAt(P, run(turn(sd.inner, F2), front2), ZE + 6))\n });\n const zz = [0, 0, 1], out = ax(PIV, F2.e, sd.s * (W2 + T / 2));\n sd.ring.setAttribute("d", poly(disc(out, F2.h, zz, 3.4, 24).map(Pv)));\n sd.trun.setAttribute("d", poly(hull(disc(ax(PIV, F2.e, sd.s * R), F2.h, zz, 2.2, 16).concat(disc(ax(PIV, F2.e, sd.s * (W2 - T / 2)), F2.h, zz, 2.2, 16)).map(Pv))));\n }\n const lip = disc(PIV, F2.e, F2.u, R, 72), back = [];\n for (const f of BACK2) for (let i = 0; i < 40; i++) back.push(onDish(F2, f * f * R * R / (4 * FOC) - DEP - SK, f * R, 2 * Math.PI * i / 40));\n outline.setAttribute("d", poly(hull(lip.concat(back).map(Pv))));\n seam.setAttribute("d", seen(F2, 0.62 * R, 48));\n hubR.setAttribute("d", seen(F2, 3.2, 20));\n rim.setAttribute("d", poly(lip.map(Pv)));\n const mouth = onDish(F2, FOC - DEP - 3, 0, 0), tail = onDish(F2, FOC - DEP + 3, 0, 0);\n struts.setAttribute("d", [90, 210, 330].map((d) => seg(Pv(onDish(F2, 0, R, rad(d))), Pv(mouth))).join(""));\n put(feed, {\n sil: poly(hull(disc(mouth, F2.e, F2.u, 3.6, 16).concat(disc(tail, F2.e, F2.u, 2.4, 16)).map(Pv))),\n crease: poly(disc(tail, F2.e, F2.u, 1.3, 16).map(Pv))\n });\n }\n const az = spring(REST2[0], { eps: 0.01 }), el = spring(REST2[1], { eps: 0.01 });\n const B6 = register(stage, (dt) => {\n const a = stepS(az, dt), b = stepS(el, dt);\n draw(az.x, el.x);\n return a || b;\n });\n bag.add(B6.unregister);\n const hub = P(0, 0, ZE);\n function retarget() {\n if (over) {\n az.t = AZ0 - reach * Math.tanh((over[0] - hub[0]) / 120);\n el.t = EL0 + reach / 2 * Math.tanh((hub[1] - over[1]) / 90);\n read.textContent = `az ${Math.round((az.t + 360) % 360)} \\xB7 el ${Math.round(el.t)}`;\n } else {\n az.t = REST2[0];\n el.t = REST2[1];\n read.textContent = "rest";\n }\n B6.wake();\n }\n bag.add(pointer(stage, {\n move: (p) => {\n over = p;\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n reach = v;\n if (over) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/drawer.ts\nvar N3 = 3;\nvar W3 = 54;\nvar D3 = 60;\nvar Z02 = 5;\nvar M = 3;\nvar PITCH = 26;\nvar G = 1.6;\nvar FT3 = 2.4;\nvar T2 = 1.3;\nvar IN = 3.2;\nvar LOW = 2.2;\nvar HIGH = 3.6;\nvar DIV = 2.6;\nvar S2 = 2.1;\nvar ZB3 = Z02 + M;\nvar H3 = ZB3 + N3 * PITCH + M;\nvar REST3 = 0.7;\nvar inner2 = (q) => 0.375 * q.nu + 0.307 * q.nv > 0;\nfunction slab2(P, ring, inset, y0, y1) {\n const at2 = (r, y) => r.map((q) => P(q.u, y, q.v));\n return { sil: poly(hull(at2(ring, y0).concat(at2(ring, y1)))), crease: open(at2(run(inset, inner2), y1)) };\n}\nvar mount5 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const MAX2 = TABLE.drawer[2];\n const C = Cam(45, 0.5, S2);\n const out = D3 + MAX2 + FT3, ext = [[0, 0, 0], [W3, 0, 0], [0, 0, H3], [W3, 0, H3], [W3, D3, 0], [0, out, ZB3], [0, out, H3 - M], [W3, out, ZB3]];\n fit(C, ext.concat(ext.map(([x, y, z]) => [W3 - x, D3 - y, H3 - z])), 200, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const [pr, pi] = rings(3, 3, W3 - 3, D3 - 3, 2.4, 1);\n put(solid(g), prism(P, front2, pr, pi, 0, Z02));\n const [cr, ci] = rings(0, 0, W3, D3, 3, 1.4);\n put(solid(g), prism(P, front2, cr, ci, Z02, H3));\n const x0 = M + IN, x1 = W3 - M - IN, xs = [x0 + (x1 - x0) / 3, x0 + 2 * (x1 - x0) / 3];\n const onFace = (r, y) => poly(r.map((q) => P(q.u, y, q.v)));\n mk("path", { class: "nf lo", d: Array.from({ length: N3 }, (_, k) => {\n const z = ZB3 + k * PITCH + G / 2;\n return onFace(rrect(x0 - 0.8, z + LOW - 0.8, x1 + 0.8, z + PITCH - G - HIGH + 1.4, 0.8, 2), D3);\n }).join("") }, g);\n const drawers = Array.from({ length: N3 }, (_, k) => {\n const z0 = ZB3 + k * PITCH + G / 2, z1 = z0 + PITCH - G, dg = mk("g", {}, g);\n const parts = [0, 1, 2, 3].map(() => mk("path", { class: "lo" }, dg));\n parts.push(mk("path", { class: "nf sil" }, dg));\n return { z0, z1, parts, front: solid(dg), pull: solid(dg), drawn: NaN };\n });\n const face = (d) => rrect(M, d.z0, W3 - M, d.z1, 1.6, 4), faceIn = (d) => rrect(M + 1, d.z0 + 1, W3 - M - 1, d.z1 - 1, 1, 4);\n const box = (a, b, y0, y1, z0, z1, r) => y1 - y0 < 0.05 ? "" : prism(P, front2, rrect(a, y0, b, y1, r, 3), null, z0, z1).sil;\n function draw(d, o) {\n if (o === d.drawn) return;\n d.drawn = o;\n const zb = d.z0 + LOW, zt = d.z1 - HIGH, yb = D3 + o, yf = yb + FT3;\n const [floor, left, divs, right, outline] = d.parts;\n floor.setAttribute("d", box(x0, x1, D3, yb, zb, zb + T2, 0.6));\n left.setAttribute("d", box(x0, x0 + T2, D3, yb, zb + T2, zt, 0.5));\n divs.setAttribute("d", xs.map((x) => box(x - 0.5, x + 0.5, D3, yb, zb + T2, zt - DIV, 0.4)).join(""));\n right.setAttribute("d", box(x1 - T2, x1, D3, yb, zb + T2, zt, 0.5));\n outline.setAttribute("d", box(x0, x1, D3, yb, zb, zt, 0.8));\n put(d.front, slab2(P, face(d), faceIn(d), yb, yf));\n const zp = (d.z0 + d.z1) / 2, xm = W3 / 2;\n const [br, bi] = rings(xm - 9, yf, xm + 9, yf + 2.6, 1.3, 0.6);\n put(d.pull, prism(P, front2, br, bi, zp - 1.3, zp + 1.3));\n }\n const tw = drawers.map(() => tween(0));\n const B6 = register(stage, (_dt, now) => {\n let m = false;\n drawers.forEach((d, k) => {\n draw(d, tval(tw[k], now));\n if (!tdone(tw[k], now)) m = true;\n });\n return m;\n });\n bag.add(B6.unregister);\n let pull = value, picked = -2;\n function choose(f, now) {\n if (f === picked) return;\n picked = f;\n const a = f < 0 ? 1 : f;\n tw.forEach((t, k) => tset(t, k === a ? f < 0 ? pull * REST3 : pull : 0, now, 0));\n drawers.forEach((d, k) => d.front.sil.classList.toggle("hi", k === a));\n read.textContent = f < 0 ? "rest" : `drawer ${N3 - f} \\xB7 0`;\n B6.wake();\n }\n tw[1] = tween(pull * REST3);\n choose(-1, -1e9);\n drawers.forEach((d, k) => draw(d, tval(tw[k], 0)));\n const c = Math.cos(C.az), s = Math.sin(C.az), zf = Math.sqrt(1 - C.k * C.k);\n const onFront = (p, yf) => {\n const x = (p[0] - C.ox) / (C.S * c) + yf * s / c;\n return [x, ((x * s + yf * c) * C.k - (p[1] - C.oy) / C.S) / zf];\n };\n function pick(p) {\n for (let k = N3 - 1; k >= 0; k--) {\n const [x, z2] = onFront(p, D3 + FT3 + (k === 1 ? pull * REST3 : 0)), d = drawers[k];\n if (x >= M && x <= W3 - M && z2 >= d.z0 && z2 <= d.z1) return k;\n }\n const z = onFront(p, D3 + FT3)[1];\n return drawers.reduce((best, d, k) => Math.abs(z - (d.z0 + d.z1) / 2) < Math.abs(z - (drawers[best].z0 + drawers[best].z1) / 2) ? k : best, 0);\n }\n bag.add(pointer(stage, { move: (p) => choose(pick(p), performance.now()), leave: () => choose(-1, performance.now()) }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n pull = v;\n const f = picked;\n picked = -2;\n choose(f, performance.now());\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/elevator.ts\nvar SX = 32;\nvar SY = 34;\nvar FH = 36;\nvar CH2 = 25;\nvar T3 = 2.5;\nvar NF = 4;\nvar TOP = 3 * FH + CH2 + 7;\nvar CAR = [5, 7, 28, 27];\nvar RX = 16.5;\nvar CW = [-8, 11, -2, 23];\nvar CWH = 17;\nvar WALL = 2;\nvar BASE = fillet([[-16, -2], [33, -2], [33, -56], [76, -56], [76, 38], [-16, 38]], [3, 2, 4, 6, 6, 6]);\nvar FLOOR = fillet([[33, -56], [72, -56], [72, -6], [43, -6], [43, 34], [33, 34]], [4, 4, 4, 3, 3, 3]);\nvar CWX = (CW[0] + CW[2]) / 2;\nvar SR = (RX - CWX) / 2;\nvar SC = [CWX + SR, SY / 2, TOP + 3 + SR + 2];\nvar REST_Z = 1.5 * FH - 4;\nvar NAMES = ["ground", "floor 1", "floor 2", "floor 3"];\nvar mount6 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.3);\n fit(C, [[-16, -56, -8], [76, 38, -8], [76, -56, -8], [-16, 38, -8], [SC[0], SC[1], SC[2] + SR]], 200, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const block = (box, r, b, z0, z1) => {\n const [o, i] = rings(...box, r, b);\n put(solid(g), prism(P, front2, o, i, z0, z1));\n };\n const line = (cls) => mk("path", { class: cls }, g);\n const slab3 = (pts, z0, z1) => {\n const n = pts.length, near = pts.map((p, i) => {\n const q = pts[(i + 1) % n];\n return q[1] - p[1] - (q[0] - p[0]) > 0;\n });\n const runOf = (want) => {\n const s = near.findIndex((f, i) => f === want && near[(i + n - 1) % n] !== want), out = [];\n for (let i = s; near[i % n] === want; i++) out.push(pts[i % n]);\n return out.concat([pts[(s + out.length) % n]]);\n };\n const at2 = (r, z) => r.map((p) => P(p[0], p[1], z)), back = runOf(false), fore = runOf(true);\n const sil = mk("path", { class: "sil", d: poly(at2(back, z1).concat(at2(fore, z0))) }, g);\n mk("path", { class: "nf lo", d: open(at2(fore, z1)) }, g);\n return sil;\n };\n slab3(BASE, -8, -T3);\n block([-14 - WALL, -WALL, -14, SY], 1, 0.5, -T3, TOP);\n block([-14, -WALL, SX, 0], 1, 0.5, -T3, TOP);\n block([RX - 1.3, 3, RX + 1.3, 5.4], 0.8, 0.5, -T3, TOP);\n const [cwo, cwi] = rings(...CW, 1.6, 0.8);\n const cw = solid(g), cwBands = line("nf lo"), cwRope = line("nf");\n const [co, ci] = rings(...CAR, 2.4, 1.1);\n const car = solid(g), door = line("nf"), split = line("nf lo");\n const [ho, hi] = rings(RX - 1.7, 5.4, RX + 1.7, 28.6, 0.8, 0.5);\n const head = solid(g), rope = line("nf");\n block([RX - 1.3, 28.6, RX + 1.3, 31], 0.8, 0.5, -T3, TOP);\n block([-14 - WALL, -WALL, SX + 1, SY + 1], 4, 1.4, TOP, TOP + 3);\n block([CWX + 2, 4, RX - 2, SY / 2 - 3], 2, 1, TOP + 3, SC[2] + 4);\n mk("path", { class: "nf", d: seg(P(RX, SY / 2, TOP + 3), P(RX, SY / 2, SC[2])) + seg(P(CWX, SY / 2, TOP + 3), P(CWX, SY / 2, SC[2])) }, g);\n const disc2 = (y, r) => circ(r, 28).map((q) => P(SC[0] + q.u, y, SC[2] + q.v));\n put(solid(g), { sil: poly(hull(disc2(SY / 2 - 1.8, SR).concat(disc2(SY / 2 + 1.8, SR)))), crease: poly(disc2(SY / 2 + 1.8, SR - 1.4)) });\n const spokes = line("nf lo");\n mk("path", { class: "nf lo", d: poly(disc2(SY / 2 + 1.8, 2.6)) }, g);\n const lands = [];\n for (let f = 0; f < NF; f++) {\n const z = f * FH, sil = slab3(FLOOR, z - T3, z);\n if (f < NF - 1) for (const y of [-53, -15]) block([62, y, 68, y + 6], 1.5, 0.8, z, z + FH - T3);\n const dots = [];\n for (let k = 0; k <= f; k++) {\n const d = flatDot(g, C, 0.8, "dot off");\n place(d, P(37, CAR[1] - 3 - k * 3, z));\n dots.push(d);\n }\n lands.push({ sil, dots });\n }\n const sp = spring(REST_Z, { k: value, c: 1.8 * Math.sqrt(value) });\n let drawn = NaN, chosen = null;\n function draw(z) {\n const zc = 3 * FH - z + 6, mid = (CAR[1] + CAR[3]) / 2;\n put(cw, prism(P, front2, cwo, cwi, zc, zc + CWH));\n cwBands.setAttribute("d", [5, 9, 13].map((v) => seg(P(CW[0] + 0.6, CW[3], zc + v), P(CW[2] - 0.6, CW[3], zc + v))).join(""));\n cwRope.setAttribute("d", seg(P(CWX, SY / 2, zc + CWH), P(CWX, SY / 2, TOP)));\n put(car, prism(P, front2, co, ci, z, z + CH2));\n door.setAttribute("d", poly(rrect(CAR[1] + 3.5, z + 1.2, CAR[3] - 3.5, z + CH2 - 4, 1.2, 4).map((q) => P(CAR[2], q.u, q.v))));\n split.setAttribute("d", seg(P(CAR[2], mid, z + 1.2), P(CAR[2], mid, z + CH2 - 4)));\n put(head, prism(P, front2, ho, hi, z + CH2, z + CH2 + 2.4));\n rope.setAttribute("d", seg(P(RX, SY / 2, z + CH2 + 2.4), P(RX, SY / 2, TOP)));\n const a = z / SR, y = SY / 2 + 1.8, at2 = (t, r) => P(SC[0] + Math.cos(t) * r, y, SC[2] + Math.sin(t) * r);\n spokes.setAttribute("d", [0, 1, 2, 3, 4, 5].map((k) => seg(at2(a + k * Math.PI / 3, 2.6), at2(a + k * Math.PI / 3, SR - 1.4))).join(""));\n }\n const B6 = register(stage, (dt) => {\n const m = stepS(sp, dt);\n if (sp.x !== drawn) {\n drawn = sp.x;\n draw(sp.x);\n }\n return m;\n });\n bag.add(B6.unregister);\n const base = P(CAR[2], SY / 2, 0)[1], perZ = base - P(CAR[2], SY / 2, 1)[1];\n const pick = (p) => clamp(Math.floor(((base - p[1]) / perZ + 8) / FH), 0, NF - 1);\n function choose(f) {\n if (f === chosen) return;\n chosen = f;\n sp.t = f < 0 ? REST_Z : f * FH;\n car.sil.classList.toggle("hi", f < 0);\n lands.forEach((l, i) => {\n l.sil.classList.toggle("hi", i === f);\n l.dots.forEach((d) => d.setAttribute("class", i === f ? "dot m" : "dot off"));\n });\n read.textContent = f < 0 ? "rest" : NAMES[f];\n B6.wake();\n }\n choose(-1);\n bag.add(pointer(stage, { move: (p) => choose(pick(p)), leave: () => choose(-1) }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n sp.k = v;\n sp.c = 1.8 * Math.sqrt(v);\n B6.wake();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/exploded.ts\nvar LAY = [\n { name: "surface", r: [0, 0, 132, 96], rad: 7, segs: [[[1.5, 12], [130.5, 12]]], dots: [[7, 6], [13, 6], [19, 6]] },\n { name: "sidebar", r: [5, 17, 38, 91], rad: 4, segs: [[[10, 25], [28, 25]], [[10, 33], [32, 33]], [[10, 41], [24, 41]], [[10, 49], [30, 49]], [[10, 83], [22, 83]]] },\n { name: "card", r: [48, 22, 120, 62], rad: 5, segs: [[[54, 30], [92, 30]], [[54, 38], [112, 38]], [[54, 46], [104, 46]], [[54, 54], [80, 54]]] },\n { name: "popover", r: [80, 50, 126, 86], rad: 4, segs: [[[86, 58], [118, 58]], [[86, 74], [116, 74]], [[86, 80], [108, 80]]], sel: [83, 62, 123, 70] }\n];\nvar REST4 = 0.18;\nvar TK = 2.4;\nfunction inside(pt, pg) {\n let c = false;\n for (let i = 0, j = pg.length - 1; i < pg.length; j = i++) {\n const [xi, yi] = pg[i], [xj, yj] = pg[j];\n if (yi > pt[1] !== yj > pt[1] && pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi) c = !c;\n }\n return c;\n}\nvar mount7 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let GAP4 = value, act = -1, lastP = null;\n const e = spring(REST4, { eps: 2e-3 });\n const C = Cam(45, 0.5, 1.42);\n fit(C, [[0, 0, 0], [132, 96, 0], [132, 0, 0], [0, 96, 0], [0, 0, 3 * 34 + TK], [132, 0, 3 * 34 + TK]], 180, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const els = LAY.map((L7, i) => {\n const [ring, inner3] = rings(...L7.r, L7.rad, 1.3);\n const ext = extremes(P, ring);\n const guide = i > 0 ? mk("path", { class: "nf dash" }, g) : null;\n const el = solid(g);\n const marks = mk("path", { class: "nf" }, el.g);\n const selRing = L7.sel ? rrect(...L7.sel, 2) : null;\n const selEl = L7.sel ? mk("path", { class: "nf sil" }, el.g) : null;\n const dotEls = L7.dots ? L7.dots.map(() => flatDot(el.g, C, 1.6, "nf")) : null;\n return { ring, inner: inner3, ext, guide, el, marks, selRing, selEl, dotEls };\n });\n const corners = ([x0, y0, x1, y1], z) => [P(x0, y0, z), P(x1, y0, z), P(x1, y1, z), P(x0, y1, z)];\n function pick(p) {\n if (!p) return -1;\n for (let i = LAY.length - 1; i >= 0; i--) if (inside(p, corners(LAY[i].r, i * GAP4 * e.t + TK))) return i;\n return -1;\n }\n function setAct(a) {\n if (a === act) return;\n act = a;\n els.forEach((E2, i) => E2.el.sil.classList.toggle("hi", i === a));\n B6.wake();\n }\n const B6 = register(stage, (dt) => {\n const m = stepS(e, dt), z = (i) => i * GAP4 * e.x;\n LAY.forEach((L7, i) => {\n const E2 = els[i], zi = z(i), zt = zi + TK;\n put(E2.el, prism(P, front2, E2.ring, E2.inner, zi, zt));\n E2.marks.setAttribute("d", L7.segs.map(([a, b]) => seg(P(a[0], a[1], zt), P(b[0], b[1], zt))).join(""));\n if (E2.selEl && E2.selRing) E2.selEl.setAttribute("d", poly(ringAt(P, E2.selRing, zt)));\n if (E2.dotEls && L7.dots) L7.dots.forEach(([dx, dy], k) => place(E2.dotEls[k], P(dx, dy, zt)));\n if (E2.guide) {\n const zp = z(i - 1) + TK;\n E2.guide.setAttribute("d", E2.ext.map((q) => seg(P(q.u, q.v, zi), P(q.u, q.v, zp))).join(""));\n }\n });\n read.textContent = act >= 0 ? `0${act + 1} \\xB7 ${LAY[act].name} \\xB7 z ${z(act).toFixed(1)}` : `gap ${(GAP4 * e.x).toFixed(1)}`;\n return m;\n });\n bag.add(B6.unregister);\n bag.add(pointer(stage, {\n move: (p) => {\n lastP = p;\n e.t = REST4 + (1 - REST4) * clamp((p[0] - 60) / 280, 0, 1);\n setAct(pick(p));\n B6.wake();\n },\n leave: () => {\n lastP = null;\n e.t = REST4;\n setAct(-1);\n B6.wake();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n GAP4 = v;\n if (lastP) setAct(pick(lastP));\n B6.wake();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/keyboard.ts\nvar U2 = 14;\nvar PAD = 0.8;\nvar TAPER = 1.8;\nvar Z03 = 1;\nvar TRAVEL = 8;\nvar BZ = 6;\nvar CASE = 8;\nvar W4 = 15;\nvar ROW_H = [12.6, 11.3, 10.4, 11, 11.8];\nvar L = (s) => s.split(" ").map((c) => [1, "key " + c]);\nvar ROWS = [\n [[1, "esc"], ...L("1 2 3 4 5 6 7 8 9 0 - ="), [2, "bksp"]],\n [[1.5, "tab"], ...L("q w e r t y u i o p [ ]"), [1.5, "key \\\\"]],\n [[1.75, "caps"], ...L("a s d f g h j k l ; \'"), [2.25, "enter"]],\n [[2.25, "shift l"], ...L("z x c v b n m , . /"), [2.75, "shift r"]],\n [[1.5, "ctrl l"], [1, "super"], [1.5, "alt l"], [7, "space"], [1.5, "alt r"], [1, "fn"], [1.5, "ctrl r"]]\n];\nvar HOMING = ["key f", "key j"];\nvar falloff2 = (u) => clamp(1 - u, 0, 1);\nvar mount8 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.42);\n fit(C, [[-BZ, -BZ, -CASE], [W4 * U2 + BZ, 5 * U2 + BZ, -CASE], [W4 * U2 + BZ, -BZ, -CASE], [-BZ, 5 * U2 + BZ, -CASE], [0, 0, ROW_H[0]]], 200, 166);\n const P = proj(C), front2 = facing(C);\n let R9 = value, over = null, lit2 = null;\n const g = mk("g", {}, svg);\n const [cr, ci] = rings(-BZ, -BZ, W4 * U2 + BZ, 5 * U2 + BZ, 9, 2.2);\n put(solid(g), prism(P, front2, cr, ci, -CASE, 0));\n const keys2 = [];\n ROWS.forEach((row, r) => {\n let x = 0;\n for (const [w, name] of row) {\n const x0 = x * U2, x1 = (x + w) * U2, y0 = r * U2, y1 = (r + 1) * U2, t = PAD + TAPER;\n const el = solid(g);\n keys2.push({\n r,\n name,\n x0,\n x1,\n y0,\n y1,\n h: ROW_H[r],\n foot: rrect(x0 + PAD, y0 + PAD, x1 - PAD, y1 - PAD, 2.6, 4),\n top: rrect(x0 + t, y0 + t, x1 - t, y1 - t, 2, 4),\n inner: rrect(x0 + t + 0.9, y0 + t + 0.9, x1 - t - 0.9, y1 - t - 0.9, 1.2, 4),\n el,\n sp: spring(ROW_H[r], { eps: 0.02 }),\n drawn: NaN,\n bump: HOMING.includes(name) ? mk("path", { class: "lo nf" }, el.g) : null\n });\n x += w;\n }\n });\n const enter = keys2.find((k) => k.name === "enter");\n function sink(at2, pressed, radius, depth2) {\n for (const k of keys2) {\n const dx = Math.max(k.x0 - at2[0], 0, at2[0] - k.x1), dy = Math.max(k.y0 - at2[1], 0, at2[1] - k.y1);\n const f = k === pressed ? 1 : falloff2(Math.hypot(dx, dy) / (radius * U2));\n k.sp.t = k.h - TRAVEL * depth2 * f;\n }\n }\n const restAt = [(enter.x0 + enter.x1) / 2, (enter.y0 + enter.y1) / 2];\n sink(restAt, enter, 1.6, 0.6);\n for (const k of keys2) k.sp.x = k.sp.t;\n function drawKey(k) {\n const h = k.sp.x;\n if (h === k.drawn) return;\n k.drawn = h;\n put(k.el, { sil: poly(hull(ringAt(P, k.foot, Z03).concat(ringAt(P, k.top, h)))), crease: open(ringAt(P, run(k.inner, front2), h)) });\n if (k.bump) {\n const cx = (k.x0 + k.x1) / 2, cy = k.y1 - PAD - TAPER - 2.4;\n k.bump.setAttribute("d", seg(P(cx - 2.2, cy, h), P(cx + 2.2, cy, h)));\n }\n }\n function light(k) {\n if (k === lit2) return;\n lit2?.el.sil.classList.remove("hi");\n lit2 = k;\n k.el.sil.classList.add("hi");\n }\n const B6 = register(stage, (dt) => {\n let m = false;\n for (const k of keys2) {\n if (stepS(k.sp, dt)) m = true;\n drawKey(k);\n }\n return m;\n });\n bag.add(B6.unregister);\n const keyAt = (r, x) => keys2.find((k) => k.r === r && x >= k.x0 && x < k.x1);\n function hit(p) {\n for (let r = ROWS.length - 1; r >= 0; r--) {\n const q2 = unproj(C, p[0], p[1], ROW_H[r]);\n if (q2[1] < r * U2 || q2[1] >= (r + 1) * U2 || q2[0] < 0 || q2[0] >= W4 * U2) continue;\n return { at: q2, key: keyAt(r, q2[0]) };\n }\n const q = unproj(C, p[0], p[1], ROW_H[2]);\n if (q[0] < -BZ || q[0] > W4 * U2 + BZ || q[1] < -BZ || q[1] > 5 * U2 + BZ) return null;\n const at2 = [clamp(q[0], 0, W4 * U2 - 0.01), clamp(q[1], 0, 5 * U2 - 0.01)];\n return { at: at2, key: keyAt(Math.floor(at2[1] / U2), at2[0]) };\n }\n function retarget() {\n if (over) {\n sink(over.at, over.key, R9, 1);\n light(over.key);\n read.textContent = over.key.name;\n } else {\n sink(restAt, enter, 1.6, 0.6);\n light(enter);\n read.textContent = "rest";\n }\n B6.wake();\n }\n light(enter);\n bag.add(pointer(stage, {\n move: (p) => {\n over = hit(p);\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v;\n if (over) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/laptop.ts\nvar W5 = 150;\nvar D4 = 106;\nvar HB = 4;\nvar T4 = 2.4;\nvar HZ = HB + T4 / 2;\nvar R2 = 7;\nvar B2 = 1.1;\nvar MIN = 15;\nvar REST5 = 100;\nvar BZ2 = 4.2;\nvar CHIN = 7.5;\nvar KU = 8.6;\nvar KY = 6;\nvar lidAt = (th) => {\n const c = Math.cos(rad(th)), s = Math.sin(rad(th));\n return (u, v, w) => [u, v * c - w * s, HZ + v * s + w * c];\n};\nfunction keys() {\n const out = [], x0 = (W5 - 14.5 * KU) / 2;\n const row = (y2, h2, widths) => {\n let x = x0;\n for (const w of widths) {\n out.push([x, y2, x + w * KU, y2 + h2]);\n x += w * KU;\n }\n };\n const ones = (n) => Array(n).fill(1);\n row(KY, KU * 0.6, Array(14).fill(14.5 / 14));\n let y = KY + KU * 0.6;\n for (const w of [[...ones(13), 1.5], [1.5, ...ones(13)], [1.75, ...ones(11), 1.75], [2.25, ...ones(10), 2.25]]) {\n row(y, KU, w);\n y += KU;\n }\n row(y, KU, [1, 1, 1, 1.25, 5, 1.25, 1]);\n const ax2 = x0 + 11.5 * KU, h = KU / 2;\n out.push([ax2, y + h, ax2 + KU, y + KU], [ax2 + KU, y, ax2 + 2 * KU, y + h], [ax2 + KU, y + h, ax2 + 2 * KU, y + KU], [ax2 + 2 * KU, y + h, ax2 + 3 * KU, y + KU]);\n return { out, y1: y + KU, x0 };\n}\nvar mount9 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let maxA = value, over = null;\n const C = Cam(45, 0.5, 1.3), pts = [[0, 0, 0], [W5, 0, 0], [0, D4, 0], [W5, D4, 0]];\n for (const th of [MIN, 90, REST5, 125]) for (const u of [0, W5]) for (const v of [0, D4]) for (const w of [-T4 / 2, T4 / 2]) pts.push(lidAt(th)(u, v, w));\n fit(C, pts, 200, 166);\n const P = proj(C), front2 = facing(C);\n const o = P(0, 0, 0), ex = P(1, 0, 0), ey = P(0, 1, 0), ez = P(0, 0, 1);\n const r1 = [ex[0] - o[0], ey[0] - o[0], ez[0] - o[0]], r22 = [ex[1] - o[1], ey[1] - o[1], ez[1] - o[1]];\n let eye = [r1[1] * r22[2] - r1[2] * r22[1], r1[2] * r22[0] - r1[0] * r22[2], r1[0] * r22[1] - r1[1] * r22[0]];\n if (eye[2] < 0) eye = eye.map((n) => -n);\n const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n const g = mk("g", {}, svg);\n const [br, bi] = rings(0, 0, W5, D4, R2, B2);\n put(solid(g), prism(P, front2, br, bi, 0, HB));\n const top = (q) => poly(q.map((s) => P(s.u, s.v, HB)));\n const kb = keys();\n mk("path", { class: "nf lo", d: kb.out.map(([a, b, c, d]) => top(rrect(a + 0.5, b + 0.5, c - 0.5, d - 0.5, 1.1, 2))).join("") }, g);\n mk("path", { class: "nf", d: top(rrect(kb.x0 - 1.6, KY - 1.6, W5 - kb.x0 + 1.6, kb.y1 + 1.6, 3, 4)) }, g);\n mk("path", { class: "nf", d: top(rrect(W5 / 2 - 35, D4 - 45, W5 / 2 + 35, D4 - 5, 3.2, 4)) }, g);\n const lidG = mk("g", {}, g), lid = solid(lidG), scr2 = mk("g", {}, lidG);\n lid.sil.classList.add("hi");\n const [lr, li] = rings(0, 0, W5, D4, R2, B2);\n const disp = mk("path", { class: "nf" }, scr2), bar2 = mk("path", { class: "nf lo" }, scr2), dock = mk("path", { class: "nf lo" }, scr2);\n const win = mk("path", {}, scr2), strip2 = mk("path", { class: "nf lo" }, scr2);\n const cam = mk("circle", { r: 0.8, class: "dot off" }, scr2);\n const lights = [0, 1, 2].map(() => mk("circle", { r: 0.8, class: "dot off" }, scr2));\n const VT = D4 - BZ2, WX02 = 30, WX12 = 104, WV0 = CHIN + 16, WV1 = VT - 12;\n let faced = null, drawn = NaN;\n function drawLid(th) {\n if (th === drawn) return;\n drawn = th;\n const f = lidAt(th), c = Math.cos(rad(th)), s = Math.sin(rad(th));\n const at2 = (u, v, w) => P(...f(u, v, w)), on = (u, v) => at2(u, v, -T4 / 2);\n const plane = (q, w) => q.map((p) => at2(p.u, p.v, w));\n const shows = dot([0, s, -c], eye) > 0, wf = shows ? -T4 / 2 : T4 / 2;\n const wall = (q) => dot([q.nu, q.nv * c, q.nv * s], eye) > 0;\n put(lid, { sil: poly(hull(plane(lr, -T4 / 2).concat(plane(lr, T4 / 2)))), crease: open(plane(run(li, wall), wf)) });\n if (shows !== faced) {\n faced = shows;\n if (shows) lid.g.after(scr2);\n else lid.g.before(scr2);\n }\n const face = (q) => poly(q.map((p) => on(p.u, p.v)));\n disp.setAttribute("d", face(rrect(BZ2, CHIN, W5 - BZ2, VT, 3, 4)));\n bar2.setAttribute("d", seg(on(BZ2 + 1.5, VT - 4), on(W5 - BZ2 - 1.5, VT - 4)));\n dock.setAttribute("d", face(rrect(W5 / 2 - 28, CHIN + 2.5, W5 / 2 + 28, CHIN + 8.5, 2.2, 4)));\n win.setAttribute("d", face(rrect(WX02, WV0, WX12, WV1, 2.6, 4)));\n strip2.setAttribute("d", seg(on(WX02, WV1 - 6), on(WX12, WV1 - 6)) + seg(on(WX02 + 20, WV0), on(WX02 + 20, WV1 - 6)));\n place(cam, on(W5 / 2, D4 - BZ2 / 2));\n lights.forEach((el, k) => place(el, on(WX02 + 4 + k * 3.4, WV1 - 3)));\n }\n const sp = spring(REST5, { eps: 0.05 });\n const Bk = register(stage, (dt) => {\n const m = stepS(sp, dt);\n drawLid(sp.x);\n return m;\n });\n bag.add(Bk.unregister);\n drawLid(REST5);\n const yTop = P(W5 / 2, 0, HZ + D4)[1] + 12, yBot = P(W5 / 2, D4, 0)[1] - 12;\n function retarget() {\n if (over === null) {\n sp.t = Math.min(REST5, maxA);\n read.textContent = "rest";\n } else {\n sp.t = lerp(maxA, MIN, clamp((over - yTop) / (yBot - yTop), 0, 1));\n read.textContent = sp.t <= MIN + 0.5 ? "shut" : sp.t >= maxA - 0.5 ? "open" : `lid ${Math.round(sp.t)}\\xB0`;\n }\n Bk.wake();\n }\n bag.add(pointer(stage, {\n move: (p) => {\n over = p[1];\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n maxA = v;\n retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/lockers.ts\nvar COLS = 6;\nvar ROWS2 = 2;\nvar W6 = 24;\nvar HR2 = 38;\nvar GAP = 1.2;\nvar FM = 3;\nvar ZP = 6;\nvar D5 = 26;\nvar T5 = 1.4;\nvar DEP2 = 23;\nvar BW = COLS * W6 + 2 * FM;\nvar BH2 = ROWS2 * HR2 + 2 * FM;\nvar DW = W6 - 2 * GAP;\nvar DH = HR2 - 2 * GAP;\nvar REST_I = 5;\nvar REST_A = 22;\nvar FAR = 120;\nvar OUTLINE = rrect(0, 0, DW, DH, 1.8, 4);\nvar VENTS = [0, 1, 2].map((k) => rrect(4, DH - 6.6 - k * 3.2, DW - 4, DH - 5.2 - k * 3.2, 0.7, 2));\nvar PLATE = rrect(DW / 2 - 4.5, DH - 19, DW / 2 + 4.5, DH - 15, 1, 3);\nvar CUP = rrect(DW - 5.6, DH / 2 - 7, DW - 2.8, DH / 2 + 2, 1.2, 3);\nvar FRAME = rrect(2.6, 2.6, DW - 2.6, DH - 2.6, 1.4, 3);\nvar area = (pts) => pts.reduce((s, p, i) => {\n const q = pts[(i + 1) % pts.length];\n return s + p[0] * q[1] - q[0] * p[1];\n}, 0);\nvar mount10 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.6);\n const sw = [Math.cos(rad(FAR)) * DW, Math.sin(rad(FAR)) * DW];\n fit(C, [\n [-4, -4, 0],\n [BW + 4, D5 + 4, 0],\n [BW + 4, -4, 0],\n [-4, D5 + 4, 0],\n [0, 0, ZP + BH2],\n [BW, 0, ZP + BH2],\n [FM + sw[0], D5 + sw[1], ZP + FM],\n [FM + sw[0], D5 + sw[1], ZP + BH2 - FM]\n ], 200, 166);\n const P = proj(C), front2 = facing(C);\n let MAX2 = value, act = -1;\n function onFace(p, y0) {\n const o = P(0, y0, 0), a = P(1, y0, 0), b = P(0, y0, 1);\n const ex = [a[0] - o[0], a[1] - o[1]], ez = [b[0] - o[0], b[1] - o[1]], det = ex[0] * ez[1] - ex[1] * ez[0];\n const qx = p[0] - o[0], qy = p[1] - o[1];\n return [(qx * ez[1] - qy * ez[0]) / det, (ex[0] * qy - ex[1] * qx) / det];\n }\n function through(A2, B7, L7) {\n const pa = P(...A2), pb = P(...B7), a = onFace(pa, D5), b = onFace(pb, D5), m = 0.9;\n let t0 = 0, t1 = 1;\n const lim = [[a[0] - b[0], a[0] - (L7.x0 + m)], [b[0] - a[0], L7.x0 + DW - m - a[0]], [a[1] - b[1], a[1] - (L7.z0 + m)], [b[1] - a[1], L7.z0 + DH - m - a[1]]];\n for (const [p, q] of lim) {\n if (p === 0) {\n if (q < 0) return "";\n continue;\n }\n const r = q / p;\n if (p < 0) t0 = Math.max(t0, r);\n else t1 = Math.min(t1, r);\n }\n if (t1 - t0 < 0.01) return "";\n const at2 = (t) => [pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t];\n return seg(at2(t0), at2(t1));\n }\n const g = mk("g", {}, svg);\n const [pr, pi] = rings(-4, -4, BW + 4, D5 + 4, 5, 1.6);\n put(solid(g), prism(P, front2, pr, pi, 0, ZP));\n const [br, bi] = rings(0, 0, BW, D5, 3, 1.4);\n put(solid(g), prism(P, front2, br, bi, ZP, ZP + BH2));\n const lockers2 = [];\n for (let c = 0; c < COLS; c++) for (let r = 0; r < ROWS2; r++) {\n const x0 = FM + c * W6 + GAP, z0 = ZP + FM + r * HR2 + GAP;\n const lg = mk("g", {}, g);\n mk("path", { d: poly(OUTLINE.map((q) => P(x0 + q.u, D5, z0 + q.v))), class: "" }, lg);\n const L7 = { c, r, n: (ROWS2 - 1 - r) * COLS + c + 1, x0, z0, drawn: NaN, sign0: 0, tw: tween(0) };\n const zs = L7.z0 + DH - 9.5, x1 = L7.x0 + DW;\n const inside3 = [\n through([L7.x0, D5, L7.z0], [L7.x0, D5 - DEP2, L7.z0], L7),\n through([L7.x0, D5 - 1.6, zs], [x1, D5 - 1.6, zs], L7),\n through([L7.x0, D5 - 1.6, zs], [L7.x0, D5 - DEP2, zs], L7)\n ].join("");\n mk("path", { d: inside3, class: "nf lo" }, lg);\n L7.plate = mk("path", { class: "lo" }, lg);\n L7.face = mk("path", { class: "sil" }, lg);\n L7.marks = mk("path", { class: "nf lo" }, lg);\n L7.cup = mk("path", { class: "nf" }, lg);\n lockers2.push(L7);\n }\n const byN = (n) => lockers2.find((L7) => L7.n === n);\n function drawDoor(L7, th) {\n if (th === L7.drawn) return;\n L7.drawn = th;\n const cs = Math.cos(rad(th)), sn = Math.sin(rad(th));\n const at2 = (s2) => (q) => P(L7.x0 + q.u * cs - s2 * sn, D5 + T5 / 2 + q.u * sn + s2 * cs, L7.z0 + q.v);\n const fr = OUTLINE.map(at2(T5 / 2)), bk = OUTLINE.map(at2(-T5 / 2));\n const outward = area(fr) * L7.sign0 > 0, s = outward ? T5 / 2 : -T5 / 2;\n L7.plate.setAttribute("d", poly(hull(fr.concat(bk))));\n L7.face.setAttribute("d", poly(outward ? fr : bk));\n const marks = outward ? [...VENTS, PLATE] : [...VENTS, FRAME];\n L7.marks.setAttribute("d", marks.map((m) => poly(m.map(at2(s)))).join(""));\n L7.cup.setAttribute("d", outward ? poly(CUP.map(at2(s))) : "");\n }\n for (const L7 of lockers2) L7.sign0 = Math.sign(area(OUTLINE.map((q) => P(L7.x0 + q.u, D5 + T5, L7.z0 + q.v))));\n const B6 = register(stage, (_dt, now) => {\n let m = false;\n for (const L7 of lockers2) {\n drawDoor(L7, tval(L7.tw, now));\n if (!tdone(L7.tw, now)) m = true;\n }\n return m;\n });\n bag.add(B6.unregister);\n function hit(p) {\n const [x, z] = onFace(p, D5 + T5);\n if (x < 0 || x > BW || z < ZP || z > ZP + BH2) return -1;\n const c = clamp(Math.floor((x - FM) / W6), 0, COLS - 1), r = clamp(Math.floor((z - ZP - FM) / HR2), 0, ROWS2 - 1);\n return (ROWS2 - 1 - r) * COLS + c + 1;\n }\n function choose(n, force) {\n if (n === act && !force) return;\n act = n;\n const now = performance.now(), lit2 = n > 0 ? n : REST_I;\n for (const L7 of lockers2) {\n tset(L7.tw, L7.n === n ? MAX2 : n <= 0 && L7.n === REST_I ? REST_A : 0, now, 0);\n L7.face.classList.toggle("hi", L7.n === lit2);\n L7.cup.classList.toggle("hi", L7.n === lit2);\n }\n read.textContent = n > 0 ? "locker " + String(n).padStart(2, "0") : "rest";\n B6.wake();\n }\n byN(REST_I).tw = tween(REST_A);\n choose(0, true);\n bag.add(pointer(stage, { move: (p) => choose(Math.max(0, hit(p))), leave: () => choose(0) }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n MAX2 = v;\n if (act > 0) choose(act, true);\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/loupe.ts\nvar SW = 124;\nvar SD = 92;\nvar ST = 2.4;\nvar ROWS3 = 7;\nvar Y0 = 13;\nvar DY = 11;\nvar ML = 26;\nvar MR = 12;\nvar R3 = 27;\nvar LZ = 20;\nvar RIM = 1.2;\nvar GLASS = R3 - 1.4;\nvar LEGS = [45, 165, 285];\nvar FOOT = 35;\nvar PAD2 = 1.8;\nvar REST6 = [ML + 9, 46];\nvar S3 = 2.1;\nvar mount11 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, S3), top = LZ + RIM;\n const feet = LEGS.map((a) => [Math.cos(a * Math.PI / 180), Math.sin(a * Math.PI / 180)]);\n const [bx, by] = unproj(C, ...proj(C)(0, 0, 0), top);\n const ext = [[0, 0, -ST], [SW, 0, -ST], [0, SD, -ST], [SW, SD, -ST]];\n for (const q of circ(R3, 16)) ext.push([REST6[0] + bx + q.u, REST6[1] + by + q.v, top]);\n fit(C, ext, 200, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const [so, si] = rings(0, 0, SW, SD, 6, 1.4);\n put(solid(g), prism(P, front2, so, si, -ST, 0));\n const rowY = (i) => Y0 + i * DY;\n const lines = [[[ML, Y0 - 7], [ML, rowY(ROWS3 - 1) + 7]], ...Array.from({ length: ROWS3 }, (_, i) => [[ML, rowY(i)], [SW - MR, rowY(i)]])];\n mk("path", { class: "nf lo", d: lines.map(([p, q]) => seg(P(p[0], p[1], 0), P(q[0], q[1], 0))).join("") }, g);\n const legs = mk("path", { class: "nf" }, g);\n const rim = solid(g);\n const glint = mk("path", { class: "nf lo" }, g);\n const seen = mk("path", { class: "nf sil" }, g);\n const shine = circ(GLASS * 0.72, 64).slice(26, 36);\n const ring = circ(R3, 64), lens = circ(GLASS, 64), edge = rrect(0, 0, SW, SD, 6, 12), pad = circ(PAD2, 12);\n const floor = (x, y) => x >= 0 && x <= SW && y >= 0 && y <= SD ? 0 : -ST;\n const sx = spring(REST6[0]), sy = spring(REST6[1]), mag = spring(value);\n let cx = NaN, cy = NaN, dm = NaN;\n function draw() {\n if (sx.x === cx && sy.x === cy && mag.x === dm) return;\n const moved = sx.x !== cx || sy.x !== cy;\n cx = sx.x;\n cy = sy.x;\n dm = mag.x;\n const dx = cx + bx, dy = cy + by;\n if (moved) {\n const at2 = (q, z) => P(dx + q.u, dy + q.v, z);\n legs.setAttribute("d", feet.map(([c, s]) => {\n const fx = dx + c * FOOT, fy = dy + s * FOOT;\n const z = floor(fx, fy);\n return seg(P(fx, fy, z), P(dx + c * (R3 - 1), dy + s * (R3 - 1), LZ)) + poly(pad.map((q) => P(fx + q.u, fy + q.v, z)));\n }).join(""));\n put(rim, { sil: poly(hull(ring.map((q) => at2(q, LZ)).concat(ring.map((q) => at2(q, top))))), crease: poly(lens.map((q) => at2(q, top))) });\n glint.setAttribute("d", open(shine.map((q) => at2(q, top))));\n }\n const r = GLASS - 1;\n const cut2 = (p, q) => {\n const a0 = (p[0] - cx) * dm, a1 = (p[1] - cy) * dm, e0 = (q[0] - p[0]) * dm, e1 = (q[1] - p[1]) * dm;\n const A2 = e0 * e0 + e1 * e1, B6 = a0 * e0 + a1 * e1, D9 = B6 * B6 - A2 * (a0 * a0 + a1 * a1 - r * r);\n if (D9 <= 0) return "";\n const t0 = Math.max(0, (-B6 - Math.sqrt(D9)) / A2), t1 = Math.min(1, (-B6 + Math.sqrt(D9)) / A2);\n return t1 > t0 ? seg(P(dx + a0 + e0 * t0, dy + a1 + e1 * t0, top), P(dx + a0 + e0 * t1, dy + a1 + e1 * t1, top)) : "";\n };\n let d = edge.map((q, i) => cut2([q.u, q.v], [edge[(i + 1) % edge.length].u, edge[(i + 1) % edge.length].v])).join("");\n for (const [p, q] of lines) d += cut2(p, q);\n seen.setAttribute("d", d);\n }\n const loop = register(stage, (dt) => {\n const a = stepS(sx, dt), b = stepS(sy, dt), c = stepS(mag, dt);\n draw();\n return a || b || c;\n });\n bag.add(loop.unregister);\n rim.sil.classList.add("hi");\n read.textContent = "rest";\n draw();\n bag.add(pointer(stage, {\n move: (p) => {\n const [x, y] = unproj(C, p[0], p[1], 0);\n sx.t = clamp(x, ML, SW - MR);\n sy.t = clamp(y, Y0, rowY(ROWS3 - 1));\n read.textContent = `row ${Math.round((sy.t - Y0) / DY) + 1} \\xB7 0`;\n loop.wake();\n },\n leave: () => {\n sx.t = REST6[0];\n sy.t = REST6[1];\n read.textContent = "rest";\n loop.wake();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n mag.t = v;\n loop.wake();\n }, destroy: bag.dispose };\n};\n\n// src/figures/padlock.ts\nvar W7 = 56;\nvar D6 = 20;\nvar H4 = 44;\nvar B3 = 1.6;\nvar T6 = 4.2;\nvar S22 = 30;\nvar XL = 13;\nvar YC = D6 / 2;\nvar ARM2 = 13;\nvar SINK = 7;\nvar LIFT3 = 11;\nvar O1 = 0.42;\nvar REST_TURN = 26;\nvar R0 = 56;\nvar R1 = 200;\nvar SC2 = 2.75;\nvar TMAX = 100;\nvar VX = Math.SQRT1_2 * Math.sqrt(3);\nvar ss = (t) => t * t * (3 - 2 * t);\nfunction samples(pts) {\n return pts.map((p, i) => {\n const a = pts[(i + pts.length - 1) % pts.length], b = pts[(i + 1) % pts.length];\n const tx = b[0] - a[0], tz = b[1] - a[1], l = Math.hypot(tx, tz) || 1;\n return { u: p[0], v: p[1], nu: tz / l, nv: -tx / l };\n });\n}\nfunction cross(p, q, r, s) {\n const e0 = q[0] - p[0], e1 = q[1] - p[1], f0 = s[0] - r[0], f1 = s[1] - r[1], g0 = r[0] - p[0], g1 = r[1] - p[1];\n const d = e0 * f1 - e1 * f0;\n if (!d) return null;\n const t = (g0 * f1 - g1 * f0) / d, u = (g0 * e1 - g1 * e0) / d;\n return t > 0 && t < 1 && u > 0 && u < 1 ? [p[0] + t * e0, p[1] + t * e1] : null;\n}\nfunction untangle(L7) {\n for (let i = 0; i < L7.length - 3; i++) for (let j = L7.length - 2; j > i + 1; j--) {\n const x = cross(L7[i], L7[i + 1], L7[j], L7[j + 1]);\n if (x) {\n L7.splice(i + 1, j - i, x);\n break;\n }\n }\n return L7;\n}\nfunction tube2(Q, rho) {\n const n = Q.length, A2 = [], Bk = [];\n for (let i = 0; i < n; i++) {\n const a = Q[Math.max(i - 1, 0)], c = Q[i], b = Q[Math.min(i + 1, n - 1)];\n const u0 = [c[0] - a[0], c[1] - a[1]], u1 = [b[0] - c[0], b[1] - c[1]];\n const l0 = Math.hypot(u0[0], u0[1]) || 1, l1 = Math.hypot(u1[0], u1[1]) || 1;\n let tx = u0[0] / l0 + u1[0] / l1, ty = u0[1] / l0 + u1[1] / l1;\n const l = Math.hypot(tx, ty) || 1;\n tx /= l;\n ty /= l;\n A2.push([c[0] - ty * rho, c[1] + tx * rho]);\n Bk.push([c[0] + ty * rho, c[1] - tx * rho]);\n }\n const cap = (c) => {\n const o = [];\n for (let k = 7; k >= 1; k--) {\n const t = Math.PI * k / 8;\n o.push([c[0] + rho * Math.cos(t), c[1] + rho * 0.5 * Math.sin(t)]);\n }\n return o;\n };\n return poly([...untangle(A2), ...cap(Q[n - 1]), ...untangle(Bk).reverse(), ...cap(Q[0])]);\n}\nvar mount12 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const tm = spring(value);\n let over = false;\n const line = (P2, lift, th) => {\n const u = [Math.cos(rad(th)), -Math.sin(rad(th))], za = H4 + ARM2 + lift, r = S22 / 2;\n const at2 = (s, z) => P2(XL + u[0] * s, YC + u[1] * s, z);\n const pts = [at2(S22, Math.max(H4 - SINK + lift, H4)), at2(S22, za)];\n for (let i = 1; i < 28; i++) {\n const f = Math.PI * i / 28;\n pts.push(at2(r + r * Math.cos(f), za + r * Math.sin(f)));\n }\n pts.push(at2(0, za), at2(0, H4));\n return pts;\n };\n const C = Cam(45, 0.5, SC2);\n const id = (x, y, z) => [x, y, z];\n const ext = [[0, 0, 0], [W7, 0, 0], [0, D6, 0], [W7, D6, 0], [0, 0, H4]];\n for (const th of [0, 40, 80, TMAX]) for (const p of line(id, LIFT3, th)) ext.push([p[0], p[1], p[2] + T6]);\n fit(C, ext, 200, 170);\n const P = proj(C), rho = T6 * SC2;\n const face = fillet([[0, 0], [W7, 0], [W7, H4], [0, H4]], [14, 14, 7, 7], 12);\n const inner3 = samples(fillet([[B3, B3], [W7 - B3, B3], [W7 - B3, H4 - B3], [B3, H4 - B3]], [14 - B3, 14 - B3, 7 - B3, 7 - B3], 12));\n const g = mk("g", {}, svg);\n put(solid(g), {\n sil: poly(hull(face.map((p) => P(p[0], 0, p[1])).concat(face.map((p) => P(p[0], D6, p[1]))))),\n crease: open(run(inner3, (q) => q.nu * VX + q.nv > 0).map((q) => P(q.u, D6, q.v)))\n });\n const kx = W7 / 2, kz = H4 * 0.56, kr = 4.4, kw = 1.8, kb = kz - 12, a0 = Math.asin(kw / kr), key = [];\n for (let i = 0; i <= 20; i++) {\n const t = -Math.PI / 2 + a0 + (2 * Math.PI - 2 * a0) * i / 20;\n key.push([kx + kr * Math.cos(t), kz + kr * Math.sin(t)]);\n }\n for (let i = 0; i <= 6; i++) {\n const t = Math.PI + Math.PI * i / 6;\n key.push([kx + kw * Math.cos(t), kb + kw * Math.sin(t)]);\n }\n mk("path", { d: poly(key.map((p) => P(p[0], D6, p[1]))), class: "nf" }, g);\n const hole = circ(T6 + 1.3, 20);\n for (const x of [XL, XL + S22]) mk("path", { d: poly(hole.map((q) => P(x + q.u, YC + q.v, H4))), class: "nf lo" }, g);\n const bar2 = mk("path", { class: "sil" }, g);\n const pose3 = (o, max) => [LIFT3 * ss(clamp(o / O1, 0, 1)), max * ss(clamp((o - O1) / (1 - O1), 0, 1))];\n const restO = () => O1 + (1 - O1) * (0.5 - Math.sin(Math.asin(1 - 2 * REST_TURN / tm.t) / 3));\n const sp = spring(restO(), { eps: 2e-3 });\n let dO = NaN, dT = NaN;\n function draw() {\n if (sp.x === dO && tm.x === dT) return;\n dO = sp.x;\n dT = tm.x;\n const [lift, th] = pose3(sp.x, tm.x);\n bar2.setAttribute("d", tube2(line(P, lift, th), rho));\n }\n const loop = register(stage, (dt) => {\n const a = stepS(sp, dt), b = stepS(tm, dt);\n draw();\n return a || b;\n });\n bag.add(loop.unregister);\n const c0 = P(W7 / 2, D6 / 2, H4 / 2);\n function retarget() {\n const [lift, th] = pose3(sp.t, tm.t), shut = over && lift < SINK;\n read.textContent = !over ? "rest" : shut ? "locked" : th < 1 ? "open" : Math.round(th) + "\\xB0";\n bar2.classList.toggle("hi", !shut);\n loop.wake();\n }\n retarget();\n bag.add(pointer(stage, {\n move: (p) => {\n over = true;\n sp.t = clamp((R1 - Math.hypot(p[0] - c0[0], p[1] - c0[1])) / (R1 - R0), 0, 1);\n retarget();\n },\n leave: () => {\n over = false;\n sp.t = restO();\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n tm.t = v;\n if (!over) sp.t = restO();\n retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/patch.ts\nvar NC = 12;\nvar PITCH2 = 15;\nvar GAP2 = 8;\nvar EAR2 = 15;\nvar MAR = 5;\nvar H5 = 44;\nvar T7 = 2.6;\nvar DEEP = 18;\nvar X02 = EAR2 + MAR;\nvar W8 = 2 * X02 + NC * PITCH2 + GAP2;\nvar ZT2 = 30;\nvar ZB4 = 12.5;\nvar JW = 5;\nvar JH = 4.2;\nvar NW = 1.9;\nvar NH = 1.7;\nvar JD = 2.4;\nvar PW = 4.3;\nvar PH2 = 3.5;\nvar PL = 4;\nvar BL = 5;\nvar CR2 = 1.8;\nvar OUT2 = 6;\nvar LEAN = 10;\nvar LZ2 = 7;\nvar LIFT4 = 9;\nvar EMPTY = [2, 10, 18];\nvar REST7 = 7;\nvar HIT_Y = 2.5;\nvar JIT = (p) => [p * 7 % 5 - 2, p * 11 % 7 - 3];\nvar falloff3 = (d, R9) => d === 0 ? 0 : clamp(1 - (d - 1) / R9, 0, 1);\nfunction tube3(q, w) {\n const n = q.length, L7 = [], Rt = [], nrm = [];\n for (let i = 0; i < n; i++) {\n const a = q[Math.max(0, i - 1)], b = q[Math.min(n - 1, i + 1)], l = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;\n const t = [(b[0] - a[0]) / l, (b[1] - a[1]) / l];\n nrm.push(t);\n L7.push([q[i][0] - t[1] * w, q[i][1] + t[0] * w]);\n Rt.push([q[i][0] + t[1] * w, q[i][1] - t[0] * w]);\n }\n const cap = (c, t, s) => {\n const out = [];\n for (let k = 1; k < 6; k++) {\n const th = k / 6 * Math.PI, cs = Math.cos(th), sn = Math.sin(th);\n out.push([c[0] + s * w * (-t[1] * cs + t[0] * sn), c[1] + s * w * (t[0] * cs + t[1] * sn)]);\n }\n return out;\n };\n return poly([...L7, ...cap(q[n - 1], nrm[n - 1], 1), ...Rt.reverse(), ...cap(q[0], nrm[0], -1)]);\n}\nvar mount13 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const S9 = 1.5, C = Cam(45, 0.5, S9);\n const tipZ = ZT2 + 27 + LIFT4 + LZ2, lowZ = ZB4 - 26 - LZ2;\n fit(C, [[0, -DEEP, H5], [W8, -DEEP, H5], [0, 0, 0], [W8, 0, 0], [X02, 34, tipZ], [W8 - X02, 34, lowZ], [X02, 34, lowZ]], 200, 166);\n const P = proj(C), front2 = facing(C);\n let R9 = value, over = -1, lit2 = null;\n const at2 = (ring, cx, y, cz) => ring.map((q) => P(cx + q.u, y, cz + q.v));\n const seen = (q) => 0.612 * q.nu + 0.5 * q.nv > 0;\n const slab3 = (ring, inner3, cx, cz, y0, y1) => ({\n sil: poly(hull(at2(ring, cx, y0, cz).concat(at2(ring, cx, y1, cz)))),\n crease: open(at2(run(inner3, seen), cx, y1, cz))\n });\n const g = mk("g", {}, svg);\n const [br, bi] = rings(EAR2 + 2, -DEEP, W8 - EAR2 - 2, -T7, 3, 1.6);\n put(solid(g), prism(P, front2, br, bi, 3, H5 - 3));\n put(solid(g), slab3(rrect(0, 0, W8, H5, 3, 4), rrect(0.7, 0.7, W8 - 0.7, H5 - 0.7, 2.3, 4), 0, 0, -T7, 0));\n const flat = (ring) => ring.map((q) => [q.u, q.v]);\n const face = (pts, cls) => mk("path", { d: poly(pts.map(([x, z]) => P(x, 0, z))), class: cls }, g);\n for (const ex2 of [EAR2 / 2 + 1, W8 - EAR2 / 2 - 1]) for (const ez2 of [8, H5 - 8]) {\n face(flat(rrect(ex2 - 3.4, ez2 - 1.5, ex2 + 3.4, ez2 + 1.5, 1.5, 3)), "nf");\n }\n const ports = [];\n for (let p = 0; p < 2 * NC; p++) {\n const r = p < NC ? 0 : 1, c = p % NC;\n ports.push({ p, r, c, cx: X02 + (c + 0.5) * PITCH2 + (c >= 6 ? GAP2 : 0), cz: r ? ZB4 : ZT2, full: !EMPTY.includes(p) });\n }\n for (const m of [0, 6]) {\n const a = ports[m].cx - PITCH2 / 2 + 1, b = ports[m + 5].cx + PITCH2 / 2 - 1;\n face(flat(rrect(a, ZB4 - JH - 3, b, ZT2 + JH + NH + 2.6, 2.4, 4)), "lo nf");\n }\n const jack = fillet([[-JW, -JH], [JW, -JH], [JW, JH], [NW, JH], [NW, JH + NH], [-NW, JH + NH], [-NW, JH], [-JW, JH]], [1, 1, 1, 0.4, 0.5, 0.5, 0.4, 1]);\n for (const pt of ports) {\n const { cx, cz } = pt, d = JD;\n pt.jack = face(jack.map(([u, v]) => [cx + u, cz + v]), "nf");\n mk("path", { d: open([[-JW + d, JH], [-JW + d, -JH + 0.816 * d], [JW, -JH + 0.816 * d]].map(([u, v]) => P(cx + u, 0, cz + v))), class: "lo nf" }, g);\n }\n const plugG = mk("g", {}, g), cabG = mk("g", {}, g);\n const plug2 = rrect(-PW, -PH2, PW, PH2, 1.2, 3), plugIn = rrect(-PW + 0.7, -PH2 + 0.7, PW - 0.7, PH2 - 0.7, 0.6, 3);\n const bootA = rrect(-3.7, -3, 3.7, 3, 1.6, 3), bootB = rrect(-2.4, -2.4, 2.4, 2.4, 2.3, 3);\n const order2 = ports.slice().sort((a, b) => a.c - b.c || a.r - b.r);\n for (const pt of order2) {\n pt.pull = spring(0, { eps: 2e-3 });\n pt.lx = spring(0, { eps: 0.01 });\n pt.lz = spring(0, { eps: 0.01 });\n if (!pt.full) continue;\n pt.plug = solid(plugG);\n pt.latch = mk("path", { class: "lo nf" }, pt.plug.g);\n pt.boot = solid(plugG);\n pt.cable = mk("path", { class: "sil" }, cabG);\n pt.drawn = "";\n }\n function drawPort(pt) {\n const k = pt.pull.x, lx = pt.lx.x, lz = pt.lz.x, key = [k, lx, lz].map((v) => v.toFixed(3)).join();\n if (key === pt.drawn) return;\n pt.drawn = key;\n const { cx, cz, r, p } = pt, y1 = PL + OUT2 * k, y2 = y1 + BL, up = r ? -1 : 1, [jx, jz] = JIT(p);\n put(pt.plug, slab3(plug2, plugIn, cx, cz, 0, y1));\n pt.latch.setAttribute("d", seg(P(cx, 0.6, cz + PH2), P(cx, y1 - 0.8, cz + PH2)));\n put(pt.boot, { sil: poly(hull(at2(bootA, cx, y1, cz).concat(at2(bootB, cx, y2, cz)))), crease: "" });\n const out = r ? 6 : 2, lean2 = r ? 2 : 3;\n const p0 = [cx, y2 - 1.5, cz], p1 = r ? [cx + 0.5, y2 + out + 4 * k, cz] : [cx, y2 + 1 + 2 * k, cz + 6];\n const p3 = [cx + lean2 + jx + lx, y2 + out + 1 + LIFT4 * k, cz + up * (24 + jz) + lz + LIFT4 * k], p2 = [p3[0] - 0.6, p3[1] - 1, p3[2] - up * 12];\n const q = [];\n for (let i = 0; i <= 16; i++) {\n const s = i / 16, a = (1 - s) ** 3, b = 3 * (1 - s) ** 2 * s, c = 3 * (1 - s) * s * s, d = s ** 3;\n q.push(P(...[0, 1, 2].map((j) => a * p0[j] + b * p1[j] + c * p2[j] + d * p3[j])));\n }\n pt.cable.setAttribute("d", tube3(q, CR2 * S9));\n }\n function aim2(a, radius, depth2) {\n const A2 = ports[a];\n for (const pt of ports) {\n const dx = pt.c - A2.c, dr = pt.r - A2.r, d = Math.hypot(dx, dr), f = falloff3(d, radius) * depth2;\n pt.pull.t = pt === A2 ? depth2 : 0;\n pt.lx.t = d ? clamp(dx / d * f * LEAN, -LEAN, LEAN) : 0;\n pt.lz.t = d ? clamp(-dr / d * f * LZ2, -LZ2, LZ2) : 0;\n }\n }\n function light(pt) {\n if (pt === lit2) return;\n const mark = (q, on) => (q.full ? [q.plug.sil, q.boot.sil, q.cable] : [q.jack]).forEach((el) => el.classList.toggle("hi", on));\n if (lit2) mark(lit2, false);\n lit2 = pt;\n mark(pt, true);\n }\n aim2(REST7, 1.6, 0.55);\n for (const pt of ports) for (const s of [pt.pull, pt.lx, pt.lz]) s.x = s.t;\n light(ports[REST7]);\n read.textContent = "rest";\n const B6 = register(stage, (dt) => {\n let m = false;\n for (const pt of ports) {\n for (const s of [pt.pull, pt.lx, pt.lz]) if (stepS(s, dt)) m = true;\n if (pt.full) drawPort(pt);\n }\n return m;\n });\n bag.add(B6.unregister);\n const o = P(0, HIT_Y, 0), ux = P(1, HIT_Y, 0), uz = P(0, HIT_Y, 1);\n const ex = [ux[0] - o[0], ux[1] - o[1]], ez = [uz[0] - o[0], uz[1] - o[1]], det = ex[0] * ez[1] - ex[1] * ez[0];\n function hit([sx, sy]) {\n const qx = sx - o[0], qy = sy - o[1], x = (qx * ez[1] - qy * ez[0]) / det, z = (ex[0] * qy - ex[1] * qx) / det;\n if (x < X02 - 2 || x > W8 - X02 + 2 || z < ZB4 - JH - 6 || z > ZT2 + JH + 8) return -1;\n let best = -1, bd = Infinity;\n for (const pt of ports) {\n const dd = Math.abs(pt.cx - x) + (Math.abs(pt.cz - z) > (ZT2 - ZB4) / 2 ? 1e3 : 0);\n if (dd < bd) {\n bd = dd;\n best = pt.p;\n }\n }\n return best;\n }\n function retarget() {\n if (over >= 0) {\n aim2(over, R9, 1);\n light(ports[over]);\n read.textContent = "port " + String(over + 1).padStart(2, "0");\n } else {\n aim2(REST7, 1.6, 0.55);\n light(ports[REST7]);\n read.textContent = "rest";\n }\n B6.wake();\n }\n bag.add(pointer(stage, {\n move: (p) => {\n const h = hit(p);\n if (h !== over) {\n over = h;\n retarget();\n }\n },\n leave: () => {\n over = -1;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v;\n if (over >= 0) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/phone.ts\nvar W9 = 68;\nvar L2 = 140;\nvar R4 = 11;\nvar GMAX = 40;\nvar TG = 2;\nvar TB = 1.6;\nvar TC = 1.8;\nvar TBAT = 7;\nvar TS = 6;\nvar TBUMP = 2.2;\nvar TL = 0.9;\nvar STACK = TG + TB + TC + TBAT + TS + TBUMP + TL;\nvar NAMES2 = ["glass", "board", "battery", "shell"];\nvar REST8 = [0.36, 0.3, 1.2];\nvar MIN2 = 0.08;\nvar PIVOT = 1;\nvar BOARD = [[4, L2 - 44], [W9 - 4, L2 - 44], [W9 - 4, L2 - 5], [36, L2 - 5], [36, L2 - 30], [4, L2 - 30]];\nvar BAT = [9, 12, W9 - 9, L2 - 48];\nvar CHIPS = [\n [10, L2 - 41, 26, L2 - 34, 1.4],\n [45, L2 - 40, 54, L2 - 35, 1.2],\n [57, L2 - 42, 62, L2 - 36, 1.2],\n [42, L2 - 27, 56, L2 - 13, 1.8]\n];\nvar RECT = [[0, 0], [W9, 0], [W9, L2], [0, L2]];\nvar FOOT2 = [RECT, BOARD, [[BAT[0], BAT[1]], [BAT[2], BAT[1]], [BAT[2], BAT[3]], [BAT[0], BAT[3]]], RECT];\nvar THICK = [TG, TB, TBAT, TS];\nfunction bases(g) {\n const b = [0], t = [TG, TB + TC, TBAT];\n for (let j = 0; j < 3; j++) b.push(b[j] + t[j] + g[j]);\n const mid = (b[3] + TS + TBUMP + TL) / 2;\n return b.map((z) => z - mid);\n}\nfunction inside2(pt, pg) {\n let c = false;\n for (let i = 0, j = pg.length - 1; i < pg.length; j = i++) {\n const [xi, yi] = pg[i], [xj, yj] = pg[j];\n if (yi > pt[1] !== yj > pt[1] && pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi) c = !c;\n }\n return c;\n}\nvar at = (ring, cx, cy) => ring.map((q) => ({ ...q, u: q.u + cx, v: q.v + cy }));\nvar mount14 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let GAP4 = value, share = null, act = -1, origin = PIVOT, lastP = null;\n const C = Cam(45, 0.5, 1.5), H10 = (STACK + 3 * GMAX) / 2;\n fit(C, [[0, 0, -H10], [W9 + 2.2, L2, -H10], [W9 + 2.2, 0, -H10], [0, L2, -H10], [0, 0, H10], [W9 + 2.2, 0, H10], [0, L2, H10]], 200, 166);\n const P = proj(C), front2 = facing(C);\n const flat = (pts, z) => poly(pts.map(([x, y]) => P(x, y, z)));\n const g = mk("g", {}, svg);\n const outer = rrect(0, 0, W9, L2, R4, 8);\n const ext = extremes(P, outer).slice(0, 2), guide = mk("path", { class: "nf dash" }, g);\n const gg = mk("g", {}, g), glass = solid(gg), gIn = rrect(1.2, 1.2, W9 - 1.2, L2 - 1.2, R4 - 1.2, 8);\n const DISP = fillet([[4, 4], [W9 - 4, 4], [W9 - 4, L2 - 4], [W9 / 2 + 12, L2 - 4], [W9 / 2 + 12, L2 - 10], [W9 / 2 - 12, L2 - 10], [W9 / 2 - 12, L2 - 4], [4, L2 - 4]], [7.5, 7.5, 7.5, 2, 3.5, 3.5, 2, 7.5]);\n const disp = mk("path", { class: "nf" }, gg), lensF = mk("path", { class: "nf" }, gg), slit = mk("path", { class: "nf lo" }, gg);\n const camF = at(circ(1.5, 16), W9 / 2 + 6, L2 - 7);\n const bg = mk("g", {}, g), BF = fillet(BOARD, [4, 4, 4, 4, 3, 4]);\n const bBack = mk("path", { class: "lo" }, bg), bFace = mk("path", { class: "sil" }, bg);\n const chips = CHIPS.map(([x0, y0, x1, y1, h]) => {\n const [ring, inner3] = rings(x0, y0, x1, y1, 1.5, 0.7);\n return { ring, inner: inner3, h, el: solid(bg) };\n });\n const tg = mk("g", {}, g), battery = solid(tg), [batR, batI] = rings(...BAT, 6, 1.6);\n const TAB = fillet([[41, L2 - 48], [52, L2 - 48], [52, L2 - 31], [41, L2 - 31]], [0.5, 0.5, 2, 2]);\n const tBack = mk("path", { class: "lo" }, tg), tFace = mk("path", {}, tg), conn = solid(tg);\n const [conR, conI] = rings(42, L2 - 38, 51, L2 - 32, 1.5, 0.7);\n const sg = mk("g", {}, g), shell = solid(sg), sIn = rrect(1.8, 1.8, W9 - 1.8, L2 - 1.8, R4 - 1.8, 8);\n const buttons = [[L2 - 68, L2 - 58], [L2 - 54, L2 - 44]].map(([y0, y1]) => {\n const [ring, inner3] = rings(W9 - 0.5, y0, W9 + 2.2, y1, 1.2, 0.5);\n return { ring, inner: inner3, el: solid(sg) };\n });\n const bump = solid(sg), bumpR = rrect(7, L2 - 33, 33, L2 - 7, 7, 6), bumpI = rrect(8, L2 - 32, 32, L2 - 8, 6, 6);\n const lenses = [[14.5, L2 - 25.5], [25.5, L2 - 14.5]].map(([x, y]) => {\n const el = solid(sg);\n return { ring: at(circ(4.6, 24), x, y), inner: at(circ(3.6, 24), x, y), eye: at(circ(1.9, 16), x, y), el, glass: mk("path", { class: "nf lo" }, el.g) };\n });\n const flash = mk("path", { class: "nf" }, sg), FL = at(circ(1.7, 16), 25.5, L2 - 25.5);\n const draw = [\n (z) => {\n put(glass, prism(P, front2, outer, gIn, z, z + TG));\n const t = z + TG;\n disp.setAttribute("d", flat(DISP, t));\n lensF.setAttribute("d", poly(ringAt(P, camF, t)));\n slit.setAttribute("d", seg(P(W9 / 2 - 6, L2 - 7, t), P(W9 / 2 + 1.5, L2 - 7, t)));\n },\n (z) => {\n bBack.setAttribute("d", flat(BF, z));\n bFace.setAttribute("d", flat(BF, z + TB));\n for (const c of chips) put(c.el, prism(P, front2, c.ring, c.inner, z + TB, z + TB + c.h));\n },\n (z) => {\n put(battery, prism(P, front2, batR, batI, z, z + TBAT));\n tBack.setAttribute("d", flat(TAB, z + 0.8));\n tFace.setAttribute("d", flat(TAB, z + 1.4));\n put(conn, prism(P, front2, conR, conI, z + 1.4, z + 2.6));\n },\n (z) => {\n put(shell, prism(P, front2, outer, sIn, z, z + TS));\n for (const b of buttons) put(b.el, prism(P, front2, b.ring, b.inner, z + 1.6, z + 4.4));\n const t = z + TS, u = t + TBUMP;\n put(bump, prism(P, front2, bumpR, bumpI, t, u));\n for (const l of lenses) {\n put(l.el, prism(P, front2, l.ring, l.inner, u, u + TL));\n l.glass.setAttribute("d", poly(ringAt(P, l.eye, u + TL)));\n }\n flash.setAttribute("d", poly(ringAt(P, FL, u)));\n }\n ];\n const sils = [glass.sil, bFace, battery.sil, shell.sil];\n const targets = () => [0, 1, 2].map((j) => GAP4 * (share === null ? REST8[j] : share));\n const gs = targets().map((t) => spring(t, { eps: 0.01 }));\n const drawn = [NaN, NaN, NaN, NaN];\n const B6 = register(stage, (dt) => {\n const T12 = targets(), order2 = [0, 1, 2].sort((a, b2) => Math.abs(a + 0.5 - origin) - Math.abs(b2 + 0.5 - origin));\n let m = false;\n for (const j of order2) {\n const d = j + 0.5 - origin, k = d > 0 ? j - 1 : j + 1;\n gs[j].t = Math.abs(d) < 1 ? T12[j] : T12[j] + gs[k].x - T12[k];\n if (stepS(gs[j], dt)) m = true;\n }\n const b = bases(gs.map((s) => s.x));\n let changed = false;\n b.forEach((z, i) => {\n if (z !== drawn[i]) {\n drawn[i] = z;\n draw[i](z);\n changed = true;\n }\n });\n if (changed) guide.setAttribute("d", ext.map((q) => seg(P(q.u, q.v, b[3]), P(q.u, q.v, b[0] + TG))).join(""));\n return m;\n });\n bag.add(B6.unregister);\n function pick(p) {\n const b = bases(targets());\n for (let i = 3; i >= 0; i--) {\n const f = FOOT2[i], top = f.map(([x, y]) => P(x, y, b[i] + THICK[i]));\n const shape = i === 1 ? top : hull(top.concat(f.map(([x, y]) => P(x, y, b[i]))));\n if (inside2(p, shape)) return i;\n }\n let best = 0, bd = Infinity;\n b.forEach((z, i) => {\n const dy = Math.abs(P(W9 / 2, L2 / 2, z + THICK[i])[1] - p[1]);\n if (dy < bd) {\n bd = dy;\n best = i;\n }\n });\n return best;\n }\n function setAct(a) {\n if (a >= 0) origin = a;\n act = a;\n const lit2 = a >= 0 ? a : PIVOT;\n sils.forEach((s, i) => s.classList.toggle("hi", i === lit2));\n read.textContent = a >= 0 ? NAMES2[a] : "rest";\n B6.wake();\n }\n setAct(-1);\n bag.add(pointer(stage, {\n move: (p) => {\n lastP = p;\n share = lerp(MIN2, 1, clamp((p[0] - 60) / 280, 0, 1));\n setAct(pick(p));\n },\n leave: () => {\n lastP = null;\n share = null;\n setAct(-1);\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n GAP4 = v;\n if (lastP) setAct(pick(lastP));\n B6.wake();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/phosphor.ts\nvar N4 = 7;\nvar PITCH3 = 10;\nvar M2 = 8;\nvar EXT = M2 * 2 + PITCH3 * (N4 - 1);\nvar T8 = 5;\nvar R5 = 2.5;\nvar DROP = 15;\nvar BT = 4;\nvar BO = 7;\nvar LOOP = [\n [0, 0, 0, 8, 0, 0, 0],\n [0, 0, 8, 20, 8, 0, 0],\n [0, 28, 34, 34, 34, 28, 0],\n [28, 34, 65, 65, 65, 34, 28],\n [65, 0, 0, 0, 0, 0, 65],\n [0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0],\n [64, 0, 0, 0, 0, 0, 0],\n [32, 64, 0, 0, 0, 0, 0],\n [16, 32, 64, 0, 0, 0, 0],\n [8, 16, 32, 64, 0, 0, 0],\n [4, 8, 16, 32, 64, 0, 0],\n [2, 4, 8, 16, 32, 64, 0],\n [1, 2, 4, 8, 16, 32, 64],\n [0, 1, 2, 4, 8, 16, 32],\n [0, 0, 1, 2, 4, 8, 16],\n [0, 0, 0, 1, 2, 4, 8],\n [0, 0, 0, 0, 1, 2, 4],\n [0, 0, 0, 0, 0, 1, 2],\n [0, 0, 0, 0, 0, 0, 1],\n [0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0]\n];\nvar FRAME_MS = 110;\nvar RESUME = 1200;\nvar RAMP = 400;\nvar SPLASH = [[0, 0, 1], [1, 0, 0.45], [-1, 0, 0.45], [0, 1, 0.45], [0, -1, 0.45]];\nvar lit = (f, r, c) => LOOP[f][r] >> N4 - 1 - c & 1;\nvar mount15 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let tau = value, clock = 0, lastFrame = -1, leftAt = -1e9, painting = false;\n let prev = null;\n const C = Cam(45, 0.5, 2.12), ZB5 = -DROP - BT;\n fit(C, [[-BO, -BO, ZB5], [EXT + BO, EXT + BO, ZB5 - 6], [EXT + BO, -BO, ZB5], [-BO, EXT + BO, ZB5], [0, 0, T8]], 200, 160);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const [br, bi] = rings(-BO, -BO, EXT + BO, EXT + BO, 11, 1.8);\n reflect(svg, g, P, front2, br, ZB5, 12);\n put(solid(g), prism(P, front2, br, bi, ZB5, -DROP));\n const [tr, ti] = rings(0, 0, EXT, EXT, 7, 1.3);\n mk("path", { d: extremes(P, tr).map((q) => seg(P(q.u, q.v, 0), P(q.u, q.v, -DROP))).join(""), class: "nf dash" }, g);\n put(solid(g), prism(P, front2, tr, ti, 0, T8));\n mk("path", { d: poly(ringAt(P, rrect(3.5, 3.5, EXT - 3.5, EXT - 3.5, 4.5, 5), T8)), class: "nf lo" }, g);\n const I = new Float32Array(N4 * N4);\n const dots = [];\n for (let r = 0; r < N4; r++) for (let c = 0; c < N4; c++) {\n const d = flatDot(g, C, R5, "dot");\n place(d, P(M2 + c * PITCH3, M2 + r * PITCH3, T8));\n dots.push(d);\n }\n function excite(x, y, amt) {\n const c = Math.round((x - M2) / PITCH3), r = Math.round((y - M2) / PITCH3);\n for (const [dr, dc, k] of SPLASH) {\n const rr = r + dr, cc = c + dc;\n if (rr < 0 || rr >= N4 || cc < 0 || cc >= N4) continue;\n I[rr * N4 + cc] = Math.max(I[rr * N4 + cc], amt * k);\n }\n }\n const B6 = register(stage, (dt, now) => {\n if (reducedMotion() && !painting) {\n for (let i = 0; i < N4 * N4; i++) I[i] = Math.max(I[i] * Math.exp(-dt * 1e3 / tau), lit(3, Math.floor(i / N4), i % N4) * 0.8);\n } else {\n const decay = Math.exp(-dt * 1e3 / tau);\n for (let i = 0; i < N4 * N4; i++) I[i] *= decay;\n const gain = painting ? 0 : clamp((now - leftAt - RESUME) / RAMP, 0, 1);\n if (gain > 0) {\n clock += dt * 1e3;\n const f = Math.floor(clock / FRAME_MS) % LOOP.length;\n if (f !== lastFrame) {\n lastFrame = f;\n for (let r = 0; r < N4; r++) for (let c = 0; c < N4; c++) if (lit(f, r, c)) I[r * N4 + c] = Math.max(I[r * N4 + c], gain);\n }\n read.textContent = `loop \\xB7 ${String(f + 1).padStart(2, "0")}/${LOOP.length}`;\n } else read.textContent = painting ? "paint" : "afterglow";\n }\n for (let i = 0; i < N4 * N4; i++) dots[i].setAttribute("opacity", (0.14 + 0.86 * clamp(I[i], 0, 1)).toFixed(3));\n return true;\n });\n bag.add(B6.unregister);\n bag.add(pointer(stage, {\n move: (p) => {\n painting = true;\n const q = unproj(C, p[0], p[1], T8);\n if (prev) {\n const n = Math.ceil(Math.hypot(q[0] - prev[0], q[1] - prev[1]) / 3);\n for (let k = 1; k <= n; k++) excite(lerp(prev[0], q[0], k / n), lerp(prev[1], q[1], k / n), 1);\n } else excite(q[0], q[1], 1);\n prev = q;\n B6.wake();\n },\n // the resume is a timestamp the tick reads, not a timer, so re-entering or tearing down has nothing to cancel\n leave: () => {\n painting = false;\n prev = null;\n leftAt = performance.now();\n clock = 0;\n lastFrame = -1;\n B6.wake();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n tau = v;\n }, destroy: bag.dispose };\n};\n\n// src/figures/plot.ts\nvar N5 = 7;\nvar X03 = 13;\nvar DX = 10;\nvar TW = 5;\nvar TY0 = 6;\nvar TY1 = 11;\nvar TH = 1;\nvar L3 = 2 * X03 + (N5 - 1) * DX;\nvar D7 = 16;\nvar BZ3 = 3.6;\nvar PY = 2;\nvar PT = 2;\nvar HP = 34;\nvar GRID = 4;\nvar AX = 7;\nvar TICK = 4;\nvar PROUD = 2.2;\nvar MARK = 4;\nvar S4 = 3.2;\nvar SPRING = { k: 40, c: 11, eps: 0.02 };\nvar KICK = 16.4;\nvar falloff4 = (d) => 1 / (1 + 0.5 * d * d);\nvar ARC = 5;\nvar FACE = /* @__PURE__ */ fillet([[L3 - 2, 0], [L3 - 2, HP], [2, HP], [2, 0]], [0.6, 4, 4, 0.6], ARC);\nvar EDGE = [ARC, 3 * (ARC + 1)];\nvar mount16 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const TOP3 = TABLE.plot[2];\n const C = Cam(45, 0.5, S4);\n fit(C, [[0, 0, -BZ3], [L3, 0, -BZ3], [0, D7, -BZ3], [L3, D7, -BZ3], [0, PY, HP], [L3, PY, HP]], 200, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const [bo, bi] = rings(0, 0, L3, D7, 6, 1.6);\n put(solid(g), prism(P, front2, bo, bi, -BZ3, 0));\n const face = (y) => FACE.map(([x, z]) => P(x, y, z));\n put(solid(g), { sil: poly(hull(face(PY).concat(face(PY + PT)))), crease: open(face(PY + PT).slice(...EDGE)) });\n const at2 = (x, z) => P(x, PY + PT, z), zs = Array.from({ length: GRID }, (_, k) => HP * (k + 1) / (GRID + 1));\n mk("path", { class: "nf lo", d: zs.map((z) => seg(at2(AX, z), at2(L3 - 7, z))).join("") }, g);\n mk("path", { class: "nf", d: seg(at2(AX, 0), at2(AX, HP - 4)) + zs.map((z) => seg(at2(AX - TICK, z), at2(AX, z))).join("") }, g);\n const tabs = Array.from({ length: N5 }, (_, i) => {\n const x = X03 + i * DX;\n return { ring: rings(x - TW / 2, TY0, x + TW / 2, TY1, 1.2, 0.4), el: solid(g) };\n });\n const sp = tabs.map((_, i) => spring(i === MARK ? PROUD : 0, SPRING));\n const drawn = tabs.map(() => NaN);\n function draw() {\n tabs.forEach((t, i) => {\n const h = clamp(sp[i].x, 0, TOP3);\n if (h === drawn[i]) return;\n drawn[i] = h;\n put(t.el, prism(P, front2, t.ring[0], t.ring[1], 0, TH + h));\n });\n }\n const loop = register(stage, (dt) => {\n let m = false;\n for (const s of sp) m = stepS(s, dt) || m;\n draw();\n return m;\n });\n bag.add(loop.unregister);\n const cx = tabs.map((_, i) => P(X03 + i * DX, (TY0 + TY1) / 2, 0)[0]);\n let lift = value, picked = -2;\n function kick(a) {\n sp.forEach((s, i) => {\n const need = lift * falloff4(Math.abs(i - a)) - s.x;\n if (need > 0) s.v = Math.max(s.v, KICK * need);\n });\n loop.wake();\n }\n function choose(a) {\n if (a === picked) return;\n picked = a;\n sp.forEach((s, i) => {\n s.t = a < 0 && i === MARK ? PROUD : 0;\n });\n if (a >= 0) kick(a);\n tabs.forEach((t, i) => t.el.sil.classList.toggle("hi", i === (a < 0 ? MARK : a)));\n read.textContent = a < 0 ? "rest" : `bar ${a + 1} \\xB7 0`;\n loop.wake();\n }\n choose(-1);\n draw();\n bag.add(pointer(stage, {\n move: (p) => choose(cx.reduce((best, x, i) => Math.abs(p[0] - x) < Math.abs(p[0] - cx[best]) ? i : best, 0)),\n leave: () => choose(-1)\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n lift = v;\n if (picked >= 0) kick(picked);\n }, destroy: bag.dispose };\n};\n\n// src/figures/plug.ts\nvar PT2 = 3;\nvar PH3 = 21;\nvar ZC = 30;\nvar PR2 = 7;\nvar B4 = 1.4;\nvar HOLE = 2.6;\nvar HS = 8;\nvar RECESS = 13;\nvar BW2 = 11;\nvar BH3 = 6.5;\nvar BR = 4.5;\nvar BL2 = 18;\nvar PIN = 2;\nvar PL2 = 11;\nvar SL = 7;\nvar S0 = 4.6;\nvar S1 = 3;\nvar CR3 = 1.8;\nvar REST9 = [48, 8];\nvar YAW = 100;\nvar TURN = 0.7;\nvar S5 = 2.8;\nvar EDGE2 = 398;\nvar ss2 = (t) => t * t * (3 - 2 * t);\nfunction untangle2(L7) {\n for (let i = 0; i < L7.length - 3; i++) for (let j = L7.length - 2; j > i + 1; j--) {\n const p = L7[i], q = L7[i + 1], r = L7[j], s = L7[j + 1];\n const e0 = q[0] - p[0], e1 = q[1] - p[1], f0 = s[0] - r[0], f1 = s[1] - r[1], g0 = r[0] - p[0], g1 = r[1] - p[1], d = e0 * f1 - e1 * f0;\n if (!d) continue;\n const t = (g0 * f1 - g1 * f0) / d, u = (g0 * e1 - g1 * e0) / d;\n if (t > 0 && t < 1 && u > 0 && u < 1) {\n L7.splice(i + 1, j - i, [p[0] + t * e0, p[1] + t * e1]);\n break;\n }\n }\n return L7;\n}\nfunction upTo(L7, X) {\n const i = L7.findIndex((p2) => p2[0] > X);\n if (i < 1) return i < 0 ? L7 : [];\n const p = L7[i - 1], q = L7[i], f = (X - p[0]) / (q[0] - p[0]);\n return [...L7.slice(0, i), [X, p[1] + (q[1] - p[1]) * f]];\n}\nfunction tube4(Q, rho, X) {\n const n = Q.length, A2 = [], Bk = [];\n for (let i = 0; i < n; i++) {\n const a = Q[Math.max(i - 1, 0)], c = Q[i], b = Q[Math.min(i + 1, n - 1)];\n let tx = b[0] - a[0], ty = b[1] - a[1];\n const l = Math.hypot(tx, ty) || 1;\n tx /= l;\n ty /= l;\n A2.push([c[0] - ty * rho, c[1] + tx * rho]);\n Bk.push([c[0] + ty * rho, c[1] - tx * rho]);\n }\n const cap = (c) => {\n const o = [];\n for (let k = 7; k >= 1; k--) {\n const t = Math.PI * k / 8;\n o.push([c[0] + rho * Math.cos(t), c[1] + rho * 0.5 * Math.sin(t)]);\n }\n return o;\n };\n return open([...upTo(untangle2(Bk), X).reverse(), ...cap(Q[0]), ...upTo(untangle2(A2), X)]);\n}\nfunction pose(s) {\n const th = rad(lerp(YAW, 180, ss2(clamp(s / TURN, 0, 1))));\n return {\n c: [lerp(REST9[0], PL2, s), lerp(REST9[1], 0, s), lerp(BH3, ZC, Math.sin(s * Math.PI / 2))],\n w: [Math.cos(th), Math.sin(th), 0],\n u: [-Math.sin(th), Math.cos(th), 0]\n };\n}\nvar mount17 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, S5), zf = Math.sqrt(1 - C.k * C.k);\n const V3 = [Math.sin(C.az) * zf, Math.cos(C.az) * zf, C.k];\n const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n const at2 = (o, a, b, d) => [o.c[0] + a * o.u[0] + d * o.w[0], o.c[1] + a * o.u[1] + d * o.w[1], o.c[2] + b];\n const ext = [];\n for (const x of [0, -PT2]) for (const y of [-PH3, PH3]) for (const z of [ZC - PH3, ZC + PH3]) ext.push([x, y, z]);\n for (const s of [0, 1]) {\n const o = pose(s);\n for (const a of [-BW2, BW2]) for (const b of [-BH3, BH3]) for (const d of [PL2, -BL2 - SL]) ext.push(at2(o, a, b, d));\n }\n fit(C, ext, 200, 166);\n const P = proj(C), Pv = (p) => P(p[0], p[1], p[2]);\n const g = mk("g", {}, svg);\n const face = rrect(-PH3, ZC - PH3, PH3, ZC + PH3, PR2, 6), lip = rrect(-PH3 + B4, ZC - PH3 + B4, PH3 - B4, ZC + PH3 - B4, PR2 - B4, 6);\n put(solid(g), {\n sil: poly(hull(face.map((q) => P(0, q.u, q.v)).concat(face.map((q) => P(-PT2, q.u, q.v))))),\n crease: open(run(lip, (q) => q.nu * V3[1] + q.nv * V3[2] > 0).map((q) => P(0, q.u, q.v)))\n });\n mk("path", { class: "nf lo", d: poly(circ(RECESS, 48).map((q) => P(0, q.u, ZC + q.v))) }, g);\n mk("path", { class: "nf", d: [-HS, HS].map((y) => poly(circ(HOLE, 16).map((q) => P(0, y + q.u, ZC + q.v)))).join("") }, g);\n const pg = mk("g", {}, svg);\n const cord = mk("path", { class: "sil" }, pg), sleeve = mk("path", { class: "sil" }, pg), body = solid(pg), pins = mk("path", { class: "sil hi" }, pg);\n const s0 = circ(S0, 16), s1 = circ(S1, 16);\n const rounded = (f, rr, d, dir2) => [0, 30, 60, 90].map((a) => [f(rr * (1 - Math.sin(rad(a)))), d + dir2 * rr * (1 - Math.cos(rad(a)))]);\n const box = (k) => rrect(-BW2 + k, -BH3 + k, BW2 - k, BH3 - k, BR - k), rod = (k) => circ(PIN - k, 12);\n const inner3 = box(2), shell = [...rounded(box, 2, 0, -1), ...rounded(box, 2, -BL2, 1)], prong = [[rod(0), 0], ...rounded(rod, PIN * 0.8, PL2, -1)];\n const sweep = (o, parts, a0 = 0) => poly(hull(parts.flatMap(([r, d]) => r.map((q) => Pv(at2(o, a0 + q.u, q.v, d))))));\n const end = [...unproj(C, 440, 150, CR3), CR3], bend = [REST9[0] + 18, REST9[1] - 52, CR3];\n let front2 = true, drawn = NaN;\n const sp = spring(0, { eps: 2e-3 });\n function draw() {\n if (sp.x === drawn) return;\n drawn = sp.x;\n const o = pose(sp.x), pinsFace = dot(o.w, V3) > 0, vu = dot(o.u, V3);\n if (pinsFace !== front2) {\n front2 = pinsFace;\n pg.append(...front2 ? [cord, sleeve, body.g, pins] : [pins, body.g, cord, sleeve]);\n }\n const dEnd = front2 ? 0 : -BL2;\n put(body, {\n sil: sweep(o, shell),\n crease: open(run(inner3, (q) => q.nu * vu + q.nv * V3[2] > 0).map((q) => Pv(at2(o, q.u, q.v, dEnd))))\n });\n pins.setAttribute("d", [-HS, HS].map((a) => sweep(o, prong, a)).join(""));\n sleeve.setAttribute("d", sweep(o, [[s0, -BL2], [s1, -BL2 - SL]]));\n const t0 = at2(o, 0, 0, -BL2 - SL + 2), t1 = at2(o, 0, 0, -BL2 - SL - 26), Q = [];\n for (let i = 0; i <= 40; i++) {\n const t = i / 40, a = (1 - t) ** 3, b = 3 * (1 - t) ** 2 * t, c = 3 * (1 - t) * t * t, d = t ** 3;\n Q.push(P(a * t0[0] + b * t1[0] + c * bend[0] + d * end[0], a * t0[1] + b * t1[1] + c * bend[1] + d * end[1], a * t0[2] + b * t1[2] + c * bend[2] + d * end[2]));\n }\n cord.setAttribute("d", tube4(Q, CR3 * S5, EDGE2));\n }\n const loop = register(stage, (dt) => {\n const m = stepS(sp, dt);\n draw();\n return m;\n });\n bag.add(loop.unregister);\n const A2 = Pv(pose(0).c), Bs = P(0, 0, ZC), ab = [Bs[0] - A2[0], Bs[1] - A2[1]], ab2 = ab[0] * ab[0] + ab[1] * ab[1];\n let pull = value, along2 = -1;\n function retarget() {\n sp.t = along2 < 0 ? 0 : Math.min(along2, pull);\n const o = pose(sp.t), tip = at2(o, 0, 0, PL2);\n read.textContent = along2 < 0 ? "rest" : `gap ${Math.round(Math.hypot(tip[0], tip[1], tip[2] - ZC))} \\xB7 0 V`;\n loop.wake();\n }\n retarget();\n draw();\n bag.add(pointer(stage, {\n move: (p) => {\n along2 = clamp(((p[0] - A2[0]) * ab[0] + (p[1] - A2[1]) * ab[1]) / ab2, 0, 1);\n retarget();\n },\n leave: () => {\n along2 = -1;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n pull = v;\n retarget();\n }, destroy: bag.dispose };\n};\n\n// src/figures/query.ts\nvar PR3 = 27;\nvar PH4 = 6;\nvar B5 = 1.6;\nvar T9 = 4.3;\nvar R6 = 16;\nvar ZC2 = 66;\nvar A0 = 200;\nvar A1 = -35;\nvar ZK = 38;\nvar ZS = 29;\nvar BR2 = 6;\nvar SWAY = 0.2;\nvar TRAVEL2 = 4;\nvar FACE2 = -45;\nvar REST10 = 36;\nvar REACH2 = 150;\nvar S6 = 2.6;\nvar LIGHT2 = [-Math.SQRT1_2, -Math.SQRT1_2];\nvar GLEAM = 0.4;\nvar LINE = /* @__PURE__ */ (() => {\n const pts = [];\n for (let i = 0; i <= 30; i++) {\n const a2 = rad(A0 + (A1 - A0) * i / 30);\n pts.push([R6 * Math.cos(a2), ZC2 + R6 * Math.sin(a2)]);\n }\n const e = pts[pts.length - 1], a = rad(A1), t = [Math.sin(a), -Math.cos(a)];\n const c1 = [e[0] + t[0] * 10, e[1] + t[1] * 10], c2 = [0, ZK + 10];\n for (let i = 1; i <= 20; i++) {\n const u = i / 20, w = 1 - u;\n pts.push([w * w * w * e[0] + 3 * w * w * u * c1[0] + 3 * w * u * u * c2[0], w * w * w * e[1] + 3 * w * w * u * c1[1] + 3 * w * u * u * c2[1] + u * u * u * ZK]);\n }\n pts.push([0, ZS]);\n return pts;\n})();\nvar ARC2 = [2, 28];\nfunction lean(Q, i, d) {\n const a = Q[Math.max(i - 1, 0)], b = Q[Math.min(i + 1, Q.length - 1)], l = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;\n const nx = -(b[1] - a[1]) / l, ny = (b[0] - a[0]) / l, k = d * (nx * LIGHT2[0] + ny * LIGHT2[1]);\n return [Q[i][0] + nx * k, Q[i][1] + ny * k];\n}\nfunction cross2(p, q, r, s) {\n const e0 = q[0] - p[0], e1 = q[1] - p[1], f0 = s[0] - r[0], f1 = s[1] - r[1], g0 = r[0] - p[0], g1 = r[1] - p[1];\n const d = e0 * f1 - e1 * f0;\n if (!d) return null;\n const t = (g0 * f1 - g1 * f0) / d, u = (g0 * e1 - g1 * e0) / d;\n return t > 0 && t < 1 && u > 0 && u < 1 ? [p[0] + t * e0, p[1] + t * e1] : null;\n}\nfunction untangle3(L7) {\n for (let i = 0; i < L7.length - 3; i++) for (let j = L7.length - 2; j > i + 1; j--) {\n const x = cross2(L7[i], L7[i + 1], L7[j], L7[j + 1]);\n if (x) {\n L7.splice(i + 1, j - i, x);\n break;\n }\n }\n return L7;\n}\nfunction tube5(Q, rho) {\n const n = Q.length, A2 = [], Bk = [], tan = [];\n for (let i = 0; i < n; i++) {\n const a = Q[Math.max(i - 1, 0)], c = Q[i], b = Q[Math.min(i + 1, n - 1)];\n const l0 = Math.hypot(c[0] - a[0], c[1] - a[1]) || 1, l1 = Math.hypot(b[0] - c[0], b[1] - c[1]) || 1;\n let tx = (c[0] - a[0]) / l0 + (b[0] - c[0]) / l1, ty = (c[1] - a[1]) / l0 + (b[1] - c[1]) / l1;\n const l = Math.hypot(tx, ty) || 1;\n tx /= l;\n ty /= l;\n tan.push([tx, ty]);\n A2.push([c[0] - ty * rho, c[1] + tx * rho]);\n Bk.push([c[0] + ty * rho, c[1] - tx * rho]);\n }\n const dome = (c, t) => Array.from({ length: 9 }, (_, k) => {\n const a = Math.PI * (k + 1) / 10, cs = -Math.cos(a), sn = -Math.sin(a);\n return [c[0] + rho * (-t[1] * cs + t[0] * sn), c[1] + rho * (t[0] * cs + t[1] * sn)];\n });\n const cut2 = (c) => Array.from({ length: 7 }, (_, k) => {\n const a = Math.PI * (7 - k) / 8;\n return [c[0] + rho * Math.cos(a), c[1] + rho * 0.5 * Math.sin(a)];\n });\n return poly([...untangle3(A2), ...cut2(Q[n - 1]), ...untangle3(Bk).reverse(), ...dome(Q[0], tan[0])]);\n}\nvar mount18 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const TMAX2 = TABLE.query[2];\n let max = value, over = false;\n const at2 = (th) => {\n const a = rad(FACE2 - th), c = Math.cos(a), n = Math.sin(a);\n return (s, z) => [s * c, s * n, z];\n };\n const ball = (lag, th) => at2(th)(clamp(lag * SWAY, -TRAVEL2, TRAVEL2), PH4 + BR2);\n const C = Cam(45, 0.5, S6);\n const ext = [[-PR3, 0, 0], [PR3, 0, 0], [0, -PR3, 0], [0, PR3, 0]];\n for (const th of [-TMAX2, -TMAX2 / 2, 0, TMAX2 / 2, TMAX2]) for (const p of LINE) {\n const q = at2(th)(p[0], p[1]);\n ext.push([q[0], q[1], q[2] + T9], [q[0], q[1], q[2] - T9]);\n }\n fit(C, ext, 200, 166);\n C.ox += 200 - proj(C)(0, 0, 0)[0];\n const P = proj(C), front2 = facing(C), rho = T9 * S6;\n const g = mk("g", {}, svg);\n put(solid(g), prism(P, front2, circ(PR3, 72), circ(PR3 - B5, 72), 0, PH4));\n const dot = solid(g);\n const disc2 = circ(BR2 * S6, 40), shine = circ(BR2 * S6 * 0.62, 40).slice(23, 31);\n const bar2 = mk("path", { class: "sil" }, g);\n const gleam = mk("path", { class: "nf lo" }, g);\n const rest = () => -Math.min(REST10, 0.9 * max);\n const sp = spring(rest()), roll = spring(rest(), { k: 36, c: 7 });\n let dH = NaN, dB = NaN;\n function draw() {\n if (sp.x !== dH) {\n dH = sp.x;\n const w = at2(sp.x);\n const Q = LINE.map((p) => P(...w(p[0], p[1])));\n bar2.setAttribute("d", tube5(Q, rho));\n gleam.setAttribute("d", open(Q.slice(ARC2[0], ARC2[1]).map((c, i) => lean(Q, ARC2[0] + i, GLEAM * rho))));\n }\n const lag = sp.x - roll.x;\n if (lag !== dB) {\n dB = lag;\n const c = P(...ball(lag, roll.x));\n put(dot, { sil: poly(disc2.map((q) => [c[0] + q.u, c[1] + q.v])), crease: open(shine.map((q) => [c[0] + q.u, c[1] + q.v])) });\n }\n }\n const loop = register(stage, (dt) => {\n roll.t = sp.x;\n const a = stepS(sp, dt), b = stepS(roll, dt);\n draw();\n return a || b;\n });\n bag.add(loop.unregister);\n const cx = P(0, 0, 0)[0];\n let px = cx;\n function retarget() {\n sp.t = over ? max * clamp((px - cx) / REACH2, -1, 1) : rest();\n read.textContent = over ? `turn ${Math.abs(Math.round(sp.t))}\\xB0 \\xB7 0` : "rest";\n dot.sil.classList.toggle("hi", !over);\n bar2.classList.toggle("hi", over);\n loop.wake();\n }\n retarget();\n bag.add(pointer(stage, {\n move: (p) => {\n over = true;\n px = p[0];\n retarget();\n },\n leave: () => {\n over = false;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n max = v;\n retarget();\n }, destroy: bag.dispose };\n};\n\n// src/figures/rail.ts\nvar L4 = 132;\nvar TOP2 = 92;\nvar POST = 4.4;\nvar FOOT3 = 34;\nvar BAR = 2;\nvar N6 = 7;\nvar X04 = 21;\nvar DX2 = 15;\nvar TH2 = 1.8;\nvar ROCK = 22;\nvar REST11 = [4, -7, 2, 13, -3, 6, -9];\nvar MARK2 = 3;\nvar S7 = 1.9;\nvar BODY = /* @__PURE__ */ fillet([[0, 9], [25, 21], [25, 25], [-25, 25], [-25, 21]], [4, 2, 2, 2, 2], 5);\nvar HOLE2 = /* @__PURE__ */ fillet([[0, 14], [15, 21.5], [-15, 21.5]], [2, 1.2, 1.2], 4);\nvar HOOK = /* @__PURE__ */ (() => [[0, 9], [0, 5], ...Array.from({ length: 10 }, (_, k) => {\n const t = rad(120 - k * 20);\n return [Math.sin(t) * 3.4, -Math.cos(t) * 3.4];\n})])();\nvar mount19 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, S7);\n const ext = [[0, -FOOT3 / 2, 0], [L4, -FOOT3 / 2, 0], [0, FOOT3 / 2, 0], [L4, FOOT3 / 2, 0], [0, 0, TOP2 + 6], [L4, 0, TOP2 + 6], [0, -30, TOP2 - 30], [L4, 30, TOP2 - 30]];\n fit(C, ext, 200, 166);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const block = (x0, y0, x1, y1, r, z0, z1) => {\n const [o, i] = rings(x0, y0, x1, y1, r, 0.7);\n put(solid(g), prism(P, front2, o, i, z0, z1));\n };\n block(-POST / 2 - 1, -FOOT3 / 2, POST / 2 + 1, FOOT3 / 2, 2.2, 0, 3);\n block(-POST / 2, -POST / 2, POST / 2, POST / 2, 1.4, 3, TOP2 + 4);\n const bodies = Array.from({ length: N6 }, () => solid(g));\n block(0, -BAR, L4, BAR, BAR, TOP2 - BAR, TOP2 + BAR);\n const hooks = bodies.map(() => mk("path", { class: "nf" }, g));\n block(L4 - POST / 2 - 1, -FOOT3 / 2, L4 + POST / 2 + 1, FOOT3 / 2, 2.2, 0, 3);\n block(L4 - POST / 2, -POST / 2, L4 + POST / 2, POST / 2, 1.4, 3, TOP2 + 4);\n const sp = REST11.map((a) => spring(a, { k: 60, c: 6, eps: 0.02 }));\n const drawn = REST11.map(() => NaN);\n function draw() {\n sp.forEach((s, i) => {\n if (s.x === drawn[i]) return;\n drawn[i] = s.x;\n const c = Math.cos(rad(s.x)), n = Math.sin(rad(s.x)), x = X04 + i * DX2;\n const at2 = (p, dx) => P(x + dx, p[0] * c + p[1] * n, TOP2 - (p[1] * c - p[0] * n));\n put(bodies[i], { sil: poly(hull(BODY.map((p) => at2(p, 0)).concat(BODY.map((p) => at2(p, TH2))))), crease: poly(HOLE2.map((p) => at2(p, TH2))) });\n hooks[i].setAttribute("d", open(HOOK.map((p) => at2(p, TH2 / 2))));\n });\n }\n const loop = register(stage, (dt) => {\n let m = false;\n for (const s of sp) m = stepS(s, dt) || m;\n draw();\n return m;\n });\n bag.add(loop.unregister);\n const cx = REST11.map((_, i) => P(X04 + i * DX2, 0, TOP2 - 17)[0]);\n let reach = value, picked = -2, side = 1;\n function choose(a) {\n picked = a;\n sp.forEach((s, i) => {\n const f = a < 0 ? 0 : clamp(1 - Math.abs(i - a) / (reach + 0.5), 0, 1);\n s.t = REST11[i] - ROCK * f * f * (i === a ? side : Math.sign(i - a));\n });\n bodies.forEach((b, i) => b.sil.classList.toggle("hi", i === (a < 0 ? MARK2 : a)));\n read.textContent = a < 0 ? "rest" : `hanger ${a + 1} \\xB7 0`;\n loop.wake();\n }\n choose(-1);\n draw();\n bag.add(pointer(stage, {\n move: (p) => {\n const a = cx.reduce((best, x, i) => Math.abs(p[0] - x) < Math.abs(p[0] - cx[best]) ? i : best, 0);\n side = p[0] < cx[a] ? 1 : -1;\n choose(a);\n },\n leave: () => choose(-1)\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n reach = v;\n choose(picked < 0 ? -1 : picked);\n }, destroy: bag.dispose };\n};\n\n// src/figures/riffle-geometry.ts\nvar N7 = 8;\nvar W10 = 84;\nvar H6 = 54;\nvar G2 = 13;\nvar TW2 = 22;\nvar TH3 = 7;\nvar TABS = [6, 31, 56];\nvar TK2 = 1.4;\nvar REST12 = -12;\nvar BACK = -24;\nvar FWD = 20;\nvar LIFT5 = 16;\nvar X05 = -5;\nvar X12 = W10 + 5;\nvar Y02 = -9;\nvar Y1 = (N7 - 1) * G2 + 9;\nvar WH = 20;\nvar WR = 6;\nvar WT = 2.4;\nfunction camera() {\n const C = Cam(45, 0.5, 1.62);\n fit(C, [[X05, Y02, 0], [X12, Y1, -8], [X12, Y02, 0], [X05, Y1, 0], [X05, Y02, H6 + TH3], [X12, Y02, H6 + TH3 + LIFT5]], 200, 166);\n return C;\n}\nvar trayRings = () => ({\n outer: rrect(X05, Y02, X12, Y1, WR, 6),\n inner: rrect(X05 + WT, Y02 + WT, X12 - WT, Y1 - WT, WR - WT, 6)\n});\nvar LR = (pts) => pts[0][0] <= pts[pts.length - 1][0] ? pts : pts.slice().reverse();\nfunction tray(P, front2, outer, inner3) {\n const far = [\n [poly(hull(ringAt(P, outer, 0).concat(ringAt(P, outer, WH)))), "sil"],\n [poly(ringAt(P, inner3, WH)), "nf"],\n [open(ringAt(P, run(inner3, (q) => !front2(q)), 2.5)), "nf lo"]\n ];\n const iF = LR(ringAt(P, run(inner3, front2), WH)), oT = LR(ringAt(P, run(outer, front2), WH)), oB = LR(ringAt(P, run(outer, front2), 0));\n const hx = (X05 + X12) / 2, onFront = (ring) => ring.map((q) => P(q.u, Y1, q.v));\n const near = [\n [poly([...iF, oT[oT.length - 1], ...oB.slice().reverse(), oT[0]]), "fo"],\n [open(oT), "nf lo"],\n [open(iF), "nf"],\n [open([oT[0], ...oB, oT[oT.length - 1]]), "nf sil"],\n [poly(onFront(rrect(hx - 11, 6.5, hx + 11, 12.5, 3, 5))), "nf"],\n [poly(onFront(rrect(hx - 9.4, 8, hx + 9.4, 11, 1.5, 5))), "nf lo"]\n ];\n return { far, near };\n}\nfunction card(i) {\n const n = N7 - i, t0 = TABS[(N7 - 1 - i) % 3];\n const shape = fillet(\n [[0, 0], [W10, 0], [W10, H6], [t0 + TW2, H6], [t0 + TW2, H6 + TH3], [t0, H6 + TH3], [t0, H6], [0, H6]],\n [1, 1, 3.2, 1.8, 2.4, 2.4, 1.8, 3.2]\n );\n return { n, t0, shape };\n}\nfunction pose2(P, i, t0, shape, th, lift) {\n const yb = i * G2, s = Math.sin(rad(th)), c = Math.cos(rad(th));\n const w = (u, v) => P(u, yb + v * s, v * c + lift);\n const wb = (u, v) => P(u, yb + v * s - TK2 * c, v * c + TK2 * s + lift);\n const punch = [];\n for (let k = 0; k < 8; k++) punch.push(w(t0 + TW2 / 2 + (k % 4 - 1.5) * 3.6, H6 + TH3 / 2 + (0.5 - Math.floor(k / 4)) * 2.8));\n return {\n back: poly(shape.map((p) => wb(p[0], p[1]))),\n face: poly(shape.map((p) => w(p[0], p[1]))),\n head: seg(w(6, H6 - 11), w(W10 - 6, H6 - 11)),\n rules: [H6 - 18, H6 - 25, H6 - 32, H6 - 39].map((v) => seg(w(6, v), w(W10 - 6, v))).join(""),\n punch\n };\n}\nfunction scene() {\n const C = camera();\n return { C, P: proj(C), front: facing(C), ...trayRings() };\n}\n\n// src/figures/riffle.ts\nvar mount20 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let stag = value;\n const { P, front: front2, outer, inner: inner3 } = scene();\n const paths = tray(P, front2, outer, inner3);\n const g = mk("g", {}, svg);\n reflect(svg, g, P, front2, outer, 0, 14);\n for (const [d2, cls] of paths.far) mk("path", { d: d2, class: cls }, g);\n const cards = [];\n for (let i = 0; i < N7; i++) {\n const { n, t0, shape } = card(i);\n const grp = mk("g", {}, g);\n const back = mk("path", { class: "lo" }, grp), face = mk("path", { class: "sil" }, grp);\n const head = mk("path", { class: "nf" }, grp), rules = mk("path", { class: "nf lo" }, grp);\n const punch = [];\n for (let k = 0; k < 8; k++) punch.push(mk("circle", { r: 1.05, class: "dot " + (k === n - 1 ? "m" : "off") }, grp));\n cards.push({ n, t0, shape, back, face, head, rules, punch, a: tween(REST12), z: tween(0) });\n }\n for (const [d2, cls] of paths.near) mk("path", { d: d2, class: cls }, g);\n const top = (i) => P(W10 / 2, i * G2 + H6 * Math.sin(rad(REST12)), H6 * Math.cos(rad(REST12)));\n const c0 = top(0), c1 = top(1), d = [c1[0] - c0[0], c1[1] - c0[1]];\n const px0 = P(0, 0, 0), px1 = P(1, 0, 0), ex = [px1[0] - px0[0], px1[1] - px0[1]];\n const HALF = W10 / 2 + 6, det = d[0] * ex[1] - d[1] * ex[0];\n function hit([x, y]) {\n const qx = x - c0[0], qy = y - c0[1];\n const s = (qx * ex[1] - qy * ex[0]) / det, r = (d[0] * qy - d[1] * qx) / det;\n if (Math.abs(r) > HALF || s < -0.5 || s > N7 + 1) return -1;\n return clamp(Math.round(s), 0, N7 - 1);\n }\n function draw(i, th, lift) {\n const cd = cards[i], q = pose2(P, i, cd.t0, cd.shape, th, lift);\n cd.back.setAttribute("d", q.back);\n cd.face.setAttribute("d", q.face);\n cd.head.setAttribute("d", q.head);\n cd.rules.setAttribute("d", q.rules);\n cd.punch.forEach((el, k) => place(el, q.punch[k]));\n }\n const B6 = register(stage, (_dt, now) => {\n let moving = false;\n cards.forEach((cd, i) => {\n draw(i, tval(cd.a, now), tval(cd.z, now));\n if (!tdone(cd.a, now) || !tdone(cd.z, now)) moving = true;\n });\n return moving;\n });\n bag.add(B6.unregister);\n let act = -1;\n const caption = (a) => a < 0 ? "rest" : String(N7 - a).padStart(2, "0");\n function setActive(a) {\n if (a === act) return;\n const now = performance.now(), from = a >= 0 ? a : act;\n act = a;\n cards.forEach((cd, i) => {\n const delay = Math.abs(i - from) * stag;\n const th = a < 0 ? REST12 : i < a ? BACK : i > a ? FWD : 0;\n tset(cd.a, th, now, delay);\n tset(cd.z, a === i ? LIFT5 : 0, now, delay);\n cd.face.classList.toggle("hi", i === a);\n cd.head.classList.toggle("hi", i === a);\n cd.punch[cd.n - 1].classList.toggle("m", i !== a);\n });\n read.textContent = caption(a);\n B6.wake();\n }\n bag.add(pointer(stage, { move: (p) => setActive(hit(p)), leave: () => setActive(-1) }));\n bag.on(stage, "keydown", (e) => {\n if (e.key === "ArrowLeft" || e.key === "ArrowDown") {\n setActive(act < 0 ? N7 - 1 : Math.min(N7 - 1, act + 1));\n e.preventDefault();\n } else if (e.key === "ArrowRight" || e.key === "ArrowUp") {\n setActive(act < 0 ? N7 - 1 : Math.max(0, act - 1));\n e.preventDefault();\n } else if (e.key === "Escape" && act >= 0) {\n setActive(-1);\n e.preventDefault();\n }\n });\n bag.on(stage, "blur", () => setActive(-1));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n stag = v;\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/router.ts\nvar X13 = 124;\nvar Y12 = 58;\nvar H7 = 14;\nvar T10 = 3;\nvar BR3 = 13;\nvar XS = [18, 48, 78, 108];\nvar YB = 11;\nvar KH = 5;\nvar KR = 4.6;\nvar L5 = 56;\nvar R02 = 3.3;\nvar R12 = 2.1;\nvar ELBOW = 0.21;\nvar STOP = R12 + 1;\nvar MAX = 44;\nvar REST13 = [-9, 3, -4, 32];\nvar LIT0 = 3;\nvar D8 = [Math.SQRT1_2, -Math.SQRT1_2];\nvar falloff5 = (u, R9) => clamp(1 - u / R9, 0.1, 1);\nvar along = (i, th, l) => {\n const s = Math.sin(rad(th)), c = Math.cos(rad(th));\n return [XS[i] + D8[0] * s * l, YB + D8[1] * s * l, H7 + KH - 1 + c * l];\n};\nvar mount21 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let R9 = value, over = null, lit2 = null;\n const C = Cam(45, 0.5, 1.78), pts = [[0, 0, 0], [X13, Y12, 0], [X13, 0, 0], [0, Y12, 0]];\n XS.forEach((_, i) => {\n for (const th of [-MAX, 0, MAX]) pts.push(along(i, th, L5 + R12));\n });\n fit(C, pts, 200, 166);\n const P = proj(C), front2 = facing(C), Pv = (q) => P(q[0], q[1], q[2]);\n const o = P(0, 0, 0), dd = P(D8[0], D8[1], 0), zz = P(0, 0, 1);\n const SH = Math.hypot(dd[0] - o[0], dd[1] - o[1]), SZ = o[1] - zz[1];\n const g = mk("g", {}, svg);\n const foot = rrect(0, 0, X13, Y12, BR3, 6), top = rrect(T10, T10, X13 - T10, Y12 - T10, BR3 - T10, 6);\n const inner3 = rrect(T10 + 1.6, T10 + 1.6, X13 - T10 - 1.6, Y12 - T10 - 1.6, BR3 - T10 - 1.6, 6);\n put(solid(g), { sil: poly(hull(ringAt(P, foot, 0).concat(ringAt(P, top, H7)))), crease: open(ringAt(P, run(inner3, front2), H7)) });\n for (let k = 0; k < 6; k++) {\n const z = H7 * 0.56, y = Y12 - T10 * (z / H7) + 0.1;\n place(mk("circle", { r: 1.05, class: k === 0 ? "dot m" : "dot off" }, g), P(34 + k * 9, y, z));\n }\n for (let j = 0; j < 3; j++) for (let k = 0; k < 13 - j % 2; k++) place(flatDot(g, C, 0.5, "dot off"), P(26 + j % 2 * 3 + k * 6, 29 + j * 5.5, H7));\n const ants = XS.map((x, i) => {\n const boss = solid(g);\n const shift = (ring) => ring.map((q) => ({ ...q, u: q.u + x, v: q.v + YB }));\n put(boss, prism(P, front2, shift(circ(KR, 16)), shift(circ(KR - 1.1, 16)), H7, H7 + KH));\n return { i, el: solid(g), sp: spring(REST13[i], { eps: 0.05 }), drawn: NaN, piv: Pv(along(i, 0, 0)) };\n });\n const gap = Math.abs(ants[1].piv[0] - ants[0].piv[0]) / SH;\n const disc2 = (p, r) => Array.from({ length: 20 }, (_, k) => [p[0] + r * SH * Math.cos(k * Math.PI / 10), p[1] + r * SH * Math.sin(k * Math.PI / 10)]);\n function drawAnt(a) {\n const th = a.sp.x;\n if (th === a.drawn) return;\n a.drawn = th;\n const b = Pv(along(a.i, th, 0)), t = Pv(along(a.i, th, L5)), e = Pv(along(a.i, th, L5 * ELBOW));\n const len = Math.hypot(t[0] - b[0], t[1] - b[1]), n = [-(t[1] - b[1]) / len, (t[0] - b[0]) / len];\n const w = lerp(R02, R12, ELBOW) * SH - 1.1;\n put(a.el, {\n sil: poly(hull(disc2(b, R02).concat(disc2(t, R12)))),\n crease: seg([e[0] + n[0] * w, e[1] + n[1] * w], [e[0] - n[0] * w, e[1] - n[1] * w])\n });\n }\n function light(a) {\n if (a === lit2) return;\n lit2?.el.sil.classList.remove("hi");\n lit2 = a;\n a.el.sil.classList.add("hi");\n }\n const B6 = register(stage, (dt) => {\n let m = false;\n for (const a of ants) {\n if (stepS(a.sp, dt)) m = true;\n drawAnt(a);\n }\n return m;\n });\n bag.add(B6.unregister);\n function retarget() {\n if (!over) {\n ants.forEach((a, i) => {\n a.sp.t = REST13[i];\n });\n light(ants[LIT0]);\n read.textContent = "rest";\n } else {\n const at2 = over;\n const dxs = ants.map((a) => (at2[0] - a.piv[0]) / SH), d0 = Math.min(...dxs.map(Math.abs));\n const near = ants[dxs.findIndex((d) => Math.abs(d) === d0)];\n ants.forEach((a, i) => {\n const dx = Math.sign(dxs[i]) * Math.max(Math.abs(dxs[i]) - STOP, 0), hz = Math.max((a.piv[1] - at2[1]) / SZ, Math.sqrt(Math.max(L5 * L5 - dx * dx, 0)), 0);\n const aim2 = Math.atan2(dx, hz) * 180 / Math.PI;\n a.sp.t = clamp(aim2, -MAX, MAX) * falloff5((Math.abs(dxs[i]) - d0) / gap, R9);\n });\n light(near);\n read.textContent = `antenna ${near.i + 1} \\xB7 ${Math.round(Math.abs(near.sp.t))}\\xB0`;\n }\n B6.wake();\n }\n light(ants[LIT0]);\n bag.add(pointer(stage, {\n move: (p) => {\n over = p;\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v;\n if (over) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/sieve.ts\nvar N8 = 3;\nvar R7 = 36;\nvar H8 = 11;\nvar PAN = 8;\nvar WALL2 = 2.6;\nvar SEAT = 2;\nvar FLOOR2 = 1.6;\nvar CHORDS = [-22, -11, 0, 11, 22];\nvar STEP2 = 40;\nvar S8 = 2.35;\nvar REST14 = 0.8;\nvar RIDE = 0.6;\nvar under = (f, i, gap) => {\n const a = f < 0 ? 1 : f;\n return i === a ? f < 0 ? gap * REST14 : gap : f >= 0 && i === a + 1 ? gap * RIDE : SEAT;\n};\nvar mount22 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const [, MID, MAX2] = TABLE.sieve;\n const C = Cam(45, 0.5, S8);\n const tall = PAN + N8 * H8 + SEAT + MAX2 * (1 + RIDE), rest = PAN + N8 * H8 + 2 * SEAT + MID * REST14;\n const ext = [];\n for (const [x, y] of [[-R7, 0], [R7, 0], [0, -R7], [0, R7]]) ext.push([x, y, rest - tall], [x, y, tall]);\n fit(C, ext, 200, 166);\n const P = proj(C), y0 = P(0, 0, 0)[1];\n const rise = (z) => P(0, 0, z)[1] - y0;\n const outer = circ(R7, 72), inner3 = circ(R7 - WALL2, 72);\n const g = mk("g", {}, svg);\n const drum = (z0, z1) => hull(outer.map((q) => P(q.u, q.v, z0)).concat(outer.map((q) => P(q.u, q.v, z1))));\n put(solid(g), { sil: poly(drum(0, PAN)), crease: poly(inner3.map((q) => P(q.u, q.v, PAN))) });\n const parts = Array.from({ length: N8 }, () => ({ s: solid(g), mesh: mk("path", { class: "nf lo" }, g) }));\n const through = (pts, zf, zm) => {\n const runs = [[]];\n for (const [u, v] of pts) {\n const s = P(u, v, zf), m = unproj(C, s[0], s[1], zm);\n if (Math.hypot(u, v) < R7 - WALL2 && Math.hypot(m[0], m[1]) < R7 - WALL2) runs[runs.length - 1].push(s);\n else if (runs[runs.length - 1].length) runs.push([]);\n }\n return runs.filter((r) => r.length > 1);\n };\n const chord = (a, b) => Array.from({ length: 29 }, (_, k) => [a[0] + (b[0] - a[0]) * k / 28, a[1] + (b[1] - a[1]) * k / 28]);\n const lines = CHORDS.flatMap((c) => [chord([c, -R7], [c, R7]), chord([-R7, c], [R7, c])]);\n lines.push(Array.from({ length: 73 }, (_, k) => {\n const t = Math.PI / 4 + k / 72 * Math.PI * 2, r = R7 - WALL2 - 0.05;\n return [r * Math.cos(t), r * Math.sin(t)];\n }));\n const body = drum(0, H8), mouth = inner3.map((q) => P(q.u, q.v, H8)), mesh = lines.flatMap((l) => through(l, FLOOR2, H8));\n const up = (pts, dy) => pts.map((p) => [p[0], p[1] + dy]);\n const gaps = Array.from({ length: N8 }, () => tween(SEAT));\n const drawn = gaps.map(() => NaN);\n let picked = -2, gap = value;\n function draw(now) {\n let z = PAN;\n gaps.forEach((t, i) => {\n z += tval(t, now);\n if (z !== drawn[i]) {\n drawn[i] = z;\n const dy = rise(z);\n put(parts[i].s, { sil: poly(up(body, dy)), crease: poly(up(mouth, dy)) });\n parts[i].mesh.setAttribute("d", mesh.map((r) => open(up(r, dy))).join(""));\n }\n z += H8;\n });\n }\n const loop = register(stage, (_dt, now) => {\n draw(now);\n return gaps.some((t) => !tdone(t, now));\n });\n bag.add(loop.unregister);\n function choose(f, now) {\n if (f === picked) return;\n picked = f;\n const a = f < 0 ? 1 : f;\n gaps.forEach((t, i) => tset(t, under(f, i, gap), now, Math.abs(i - a) * STEP2));\n parts.forEach((p, i) => p.s.sil.classList.toggle("hi", i === a));\n read.textContent = f < 0 ? "rest" : `sieve ${N8 - f} \\xB7 0`;\n loop.wake();\n }\n choose(-1, -1e9);\n draw(0);\n const bands = () => {\n let z = PAN;\n return gaps.map((_, i) => {\n z += under(-1, i, gap);\n const y = P(0, 0, z + H8 / 2)[1];\n z += H8;\n return y;\n });\n };\n let mids = bands();\n const pick = (p) => mids.reduce((best, y, i) => Math.abs(p[1] - y) < Math.abs(p[1] - mids[best]) ? i : best, 0);\n bag.add(pointer(stage, { move: (p) => choose(pick(p), performance.now()), leave: () => choose(-1, performance.now()) }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n gap = v;\n mids = bands();\n const f = picked;\n picked = -2;\n choose(f, performance.now());\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/slow.ts\nvar L6 = 210;\nvar BW3 = 26;\nvar BT2 = 5;\nvar CUBE = 15;\nvar NC2 = 6;\nvar SPEED = 1 / 9;\nvar GATE = L6 * 0.62;\nvar GH = 38;\nvar size = (u) => {\n const a = clamp(Math.min(u, 1 - u) / 0.08, 0, 1);\n return a * a * (3 - 2 * a);\n};\nvar RISE = L6 / NC2 / 2;\nvar FALL = RISE / 2;\nvar glow = (x) => {\n const d = x - GATE, w = d < 0 ? RISE : FALL;\n return Math.abs(d) >= w ? 0 : Math.cos(Math.PI / 2 * (Math.abs(d) / w)) ** 2;\n};\nvar mount23 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let slow2 = value, over = null, clock = 2.4;\n const rate = spring(1, { eps: 2e-3 });\n const C = Cam(45, 0.5, 1.8);\n fit(C, [[0, 0, 0], [L6, BW3, 0], [L6, 0, 0], [0, BW3, 0], [0, 0, GH], [GATE, -8, GH]], 200, 168);\n const P = proj(C), front2 = facing(C);\n const g = mk("g", {}, svg);\n const [bR, bI] = rings(0, 0, L6, BW3, 5, 1.3);\n put(solid(g), prism(P, front2, bR, bI, 0, BT2));\n const slats = mk("path", { class: "nf lo" }, g);\n const layer = mk("g", {}, g);\n const pool = [];\n for (let j = 0; j < NC2; j++) {\n const el = { ...solid(layer), dots: [] };\n for (let k = 0; k < 9; k++) el.dots.push(flatDot(el.g, C, 1, "dot off"));\n pool.push(el);\n }\n const gate = [[-8, -3, 0, GH - 4, GATE - 5.5], [BW3 + 3, BW3 + 8, 0, GH - 4, GATE + BW3 + 5.5], [-8, BW3 + 8, GH - 4, GH, GATE + BW3 + 8.5]].map(([y0, y1, z0, z1, key]) => {\n const el = solid(layer), [rg, ig] = rings(GATE - 1.6, y0, GATE + 1.6, y1, 1.4, 0.6);\n put(el, prism(P, front2, rg, ig, z0, z1));\n el.sil.style.transition = "none";\n return { el, key };\n });\n const lifts = Array.from({ length: NC2 }, () => spring(0, { eps: 0.03 }));\n let hot = -1, order2 = "", gatePct = 0;\n const B6 = register(stage, (dt) => {\n let m = stepS(rate, dt);\n const base = reducedMotion() ? 0 : 1;\n if (!over) rate.t = base;\n clock += dt * rate.x;\n const items = [];\n for (let j = 0; j < NC2; j++) {\n const q = j / NC2 + clock * SPEED, u = q - Math.floor(q), lap = Math.floor(q);\n items.push({ j, x: u * L6, s: size(u), serial: 141 + lap * NC2 + j });\n }\n let want = -1;\n if (over) {\n let best = 34;\n for (const it of items) if (it.s > 0.6 && Math.abs(it.x - over[0]) < best) {\n best = Math.abs(it.x - over[0]);\n want = it.j;\n }\n }\n hot = want;\n lifts.forEach((sp, j) => {\n sp.t = j === hot ? 11 : 0;\n if (stepS(sp, dt)) m = true;\n });\n items.sort((a, b) => a.x - b.x);\n let gateGlow = 0, hotSerial = 0;\n const draw = gate.map((p, i) => ({ id: "g" + i, key: p.key, g: p.el.g }));\n items.forEach((it, k) => {\n const el = pool[k], sz = CUBE * it.s, x0 = it.x - sz / 2, x1 = it.x + sz / 2, y0 = BW3 / 2 - sz / 2, y1 = BW3 / 2 + sz / 2;\n const z0 = BT2 + lifts[it.j].x, z1 = z0 + sz;\n draw.push({ id: k, key: it.x + BW3 / 2, g: el.g });\n if (sz < 0.3) {\n el.g.setAttribute("visibility", "hidden");\n return;\n }\n el.g.removeAttribute("visibility");\n const [rg, ig] = rings(x0, y0, x1, y1, 2.6 * it.s, 0.9 * it.s);\n put(el, prism(P, front2, rg, ig, z0, z1));\n const isHot = it.j === hot;\n el.sil.classList.toggle("hi", isHot);\n const pitch = sz * 0.2, rr = r2(0.55 * it.s * C.S);\n el.dots.forEach((d, b) => {\n place(d, P(it.x + (b % 3 - 1) * pitch, BW3 / 2 + (Math.floor(b / 3) - 1) * pitch, z1));\n d.setAttribute("rx", String(rr));\n d.setAttribute("ry", String(r2(rr * C.k)));\n d.setAttribute("class", it.serial >> b & 1 ? isHot ? "dot" : "dot m" : "dot off");\n });\n if (isHot) hotSerial = it.serial;\n gateGlow = Math.max(gateGlow, glow(it.x) * it.s);\n });\n draw.sort((a, b) => a.key - b.key);\n const sig = draw.map((d) => d.id).join();\n if (sig !== order2) {\n order2 = sig;\n draw.forEach((d) => layer.appendChild(d.g));\n }\n const pct = Math.round(gateGlow * 100);\n if (pct !== gatePct) {\n gatePct = pct;\n const stroke = pct ? `color-mix(in srgb, var(--hl-hi) ${pct}%, var(--hl-edge))` : "";\n gate.forEach((p) => {\n p.el.sil.style.stroke = stroke;\n });\n }\n const off = (clock * SPEED * L6 % 21 + 21) % 21, sl = [];\n for (let x = off; x < L6; x += 21) if (x > 5 && x < L6 - 5) sl.push(seg(P(x, 4, BT2), P(x, BW3 - 4, BT2)));\n slats.setAttribute("d", sl.join(""));\n read.textContent = `rate ${rate.x.toFixed(2)}\\xD7` + (hot >= 0 ? ` \\xB7 #${String(hotSerial).padStart(4, "0")}` : "");\n if (reducedMotion() && !over && rate.x === 0) return m;\n return true;\n });\n bag.add(B6.unregister);\n bag.add(pointer(stage, {\n move: (p) => {\n over = unproj(C, p[0], p[1], BT2);\n rate.t = slow2;\n B6.wake();\n },\n leave: () => {\n over = null;\n B6.wake();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n slow2 = v;\n if (over) rate.t = v;\n B6.wake();\n }, destroy: bag.dispose };\n};\n\n// src/figures/terminal.ts\nvar N9 = 24;\nvar V = 7;\nvar G3 = 11;\nvar T11 = 2.1;\nvar LH = 1.8;\nvar CH3 = 3.9;\nvar LIFT6 = 5.5;\nvar HB2 = 3.4;\nvar SLAB = 3;\nvar W11 = 150;\nvar TB2 = 17;\nvar PB2 = 17;\nvar PAD3 = 11;\nvar IN2 = 2.5;\nvar FIRST = 37;\nvar VB = PB2;\nvar VA = PB2 + V * G3;\nvar H9 = VA + TB2;\nvar E = 4;\nvar HIST = [\n "0 5 12",\n "2 7 3 9",\n "2 14",\n "4 6 10",\n "4 18",\n "2 3",\n "0 2",\n "0 2 6 8",\n "0 26",\n "0 20",\n "0 11 4",\n "2 9 12",\n "2 5 3 7",\n "4 16",\n "4 8 8",\n "2 4",\n "0 3 14",\n "0 24",\n "0 30",\n "0 17",\n "2 6 11",\n "2 13",\n "0 9 5",\n "0 4 7"\n];\nvar REST_TOP = N9 - V - 2.6;\nvar REST_LINE = 18;\nvar REST_R = 2;\nvar falloff6 = (d, R9) => clamp(1 - d / R9, 0, 1);\nvar smooth = (t) => t * t * (3 - 2 * t);\nvar front = (q) => 0.612 * q.nu + 0.5 * q.nv > 0;\nvar cut = (ring) => ring.map((q) => ({ ...q, v: clamp(q.v, VB, VA) }));\nvar mount24 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.62);\n const W32 = (u, v, w) => [u, w, v];\n fit(C, [W32(0, 0, -SLAB), W32(W11, 0, -SLAB), W32(0, H9, -SLAB), W32(W11, H9, -SLAB), W32(W11, 0, HB2 + 2.8), W32(0, H9, HB2 + 1.3), W32(PAD3, VA - 4, LIFT6 + LH)], 200, 166);\n const P = proj(C), P2 = (u, v, w) => P(u, w, v);\n let R9 = value, over = null, lit2 = [];\n const g = mk("g", {}, svg);\n const [sr, si] = rings(0, 0, W11, H9, 9, 2);\n put(solid(g), prism(P2, front, sr, si, -SLAB, 0));\n const rows = HIST.map((s, j) => {\n const [ind, ...tok] = s.split(" ").map(Number), segs = [];\n let at2 = ind;\n for (const n of tok) {\n const x0 = PAD3 + at2 * CH3;\n segs.push({ x0, x1: x0 + n * CH3 - 1.4, el: solid(g) });\n at2 += n + 1;\n }\n return { j, segs, sp: spring(0, { eps: 0.01 }), drawn: "" };\n });\n const [pr, pi] = rings(IN2, IN2, W11 - IN2, VB, 6, 1.4);\n put(solid(g), prism(P2, front, pr, pi, 0, HB2));\n const cy = (IN2 + VB) / 2;\n const chev = fillet([[0, 6.2], [8.5, 0], [0, -6.2], [0, -3.1], [4.25, 0], [0, 3.1]], [0.9, 1.1, 0.9, 0.5, 0.6, 0.5]);\n const chevEl = mk("path", { d: poly(chev.map(([x, y]) => P2(PAD3 + x, cy + y, HB2))), class: "nf" }, g);\n const [kr, ki] = rings(PAD3 + 13, cy - 5.2, PAD3 + 13 + CH3 * 1.5, cy + 5.2, 1.2, 0.8);\n const cursor = solid(g);\n put(cursor, prism(P2, front, kr, ki, HB2, HB2 + 2.8));\n const [tr, ti] = rings(IN2, VA, W11 - IN2, H9 - IN2, 6, 1.4);\n put(solid(g), prism(P2, front, tr, ti, 0, HB2));\n for (let i = 0; i < 3; i++) {\n const cx = PAD3 + i * 8.5, cv = (VA + H9 - IN2) / 2;\n const [r, ri] = rings(cx - 2.8, cv - 2.8, cx + 2.8, cv + 2.8, 2.8, 0.8);\n put(solid(g), prism(P2, front, r, ri, HB2, HB2 + 1.3));\n }\n function drawRow(rw, top2) {\n const key = top2 + "|" + rw.sp.x;\n if (key === rw.drawn) return;\n rw.drawn = key;\n const vc = VA - (rw.j - top2 + 0.5) * G3, v0 = Math.max(vc - T11, VB), v1 = Math.min(vc + T11, VA);\n const env = smooth(clamp((VA - vc - T11) / E, 0, 1)) * smooth(clamp((vc - T11 - VB) / E, 0, 1));\n const w0 = rw.sp.x * env;\n for (const s of rw.segs) {\n if (v1 - v0 < 0.05) {\n put(s.el, { sil: "", crease: "" });\n continue;\n }\n const ring = cut(rrect(s.x0, vc - T11, s.x1, vc + T11, T11, 4)), inner3 = cut(rrect(s.x0 + 0.7, vc - T11 + 0.7, s.x1 - 0.7, vc + T11 - 0.7, T11 - 0.7, 4));\n put(s.el, prism(P2, front, ring, inner3, w0, w0 + LH));\n }\n }\n function light(els) {\n for (const el of lit2) el.classList.remove("hi");\n lit2 = els;\n for (const el of lit2) el.classList.add("hi");\n }\n const top = spring(REST_TOP, { eps: 2e-3 });\n const B6 = register(stage, (dt) => {\n let m = stepS(top, dt);\n for (const rw of rows) {\n if (stepS(rw.sp, dt)) m = true;\n drawRow(rw, top.x);\n }\n return m;\n });\n bag.add(B6.unregister);\n function onFace([sx, sy], w) {\n const o = P2(0, 0, w), a = P2(1, 0, w), b = P2(0, 1, w);\n const ax2 = a[0] - o[0], ay = a[1] - o[1], bx = b[0] - o[0], by = b[1] - o[1], det = ax2 * by - ay * bx;\n return [((sx - o[0]) * by - (sy - o[1]) * bx) / det, (ax2 * (sy - o[1]) - ay * (sx - o[0])) / det];\n }\n function hit(p) {\n const qb = onFace(p, HB2), inU = (q2) => q2[0] > IN2 && q2[0] < W11 - IN2;\n if (inU(qb) && qb[1] > IN2 && qb[1] < VB) return "prompt";\n if (inU(qb) && qb[1] > VA && qb[1] < H9 - IN2) return 0;\n const q = onFace(p, 0);\n if (q[0] < 0 || q[0] > W11 || q[1] < 0 || q[1] > H9) return null;\n return q[1] <= VB ? "prompt" : clamp((VA - G3 / 2 - q[1]) / ((V - 1) * G3), 0, 1);\n }\n function retarget() {\n let t, c, r = R9;\n if (over === null) {\n t = REST_TOP;\n c = REST_LINE;\n r = REST_R;\n read.textContent = "rest";\n } else if (over === "prompt") {\n t = N9 - V;\n c = -1;\n read.textContent = "prompt";\n } else {\n t = over * (N9 - V);\n c = clamp(Math.round(over * (N9 - 1)), Math.ceil(t), Math.floor(t + V - 1));\n read.textContent = "line " + (FIRST + c);\n }\n top.t = t;\n for (const rw of rows) rw.sp.t = c < 0 ? 0 : LIFT6 * falloff6(Math.abs(rw.j - c), r);\n light(c < 0 ? [cursor.sil, chevEl] : rows[c].segs.map((s) => s.el.sil));\n B6.wake();\n }\n retarget();\n for (const rw of rows) rw.sp.x = rw.sp.t;\n bag.add(pointer(stage, {\n move: (p) => {\n over = hit(p);\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v;\n if (over !== null) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/terrain.ts\nvar N10 = 9;\nvar CELL = 14;\nvar FOOT4 = 11;\nvar HMAX = 58;\nvar EXT2 = N10 * CELL;\nvar PB3 = 5;\nvar falloff7 = (u) => u <= 0 ? 1 : u <= 0.417 ? 1 - u / 0.417 * 0.6875 : u <= 1 ? 0.3125 - (u - 0.417) / 0.583 * 0.2185 : 0.094;\nvar mount25 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n const C = Cam(45, 0.5, 1.58);\n fit(C, [[-6, -6, -PB3], [EXT2 + 6, EXT2 + 6, -PB3], [EXT2 + 6, -6, -PB3], [-6, EXT2 + 6, -PB3], [0, 0, HMAX * 0.75]], 200, 166);\n const P = proj(C), front2 = facing(C);\n let R9 = value * CELL, over = null;\n const g = mk("g", {}, svg), cols = [];\n const [pr, pi] = rings(-6, -6, EXT2 + 6, EXT2 + 6, 9, 2.2);\n put(solid(g), prism(P, front2, pr, pi, -PB3, 0));\n for (let s = 0; s <= 2 * (N10 - 1); s++) for (let i = 0; i < N10; i++) {\n const j = s - i;\n if (j < 0 || j >= N10) continue;\n const u = i / (N10 - 1), v = j / (N10 - 1);\n const h0 = 4 + 25 * Math.exp(-((u - 0.22) ** 2 + (v - 0.74) ** 2) / 0.07) + 12 * Math.exp(-((u - 0.8) ** 2 + (v - 0.26) ** 2) / 0.035);\n const x0 = i * CELL + (CELL - FOOT4) / 2, y0 = j * CELL + (CELL - FOOT4) / 2;\n const [ring, inner3] = rings(x0, y0, x0 + FOOT4, y0 + FOOT4, 2.6, 0.9);\n cols.push({ i, j, h0, ring, inner: inner3, sp: spring(h0, { eps: 0.04 }), el: solid(g), drawn: NaN });\n }\n const mark = mk("g", {}, g), md = [];\n for (let k = 0; k < 9; k++) md.push(flatDot(mark, C, 0.55, k === 4 ? "dot" : "dot m"));\n const peak = cols.reduce((a, b) => b.h0 > a.h0 ? b : a);\n const byCell = /* @__PURE__ */ new Map();\n cols.forEach((c) => byCell.set(c.i + "," + c.j, c));\n let mc = null, want = peak;\n function drawMark() {\n if (want !== mc) {\n mc = want;\n mc.el.g.after(mark);\n }\n const cx = (mc.i + 0.5) * CELL, cy = (mc.j + 0.5) * CELL, h = Math.max(0.6, mc.sp.x);\n md.forEach((el, k) => place(el, P(cx + (k % 3 - 1) * 2.5, cy + (Math.floor(k / 3) - 1) * 2.5, h)));\n }\n function drawCol(c) {\n const h = Math.max(0.6, c.sp.x);\n if (h === c.drawn) return;\n c.drawn = h;\n put(c.el, prism(P, front2, c.ring, c.inner, 0, h));\n c.el.sil.classList.toggle("hi", h > HMAX * 0.5);\n }\n const B6 = register(stage, (dt) => {\n let m = false;\n for (const c of cols) {\n if (stepS(c.sp, dt)) m = true;\n drawCol(c);\n }\n drawMark();\n return m;\n });\n bag.add(B6.unregister);\n function retarget() {\n for (const c of cols) {\n if (!over) {\n c.sp.t = c.h0;\n continue;\n }\n const dx = (c.i + 0.5) * CELL - over[0], dy = (c.j + 0.5) * CELL - over[1];\n c.sp.t = HMAX * falloff7(Math.hypot(dx, dy) / R9);\n }\n if (over) {\n const i = clamp(Math.floor(over[0] / CELL), 0, N10 - 1), j = clamp(Math.floor(over[1] / CELL), 0, N10 - 1);\n want = byCell.get(i + "," + j);\n read.textContent = `cell ${i}\\xB7${j}`;\n } else {\n want = peak;\n read.textContent = "rest";\n }\n B6.wake();\n }\n bag.add(pointer(stage, {\n move: (p) => {\n over = unproj(C, p[0], p[1], 0);\n retarget();\n },\n leave: () => {\n over = null;\n retarget();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return {\n set: (v) => {\n R9 = v * CELL;\n if (over) retarget();\n },\n destroy: bag.dispose\n };\n};\n\n// src/figures/turntable-geometry.ts\nvar BLK = [\n [-42, -42, 0, -12, -12, 14, 0],\n [-36, -36, 14, -18, -18, 34, 0],\n [0, -46, 0, 16, -6, 10, 1],\n [24, -32, 0, 40, -16, 44, 2],\n [-44, 4, 0, -4, 18, 22, 3],\n [8, 6, 0, 40, 38, 8, 4],\n [16, 14, 8, 28, 26, 20, 4],\n [-30, 28, 0, -16, 42, 12, 5]\n];\nvar HOME = 45;\nvar DETENT = 90;\nvar detent = (a) => HOME + DETENT * Math.round((a - HOME) / DETENT);\nvar depth = (b, s, c) => (b[0] + b[3]) * s + (b[1] + b[4]) * c + (b[2] + b[5]) * 0.01;\nfunction behind(A2, B6, s, c) {\n if (A2[6] === B6[6]) return A2[2] < B6[2];\n if (A2[3] <= B6[0]) return s > 0;\n if (B6[3] <= A2[0]) return s < 0;\n if (A2[4] <= B6[1]) return c > 0;\n if (B6[4] <= A2[1]) return c < 0;\n return depth(A2, s, c) < depth(B6, s, c);\n}\nfunction scr(b, s, c, k) {\n const zf = Math.sqrt(1 - k * k);\n let x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;\n for (const x of [b[0], b[3]]) for (const y of [b[1], b[4]]) for (const z of [b[2], b[5]]) {\n const X = x * c - y * s, Y = (x * s + y * c) * k - z * zf;\n x0 = Math.min(x0, X);\n x1 = Math.max(x1, X);\n y0 = Math.min(y0, Y);\n y1 = Math.max(y1, Y);\n }\n return [x0, y0, x1, y1];\n}\nfunction paintOrder(bs, s, c, k) {\n const n = bs.length, bx = bs.map((b) => scr(b, s, c, k));\n const next = bs.map(() => []), wait = bs.map(() => 0);\n for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {\n const a = bx[i], b = bx[j];\n if (i === j || a[2] <= b[0] || b[2] <= a[0] || a[3] <= b[1] || b[3] <= a[1]) continue;\n if (behind(bs[i], bs[j], s, c)) {\n next[i].push(j);\n wait[j]++;\n }\n }\n const out = [], done2 = bs.map(() => false);\n let forced = 0;\n while (out.length < n) {\n let pick = -1, pass = 0;\n for (; pass < 2 && pick < 0; pass++)\n for (let i = 0; i < n; i++)\n if (!done2[i] && (pass || !wait[i]) && (pick < 0 || depth(bs[i], s, c) < depth(bs[pick], s, c))) pick = i;\n if (pass === 2) forced++;\n done2[pick] = true;\n out.push(pick);\n for (const j of next[pick]) wait[j]--;\n }\n return { order: out, forced };\n}\nvar order = (bs, s, c, k) => paintOrder(bs, s, c, k).order;\n\n// src/figures/turntable.ts\nvar RING = 70;\nvar PT3 = 4;\nvar WMAX = 540;\nvar ON_INDEX = 14;\nvar mount26 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let coast = value;\n const spin = { a: HOME, w: 0, mode: "rest", tgt: HOME };\n const kk = spring(0.5, { eps: 5e-4 });\n let lastX = null, lastT = 0, lastMove = -1e9;\n function stepSpin(dt, now) {\n if (spin.mode === "rest") return false;\n const n = Math.max(1, Math.ceil(dt * 240)), h = dt / n;\n for (let i = 0; i < n; i++) {\n if (spin.mode === "coast") {\n spin.w *= Math.exp(-h * 1e3 / coast);\n spin.a += spin.w * h;\n if (Math.abs(spin.w) < 45 && now - lastMove > 90) {\n spin.mode = "settle";\n spin.tgt = detent(spin.a + spin.w * 0.2);\n }\n } else {\n spin.w += (-90 * (spin.a - spin.tgt) - 16 * spin.w) * h;\n spin.a += spin.w * h;\n }\n }\n if (spin.mode === "settle" && Math.abs(spin.a - spin.tgt) < 0.02 && Math.abs(spin.w) < 0.3) {\n spin.a = spin.tgt;\n spin.w = 0;\n spin.mode = "rest";\n }\n return spin.mode !== "rest";\n }\n const C = Cam(HOME, 0.5, 1.85);\n C.ox = 200;\n C.oy = 184;\n const plat = circ(RING), platIn = circ(RING - 1.5);\n const blocks = BLK.map((b) => {\n const [ring, inner3] = rings(b[0], b[1], b[3], b[4], 3, 1.1);\n return { b, ring, inner: inner3 };\n });\n const TALL = blocks.find((k) => k.b[5] >= 40);\n const tops = {};\n blocks.forEach((k) => {\n if (!tops[k.b[6]] || k.b[5] > tops[k.b[6]].b[5]) tops[k.b[6]] = k;\n });\n const base = {};\n blocks.forEach((k) => {\n if (k.b[2] === 0) base[k.b[6]] = [(k.b[0] + k.b[3]) / 2, (k.b[1] + k.b[4]) / 2];\n });\n const g = mk("g", {}, svg);\n const platter = solid(g), ticks = mk("path", { class: "nf lo" }, g), major = mk("path", { class: "nf" }, g);\n const north = flatDot(g, C, 1.7, "dot m");\n const index = mk("path", { class: "nf" }, g);\n const pool = BLK.map(() => solid(g));\n const acc = mk("g", {}, g), accD = [];\n for (let k = 0; k < 16; k++) {\n const r = Math.floor(k / 4), q = k % 4, edge = r % 3 === 0, side = q % 3 === 0;\n if (edge && side) continue;\n accD.push({ r, q, el: flatDot(acc, C, 0.6, edge || side ? "dot m" : "dot") });\n }\n let accAt = null;\n const B6 = register(stage, (dt, now) => {\n let m = stepSpin(dt, now);\n if (stepS(kk, dt)) m = true;\n C.az = rad(spin.a);\n C.k = kk.x;\n const P = proj(C), front2 = facing(C), c = Math.cos(C.az), s = Math.sin(C.az);\n put(platter, prism(P, front2, plat, platIn, -PT3, 0));\n const tk = [], mj = [];\n for (let d = 0; d < 360; d += 15) {\n const a = rad(d), long = d % 90 === 0, r0 = long ? RING - 12 : RING - 8;\n (long ? mj : tk).push(seg(P(Math.cos(a) * r0, Math.sin(a) * r0, 0), P(Math.cos(a) * (RING - 4.5), Math.sin(a) * (RING - 4.5), 0)));\n }\n ticks.setAttribute("d", tk.join(""));\n major.setAttribute("d", mj.join(""));\n place(north, P(0, -(RING - 17), 0));\n north.setAttribute("ry", String(r2(1.7 * C.S * C.k)));\n const bottom = C.oy + RING * C.S * C.k + PT3 * C.S * Math.sqrt(1 - C.k * C.k);\n index.setAttribute("d", `M${C.ox - 4} ${r2(bottom + 10)}L${C.ox} ${r2(bottom + 4)}L${C.ox + 4} ${r2(bottom + 10)}`);\n let lit2 = -1, best = ON_INDEX;\n if (lastX !== null || spin.mode !== "rest")\n for (const col in base) {\n const [x, y] = base[col], off = Math.abs(Math.atan2(x * c - y * s, x * s + y * c)) * 180 / Math.PI;\n if (off < best) {\n best = off;\n lit2 = +col;\n }\n }\n order(BLK, s, c, C.k).forEach((j, i) => {\n const k = blocks[j];\n put(pool[i], prism(P, front2, k.ring, k.inner, k.b[2], k.b[5]));\n pool[i].sil.classList.toggle("hi", k === tops[lit2]);\n if (k === TALL && accAt !== pool[i]) {\n accAt = pool[i];\n accAt.g.after(acc);\n }\n });\n const [x0, y0, , x1, y1, z1] = TALL.b, cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;\n accD.forEach(({ r, q, el }) => {\n place(el, P(cx + (q - 1.5) * 2.6, cy + (r - 1.5) * 2.6, z1));\n el.setAttribute("ry", String(r2(0.6 * C.S * C.k)));\n });\n read.textContent = `az ${String(Math.round((spin.a % 360 + 360) % 360)).padStart(3, "0")}\\xB0 \\xB7 el ${Math.round(Math.asin(kk.x) * 180 / Math.PI)}\\xB0`;\n return m;\n });\n bag.add(B6.unregister);\n bag.add(pointer(stage, {\n move: (p) => {\n const now = performance.now();\n kk.t = lerp(Math.sin(rad(40)), Math.sin(rad(20)), clamp(p[1] / 320, 0, 1));\n if (lastX !== null) {\n const dx = p[0] - lastX;\n if (reducedMotion()) spin.a -= dx * 0.9;\n else {\n const vp = dx / Math.max(8e-3, (now - lastT) / 1e3);\n spin.w = clamp(lerp(spin.w, -vp * 0.9, 0.35), -WMAX, WMAX);\n spin.mode = "coast";\n }\n if (Math.abs(dx) > 0.5) lastMove = now;\n }\n lastX = p[0];\n lastT = now;\n B6.wake();\n },\n leave: () => {\n lastX = null;\n kk.t = 0.5;\n if (reducedMotion()) spin.a = detent(spin.a);\n B6.wake();\n }\n }));\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n coast = v;\n B6.wake();\n }, destroy: bag.dispose };\n};\n\n// src/figures/vault.ts\nvar R8 = 40;\nvar DP = 14;\nvar ZC3 = 64;\nvar RD = 17;\nvar DT = 4;\nvar KN = 6;\nvar KH2 = 6;\nvar GAP3 = 10;\nvar KL = 11;\nvar KW = 12;\nvar KD = 10;\nvar BR4 = 3.6;\nvar HX = -R8 - 8;\nvar HR3 = 4.6;\nvar WX0 = -70;\nvar WX1 = 70;\nvar WZ1 = 130;\nvar WT2 = 14;\nvar COMBO = 40;\nvar REST15 = 30;\nvar STEP3 = 50;\nvar WMAX2 = 150;\nvar THETA = [72, 26, -20];\nvar V2 = [Math.SQRT1_2 * Math.sqrt(0.75), Math.SQRT1_2 * Math.sqrt(0.75), 0.5];\nvar dir = (deg) => [Math.cos(deg * Math.PI / 180), 0, Math.sin(deg * Math.PI / 180)];\nvar wrap = (d) => ((d + 50) % 100 + 100) % 100 - 50;\nfunction bar(P, o, e, a, b, ring, inner3, s0, s1) {\n const at2 = (q, s) => P(\n o[0] + e[0] * s + a[0] * q.u + b[0] * q.v,\n o[1] + e[1] * s + a[1] * q.u + b[1] * q.v,\n o[2] + e[2] * s + a[2] * q.u + b[2] * q.v\n );\n const seen = (q) => [0, 1, 2].reduce((t, i) => t + (a[i] * q.nu + b[i] * q.nv) * V2[i], 0) > 0;\n return {\n sil: poly(hull(ring.map((q) => at2(q, s0)).concat(ring.map((q) => at2(q, s1))))),\n crease: open(run(inner3, seen).map((q) => at2(q, s1)))\n };\n}\nvar mount27 = ({ stage, svg, read }, value) => {\n const bag = disposer();\n let coast = value;\n const Y = [0, 1, 0], X = [1, 0, 0], Z = [0, 0, 1], O = [0, 0, ZC3];\n const C = Cam(45, 0.5, 1.5);\n fit(C, [[WX0, -WT2, 0], [WX1, -WT2, 0], [WX0, -WT2, WZ1], [WX1, -WT2, WZ1], [WX0, KD, 0], [WX1, 0, 0], [WX0, 0, WZ1], [WX1, 0, WZ1]], 200, 166);\n const P = proj(C);\n const g = mk("g", {}, svg);\n put(solid(g), bar(P, [0, 0, 0], Y, X, Z, rrect(WX0, 0, WX1, WZ1, 9, 6), rrect(WX0 + 2, 2, WX1 - 2, WZ1 - 2, 7, 6), -WT2, 0));\n put(solid(g), bar(P, [HX, DP * 0.55, 0], Z, X, Y, circ(HR3, 28), circ(HR3 - 1, 28), ZC3 - 26, ZC3 + 26));\n for (const dz of [-15, 15]) put(solid(g), bar(P, [0, 0, ZC3 + dz], Y, X, Z, rrect(HX, -4, -R8 + 6, 4, 3, 4), rrect(HX + 1, -3, -R8 + 5, 3, 2, 4), 1, DP * 0.8));\n put(solid(g), bar(P, O, Y, X, Z, circ(R8, 72), circ(R8 - 2.4, 72), 0, DP));\n mk("path", { class: "nf lo", d: poly(circ(R8 - 8, 64).map((q) => P(q.u, DP, ZC3 + q.v))) }, g);\n mk("path", { class: "nf", d: seg(P(0, DP, ZC3 + RD + 1.8), P(0, DP, ZC3 + RD + 6.5)) }, g);\n const dial = solid(g);\n put(dial, bar(P, O, Y, X, Z, circ(RD, 56), circ(RD - 1.4, 56), DP, DP + DT));\n const ticks = mk("path", { class: "nf lo" }, g), longs = mk("path", { class: "nf" }, g), combo = mk("path", { class: "nf" }, g);\n put(solid(g), bar(P, O, Y, X, Z, circ(KN, 32), circ(KN - 1, 32), DP + DT, DP + DT + KH2));\n const bolts = THETA.map((th) => {\n const e = dir(th), t = dir(th + 90);\n const el = solid(g), keep = solid(g);\n put(keep, bar(P, O, Y, e, t, rrect(R8 + GAP3, -KW / 2, R8 + GAP3 + KL, KW / 2, 3, 4), rrect(R8 + GAP3 + 1.4, -KW / 2 + 1.4, R8 + GAP3 + KL - 1.4, KW / 2 - 1.4, 1.8, 4), 0, KD));\n for (const s of [-3.4, 3.4]) {\n const c = R8 + GAP3 + KL / 2 + s;\n place(mk("circle", { r: 0.9, class: "dot off" }, keep.g), P(e[0] * c, KD, ZC3 + e[2] * c));\n }\n return { th, e, t, el, tw: tween(0), drawn: NaN };\n });\n const drawBolt = (b, k) => {\n const s1 = lerp(R8 + GAP3 + 4.5, R8 + 1.2, k);\n if (s1 === b.drawn) return;\n b.drawn = s1;\n put(b.el, bar(P, [0, DP / 2, ZC3], b.e, b.t, Y, circ(BR4, 24), circ(BR4 - 0.9, 24), R8 - 0.2, s1));\n };\n const spin = { a: REST15, w: 0, mode: "rest", tgt: REST15 };\n let raw = REST15, said = "", last2 = null, lastT = 0, lastMove = -1e9, over = false, opened = false, from = 1, lit2 = null, drawnA = NaN;\n function drawDial() {\n if (spin.a === drawnA) return;\n drawnA = spin.a;\n const tk = [], lg = [], z = DP + DT;\n for (let m = 0; m < 100; m += 5) {\n const th = 90 + (m - spin.a) * 3.6, e = dir(th), r0 = m % 10 ? RD - 4 : RD - 6.5, r1 = RD - 1.8;\n const d = seg(P(e[0] * r0, z, ZC3 + e[2] * r0), P(e[0] * r1, z, ZC3 + e[2] * r1));\n if (m === COMBO) combo.setAttribute("d", d);\n else (m % 10 ? tk : lg).push(d);\n }\n ticks.setAttribute("d", tk.join(""));\n longs.setAttribute("d", lg.join(""));\n }\n function light() {\n const want = opened ? "bolts" : over ? "dial" : "combo";\n if (want === lit2) return;\n lit2 = want;\n bolts.forEach((b) => b.el.sil.classList.toggle("hi", want === "bolts"));\n dial.sil.classList.toggle("hi", want === "dial");\n combo.classList.toggle("hi", want === "combo");\n }\n function setOpen(on, now) {\n opened = on;\n bolts.forEach((b, i) => tset(b.tw, on ? 1 : 0, now, Math.abs(i - from) * STEP3));\n light();\n }\n function stepSpin(dt, now) {\n if (spin.mode === "rest") return false;\n const n = Math.max(1, Math.ceil(dt * 240)), h = dt / n;\n for (let i = 0; i < n; i++) {\n if (spin.mode === "coast") {\n spin.w = spin.w * Math.exp(-h * 1e3 / coast) - 40 * Math.sin(spin.a / 10 * 2 * Math.PI) * h;\n spin.a += spin.w * h;\n if (Math.abs(spin.w) < 12 && now - lastMove > 90) {\n spin.mode = "settle";\n spin.tgt = Math.round((spin.a + spin.w * 0.15) / 10) * 10;\n }\n } else {\n spin.w += (-90 * (spin.a - spin.tgt) - 16 * spin.w) * h;\n spin.a += spin.w * h;\n }\n }\n if (spin.mode === "settle" && Math.abs(spin.a - spin.tgt) < 0.01 && Math.abs(spin.w) < 0.2) {\n spin.a = spin.tgt = (spin.tgt % 100 + 100) % 100;\n spin.w = 0;\n spin.mode = "rest";\n }\n return spin.mode !== "rest";\n }\n const B6 = register(stage, (dt, now) => {\n let m = stepSpin(dt, now);\n const near = Math.abs(wrap(spin.a - COMBO));\n if (!opened && near < 0.8 && Math.abs(spin.w) < 6) setOpen(true, now);\n else if (opened && near > 2.5) setOpen(false, now);\n drawDial();\n bolts.forEach((b) => {\n drawBolt(b, tval(b.tw, now));\n if (!tdone(b.tw, now)) m = true;\n });\n const n = String((Math.round(spin.a) % 100 + 100) % 100).padStart(2, "0");\n const say = over || spin.mode !== "rest" ? `dial ${n}` + (opened ? " \\xB7 open" : "") : "rest";\n if (say !== said) read.textContent = said = say;\n return m;\n });\n bag.add(B6.unregister);\n const p0 = P(0, DP, 0), px = P(1, DP, 0), pz = P(0, DP, 1);\n const ax2 = [px[0] - p0[0], px[1] - p0[1]], az = [pz[0] - p0[0], pz[1] - p0[1]], det = ax2[0] * az[1] - ax2[1] * az[0];\n function onFace([sx, sy]) {\n const qx = sx - p0[0], qy = sy - p0[1];\n return [(qx * az[1] - qy * az[0]) / det, (ax2[0] * qy - ax2[1] * qx) / det - ZC3];\n }\n function leave() {\n over = false;\n last2 = null;\n if (reducedMotion()) {\n raw = spin.a = (Math.round(spin.a / 10) * 10 % 100 + 100) % 100;\n spin.w = 0;\n spin.mode = "rest";\n }\n light();\n B6.wake();\n }\n bag.add(pointer(stage, {\n move: (p) => {\n const [u, v] = onFace(p), r = Math.hypot(u, v);\n if (r > R8 + GAP3 + KL + 10) return leave();\n const now = performance.now(), ang = Math.atan2(v, u) * 180 / Math.PI;\n over = true;\n from = THETA.reduce((bi, th, i) => Math.abs(wrap((ang - th) / 3.6)) < Math.abs(wrap((ang - THETA[bi]) / 3.6)) ? i : bi, 0);\n if (last2 !== null) {\n const da = -((ang - last2 + 540) % 360 - 180) / 3.6 * clamp(r / 22, 0, 1);\n if (reducedMotion()) {\n raw += da;\n spin.a = Math.round(raw / 10) * 10;\n spin.mode = "rest";\n } else {\n spin.w = clamp(lerp(spin.w, da / Math.max(8e-3, (now - lastT) / 1e3), 0.35), -WMAX2, WMAX2);\n spin.mode = "coast";\n raw = spin.a;\n }\n if (Math.abs(da) > 0.05) lastMove = now;\n }\n last2 = ang;\n lastT = now;\n light();\n B6.wake();\n },\n leave\n }));\n light();\n bag.add(() => svg.replaceChildren());\n return { set: (v) => {\n coast = v;\n B6.wake();\n }, destroy: bag.dispose };\n};\n\n// src/index.ts\nfunction riffle(el, options) {\n return create({\n id: "riffle",\n label: "A tray of eight cards. Hover or use the arrow keys to pull a card.",\n rest: "rest",\n engine: mount20,\n focusable: true\n }, el, options);\n}\nfunction terrain(el, options) {\n return create({\n id: "terrain",\n label: "Eighty-one pillars on a plinth that rise around the pointer and rest as a dune with two rises.",\n rest: "rest",\n engine: mount25\n }, el, options);\n}\nfunction exploded(el, options) {\n return create({\n id: "exploded",\n label: "An app window taken apart into four layers. Moving across opens the gap; moving down picks a layer.",\n rest: "",\n engine: mount7\n }, el, options);\n}\nfunction phosphor(el, options) {\n return create({\n id: "phosphor",\n label: "A seven by seven dot matrix on a floating tile that plays a loop, and fades like phosphor where you paint it.",\n rest: "loop",\n engine: mount15\n }, el, options);\n}\nfunction slow(el, options) {\n return create({\n id: "slow",\n label: "Crates riding a belt through a gate. Hovering slows the clock without stopping it.",\n rest: "rate 1.00\\xD7",\n engine: mount23\n }, el, options);\n}\nfunction turntable(el, options) {\n return create({\n id: "turntable",\n label: "Blocks on a turntable. Flick across it to spin it; it settles on the nearest quarter turn.",\n rest: "az 045\\xB0 \\xB7 el 30\\xB0",\n engine: mount26\n }, el, options);\n}\nfunction keyboard(el, options) {\n return create({\n id: "keyboard",\n label: "A sixty-key board. The key under the pointer sinks, and its neighbours follow it down, less the further away.",\n rest: "rest",\n engine: mount8\n }, el, options);\n}\nfunction elevator(el, options) {\n return create({\n id: "elevator",\n label: "Four floors beside an open shaft. The pointer\'s height picks a floor, and the car travels there through the ones between.",\n rest: "rest",\n engine: mount6\n }, el, options);\n}\nfunction phone(el, options) {\n return create({\n id: "phone",\n label: "A phone in layers: glass, board, battery, shell. Moving across opens the gap; moving down picks a layer.",\n rest: "rest",\n engine: mount14\n }, el, options);\n}\nfunction laptop(el, options) {\n return create({\n id: "laptop",\n label: "A thin laptop: the pointer\'s height sets how far the lid stands open, and the lid follows it on a spring.",\n rest: "rest",\n engine: mount9\n }, el, options);\n}\nfunction terminal(el, options) {\n return create({\n id: "terminal",\n label: "A terminal window: the pointer\'s height scrolls back through its history, and the line under it lifts off the screen.",\n rest: "rest",\n engine: mount24\n }, el, options);\n}\nfunction cabinet(el, options) {\n return create({\n id: "cabinet",\n label: "A rack of twelve blades: the pointer\'s height pulls the nearest ones out on their rails, the farther the less.",\n rest: "rest",\n engine: mount3\n }, el, options);\n}\nfunction branches(el, options) {\n return create({\n id: "branches",\n label: "A commit graph on a board: the commit under the pointer rises, and its history rises after it, the farther back the less.",\n rest: "rest",\n engine: mount2\n }, el, options);\n}\nfunction vault(el, options) {\n return create({\n id: "vault",\n label: "A vault door: circling the pointer turns its dial, which coasts and catches every ten; on forty its three bolts draw back.",\n rest: "rest",\n engine: mount27\n }, el, options);\n}\nfunction lockers(el, options) {\n return create({\n id: "lockers",\n label: "A bank of twelve lockers, one ajar at rest: the locker under the pointer opens, and the one open before it swings shut.",\n rest: "rest",\n engine: mount10\n }, el, options);\n}\nfunction loupe(el, options) {\n return create({\n id: "loupe",\n label: "A stand loupe over a blank ruled sheet: the pointer drags it across, and the rules pass enlarged under the glass with nothing between them.",\n rest: "rest",\n engine: mount11\n }, el, options);\n}\nfunction padlock(el, options) {\n return create({\n id: "padlock",\n label: "A padlock: as the pointer nears, the shackle springs up out of the body and swings open about its long leg.",\n rest: "rest",\n engine: mount12\n }, el, options);\n}\nfunction patch(el, options) {\n return create({\n id: "patch",\n label: "A patch panel of twenty-four ports: the cable under the pointer lifts, and its neighbours lean away, less the further away.",\n rest: "rest",\n engine: mount13\n }, el, options);\n}\nfunction dish(el, options) {\n return create({\n id: "dish",\n label: "A parabolic dish on a two-axis gimbal: the pointer aims it, and it follows on a spring.",\n rest: "rest",\n engine: mount4\n }, el, options);\n}\nfunction router(el, options) {\n return create({\n id: "router",\n label: "A wifi router whose antennas lean toward the pointer, the nearest the most and the others less the further away.",\n rest: "rest",\n engine: mount21\n }, el, options);\n}\nfunction sieve(el, options) {\n return create({\n id: "sieve",\n label: "Three test sieves stacked over a pan: the pointer\'s height picks one, it rises clear of the stack, and every mesh is bare.",\n rest: "rest",\n engine: mount22\n }, el, options);\n}\nfunction rail(el, options) {\n return create({\n id: "rail",\n label: "A garment rail with seven bare hangers: the pointer brushes them, and each rocks away from it, the nearest most, and settles.",\n rest: "rest",\n engine: mount19\n }, el, options);\n}\nfunction plug(el, options) {\n return create({\n id: "plug",\n label: "A wall socket and a plug lying on the floor at the end of its cord: the pointer draws the plug up toward the socket, and it stops short of it.",\n rest: "rest",\n engine: mount17\n }, el, options);\n}\nfunction query(el, options) {\n return create({\n id: "query",\n label: "A question mark built as a solid on a plinth, its dot a loose ball: the hook turns about its stem toward the pointer, and the ball rolls after it.",\n rest: "rest",\n engine: mount18\n }, el, options);\n}\nfunction drawer(el, options) {\n return create({\n id: "drawer",\n label: "A filing cabinet of three drawers: the pointer\'s height picks one, it slides out, and inside are two dividers and nothing between them.",\n rest: "rest",\n engine: mount5\n }, el, options);\n}\nfunction basket(el, options) {\n return create({\n id: "basket",\n label: "An empty wire shopping basket under a bail handle: the pointer tilts it toward itself on a spring, so the bare floor shows, and the handle swings after it, late.",\n rest: "rest",\n engine: mount\n }, el, options);\n}\nfunction plot(el, options) {\n return create({\n id: "plot",\n label: "A bar chart with no data: seven flat tabs on its base, before a plate of grid lines. The pointer brushes them, and each lifts a little, the nearest most, and drops back to zero.",\n rest: "rest",\n engine: mount16\n }, el, options);\n}\nvar HAIRLINE_FIGURES = {\n basket,\n branches,\n cabinet,\n dish,\n drawer,\n elevator,\n exploded,\n keyboard,\n laptop,\n lockers,\n loupe,\n padlock,\n patch,\n phone,\n phosphor,\n plot,\n plug,\n query,\n rail,\n riffle,\n router,\n sieve,\n slow,\n terminal,\n terrain,\n turntable,\n vault\n};\n';
|
|
15
16
|
|
|
16
17
|
// src/hairline/utils/buildHairlineHtml.ts
|
|
17
18
|
var UNSAFE_CSS_CHARS = /[;{}<>\\]/g;
|
|
19
|
+
var HANDLE = `window[${JSON.stringify(HAIRLINE_HANDLE_GLOBAL)}]`;
|
|
18
20
|
var toScriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
|
|
21
|
+
var toOptionsJson = ({ intensity, label }) => toScriptJson(label !== void 0 ? { intensity, label } : { intensity });
|
|
19
22
|
var toCssDeclarations = (vars) => Object.entries(vars).map(([name, value]) => `${name}:${value.replace(UNSAFE_CSS_CHARS, "")};`).join("");
|
|
20
|
-
var buildHairlineHtml = ({ bundle, figure, vars, intensity, label }) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
var buildHairlineHtml = ({ bundle, figure, vars, intensity, label }) => [
|
|
24
|
+
'<!doctype html><html><head><meta charset="utf-8">',
|
|
25
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">',
|
|
26
|
+
`<style>:root{${toCssDeclarations(vars)}}html,body{margin:0;height:100%;overflow:hidden;background:var(--hairline-plate)}#hairline{width:100%;height:100%}</style>`,
|
|
27
|
+
'</head><body><div id="hairline"></div><script>(function(){',
|
|
28
|
+
bundle,
|
|
29
|
+
`var mount=HAIRLINE_FIGURES[${toScriptJson(figure)}];`,
|
|
30
|
+
`if(typeof mount==='function'){${HANDLE}=mount(document.getElementById('hairline'),${toOptionsJson({ intensity, label })});}`,
|
|
31
|
+
"})();</script></body></html>"
|
|
32
|
+
].join("");
|
|
33
|
+
var buildHairlineUpdateScript = (options) => `(function(){var h=${HANDLE};if(h&&typeof h.update==='function'){h.update(${toOptionsJson(options)});}})();true;`;
|
|
34
|
+
|
|
35
|
+
// src/hairline/HairlineVarName.ts
|
|
36
|
+
var HairlineVarName = /* @__PURE__ */ ((HairlineVarName2) => {
|
|
37
|
+
HairlineVarName2["Plate"] = "--hairline-plate";
|
|
38
|
+
HairlineVarName2["Hi"] = "--hairline-hi";
|
|
39
|
+
HairlineVarName2["Edge"] = "--hairline-edge";
|
|
40
|
+
HairlineVarName2["Mid"] = "--hairline-mid";
|
|
41
|
+
HairlineVarName2["Lo"] = "--hairline-lo";
|
|
42
|
+
return HairlineVarName2;
|
|
43
|
+
})(HairlineVarName || {});
|
|
33
44
|
|
|
34
45
|
// src/hairline/utils/hairlineVars.ts
|
|
35
46
|
var hairlineVars = (colors) => ({
|
|
36
|
-
"--hairline-plate": colors.background,
|
|
37
|
-
"--hairline-hi": colors.text,
|
|
38
|
-
"--hairline-edge": colors.textSecondary,
|
|
39
|
-
"--hairline-mid": colors.textTertiary !== void 0 ? colors.textTertiary : colors.border,
|
|
40
|
-
"--hairline-lo": colors.divider
|
|
47
|
+
["--hairline-plate" /* Plate */]: colors.background,
|
|
48
|
+
["--hairline-hi" /* Hi */]: colors.text,
|
|
49
|
+
["--hairline-edge" /* Edge */]: colors.textSecondary,
|
|
50
|
+
["--hairline-mid" /* Mid */]: colors.textTertiary !== void 0 ? colors.textTertiary : colors.border,
|
|
51
|
+
["--hairline-lo" /* Lo */]: colors.divider
|
|
41
52
|
});
|
|
42
53
|
var ORIGIN_WHITELIST = ["about:blank"];
|
|
43
54
|
var styles = StyleSheet.create({
|
|
@@ -51,23 +62,43 @@ var HairlineFigure = ({
|
|
|
51
62
|
label,
|
|
52
63
|
testID = HAIRLINE_TEST_ID
|
|
53
64
|
}) => {
|
|
65
|
+
const webRef = useRef(null);
|
|
66
|
+
const latestOptions = useRef({ intensity, label });
|
|
67
|
+
latestOptions.current = { intensity, label };
|
|
54
68
|
const html = useMemo(
|
|
55
|
-
() => buildHairlineHtml({ bundle: HAIRLINE_BUNDLE, figure, vars: hairlineVars(colors),
|
|
56
|
-
[figure, colors
|
|
69
|
+
() => buildHairlineHtml({ bundle: HAIRLINE_BUNDLE, figure, vars: hairlineVars(colors), ...latestOptions.current }),
|
|
70
|
+
[figure, colors]
|
|
57
71
|
);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
webRef.current?.injectJavaScript(buildHairlineUpdateScript({ intensity, label }));
|
|
74
|
+
}, [intensity, label]);
|
|
58
75
|
const plate = useMemo(() => ({ backgroundColor: colors.background }), [colors.background]);
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
const isLabelled = label !== void 0 && label !== "";
|
|
77
|
+
return /* @__PURE__ */ jsx(
|
|
78
|
+
View,
|
|
61
79
|
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
style: [styles.
|
|
68
|
-
|
|
80
|
+
accessible: isLabelled,
|
|
81
|
+
accessibilityRole: isLabelled ? "image" : void 0,
|
|
82
|
+
accessibilityLabel: isLabelled ? label : void 0,
|
|
83
|
+
accessibilityElementsHidden: !isLabelled,
|
|
84
|
+
importantForAccessibility: isLabelled ? "yes" : "no-hide-descendants",
|
|
85
|
+
style: [styles.host, plate],
|
|
86
|
+
testID,
|
|
87
|
+
children: /* @__PURE__ */ jsx(
|
|
88
|
+
WebView,
|
|
89
|
+
{
|
|
90
|
+
ref: webRef,
|
|
91
|
+
source: { html },
|
|
92
|
+
originWhitelist: ORIGIN_WHITELIST,
|
|
93
|
+
scrollEnabled: false,
|
|
94
|
+
bounces: false,
|
|
95
|
+
overScrollMode: "never",
|
|
96
|
+
style: [styles.web, plate],
|
|
97
|
+
importantForAccessibility: "no-hide-descendants"
|
|
98
|
+
}
|
|
99
|
+
)
|
|
69
100
|
}
|
|
70
|
-
)
|
|
101
|
+
);
|
|
71
102
|
};
|
|
72
103
|
|
|
73
104
|
// src/hairline/HairlineFigureName.ts
|
|
@@ -102,6 +133,6 @@ var HairlineFigureName = /* @__PURE__ */ ((HairlineFigureName2) => {
|
|
|
102
133
|
return HairlineFigureName2;
|
|
103
134
|
})(HairlineFigureName || {});
|
|
104
135
|
|
|
105
|
-
export { DEFAULT_HAIRLINE_INTENSITY, HAIRLINE_ASPECT_RATIO, HAIRLINE_TEST_ID, HairlineFigure, HairlineFigureName, hairlineVars };
|
|
136
|
+
export { DEFAULT_HAIRLINE_INTENSITY, HAIRLINE_ASPECT_RATIO, HAIRLINE_TEST_ID, HairlineFigure, HairlineFigureName, HairlineVarName, hairlineVars };
|
|
106
137
|
//# sourceMappingURL=native.mjs.map
|
|
107
138
|
//# sourceMappingURL=native.mjs.map
|