@vibecook/ice 0.14.0 → 0.15.0-desk.2
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/dist/{chunk-ZPUPBULB.js → chunk-52GMQ2BN.js} +1320 -450
- package/dist/chunk-52GMQ2BN.js.map +1 -0
- package/dist/{chunk-BA7XJC27.js → chunk-K4HUZAYJ.js} +49 -34
- package/dist/chunk-K4HUZAYJ.js.map +1 -0
- package/dist/{chunk-DYK6KNLG.js → chunk-PFJN3XN2.js} +39 -14
- package/dist/chunk-PFJN3XN2.js.map +1 -0
- package/dist/{chunk-IBWWLA6D.js → chunk-U4YMA4TJ.js} +6 -5
- package/dist/chunk-U4YMA4TJ.js.map +1 -0
- package/dist/desk-kit.js +2 -2
- package/dist/desk-objects.js +15 -9
- package/dist/desk-objects.js.map +1 -1
- package/dist/desk.js +31 -3
- package/dist/devtools.js +1 -1
- package/dist/dom.js +2 -2
- package/dist/index.js +1 -1
- package/dist/react.js +9 -7
- package/dist/react.js.map +1 -1
- package/dist/types/packages/core/src/index.d.ts +1 -1
- package/dist/types/packages/core/src/interaction/install.d.ts +7 -1
- package/dist/types/packages/core/src/systems/held.d.ts +3 -1
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +16 -0
- package/dist/types/packages/core/src/systems/l2-recognize.d.ts +3 -1
- package/dist/types/packages/core/src/systems/press-button.d.ts +25 -0
- package/dist/types/packages/core/src/systems/tray.d.ts +3 -0
- package/dist/types/packages/desk/src/compose/builder.d.ts +22 -1
- package/dist/types/packages/desk/src/compose/marks.d.ts +7 -0
- package/dist/types/packages/desk/src/compose/pick.d.ts +1 -1
- package/dist/types/packages/desk/src/compose/reflector.d.ts +9 -0
- package/dist/types/packages/desk/src/engine/budget.d.ts +5 -0
- package/dist/types/packages/desk/src/faults.d.ts +38 -0
- package/dist/types/packages/desk/src/ground.d.ts +158 -9
- package/dist/types/packages/desk/src/hold/focus.d.ts +1 -1
- package/dist/types/packages/desk/src/hold/pose.d.ts +27 -7
- package/dist/types/packages/desk/src/host/index.d.ts +1 -1
- package/dist/types/packages/desk/src/host/layer.d.ts +110 -4
- package/dist/types/packages/desk/src/index.d.ts +5 -2
- package/dist/types/packages/desk/src/kind.d.ts +7 -5
- package/dist/types/packages/desk/src/kinds/world.d.ts +20 -2
- package/dist/types/packages/desk/src/kit/editor.d.ts +3 -1
- package/dist/types/packages/desk/src/kit/hold.d.ts +1 -1
- package/dist/types/packages/desk/src/missing/layout.d.ts +27 -0
- package/dist/types/packages/desk/src/missing/object.d.ts +6 -0
- package/dist/types/packages/desk/src/missing/pass.d.ts +39 -0
- package/dist/types/packages/desk/src/missing/shaders.d.ts +7 -0
- package/dist/types/packages/desk/src/missing/slots.d.ts +8 -0
- package/dist/types/packages/desk/src/shaders.gen.d.ts +5 -4
- package/dist/types/packages/desk/src/theme.d.ts +22 -0
- package/dist/types/packages/desk/src/tray/drawer.d.ts +16 -5
- package/dist/types/packages/desk/src/tray/flux.d.ts +4 -1
- package/dist/types/packages/desk/src/tray/index.d.ts +1 -1
- package/dist/types/packages/desk/src/tray/pass.d.ts +7 -2
- package/dist/types/packages/desk/src/tray/specimens.d.ts +21 -6
- package/dist/types/packages/dom/src/desk-host.d.ts +15 -2
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +6 -0
- package/dist/types/packages/objects/src/board/layout.d.ts +1 -1
- package/dist/types/packages/objects/src/shaders.gen.d.ts +1 -1
- package/dist/types/packages/react/src/selection-menu.d.ts +5 -2
- package/package.json +1 -1
- package/dist/chunk-BA7XJC27.js.map +0 -1
- package/dist/chunk-DYK6KNLG.js.map +0 -1
- package/dist/chunk-IBWWLA6D.js.map +0 -1
- package/dist/chunk-ZPUPBULB.js.map +0 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Viewport,
|
|
9
9
|
defineQuery,
|
|
10
10
|
writeRuntimeResource
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-PFJN3XN2.js";
|
|
12
12
|
import {
|
|
13
13
|
worldToScreen
|
|
14
14
|
} from "./chunk-JS6PG6YD.js";
|
|
@@ -537,6 +537,7 @@ function createDeskHost(opts) {
|
|
|
537
537
|
host,
|
|
538
538
|
world,
|
|
539
539
|
framePick: stack.framePick,
|
|
540
|
+
pickAt: stack.pickAt,
|
|
540
541
|
navGeometry: stack.navGeometry,
|
|
541
542
|
heldPose: stack.heldPose,
|
|
542
543
|
trayPose: stack.trayPose,
|
|
@@ -551,11 +552,11 @@ function createDeskHost(opts) {
|
|
|
551
552
|
host.dispose();
|
|
552
553
|
throw err;
|
|
553
554
|
}
|
|
554
|
-
const remoteCursors = createRemoteCursorsReflector(host, world);
|
|
555
|
+
const remoteCursors = layer.cursors === false ? void 0 : createRemoteCursorsReflector(host, world);
|
|
555
556
|
const unregister = [
|
|
556
557
|
core.registerReflector(layer.reflector),
|
|
557
558
|
core.registerReflector(createCursorReflector(host, stack.readCursor)),
|
|
558
|
-
core.registerReflector(remoteCursors.reflector)
|
|
559
|
+
...remoteCursors !== void 0 ? [core.registerReflector(remoteCursors.reflector)] : []
|
|
559
560
|
];
|
|
560
561
|
const motionQuery = container.ownerDocument.defaultView?.matchMedia("(prefers-reduced-motion: reduce)");
|
|
561
562
|
const syncReducedMotion = () => {
|
|
@@ -605,7 +606,7 @@ function createDeskHost(opts) {
|
|
|
605
606
|
motionQuery?.removeEventListener("change", syncReducedMotion);
|
|
606
607
|
engine.transitions.setReducedMotion(false);
|
|
607
608
|
for (const unreg of unregister) unreg();
|
|
608
|
-
remoteCursors
|
|
609
|
+
remoteCursors?.destroy();
|
|
609
610
|
layer.dispose();
|
|
610
611
|
host.dispose();
|
|
611
612
|
}
|
|
@@ -631,4 +632,4 @@ export {
|
|
|
631
632
|
createDeskHost,
|
|
632
633
|
DOM_VERSION
|
|
633
634
|
};
|
|
634
|
-
//# sourceMappingURL=chunk-
|
|
635
|
+
//# sourceMappingURL=chunk-U4YMA4TJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../dom/src/host.ts","../../dom/src/loop.ts","../../dom/src/input-ownership.ts","../../dom/src/pointer-adapter.ts","../../dom/src/reflectors/cursor.ts","../../dom/src/reflectors/remote-cursors.ts","../../dom/src/widget-focus.ts","../../dom/src/desk-host.ts","../../dom/src/index.ts"],"sourcesContent":["/**\n * The canvas host (design-004 §1, host pipeline; SCREEN SPACE ONLY since design-015 D5b).\n *\n * The container is styled to be a stable, gesture-clean viewport: `relative` so\n * absolutely-positioned children (the desk's canvas, the one focused editor, the\n * remote-cursor plane, the app's screen-space chrome) anchor to it,\n * `overflow:hidden` to clip them, `touch-action:none` + `user-select:none` so\n * browser scroll/zoom and text selection never fight the interaction stack.\n *\n * THE CONTENT PLANE IS GONE (design-015 §2 law 2). Until D5b the host also made\n * one camera-transformed `<div>` — M3's \"content plane\", the P1 every DOM\n * widget mounted in, carrying the camera as ONE CSS transform written by the\n * plane-transform reflector (kernel `planeCssTransform`, gone since D7). No DOM element\n * carries a camera transform any more: everything under the camera is the\n * desk renderer's, and the DOM that remains is screen-space.\n */\n\nexport interface CanvasHost {\n /** The app-provided viewport element (styled, not created, by the host). */\n readonly container: HTMLElement;\n /** Clear the inline styles the host wrote. */\n dispose(): void;\n}\n\n/** The container styles the host owns unconditionally (cleared on dispose). */\nconst CONTAINER_STYLE: Readonly<Record<string, string>> = {\n overflow: \"hidden\",\n touchAction: \"none\",\n userSelect: \"none\",\n};\n\nexport function createCanvasHost(container: HTMLElement): CanvasHost {\n // The children need the container to be a POSITIONED containing block — but\n // `absolute`/`fixed` already qualify, so only promote a `static` container\n // to `relative`. Stomping an app's `position: absolute` with inline\n // `relative` collapses the common `#app { position: absolute; inset: 0 }`\n // sizing pattern to zero height (inset offsets a relative box, it does not\n // size it), and the host's own overflow:hidden then clips everything.\n // Inline style first (authoritative and environment-independent), computed\n // second (catches stylesheet rules like `#app { position: absolute }`).\n const view = container.ownerDocument.defaultView;\n const position =\n container.style.position !== \"\"\n ? container.style.position\n : (view?.getComputedStyle(container).position ?? \"\");\n const promoteToRelative = position === \"\" || position === \"static\";\n if (promoteToRelative) container.style.position = \"relative\";\n // Record each host-owned property's prior inline value BEFORE overwriting it,\n // so dispose restores what the caller set (e.g. an inline overflow:auto) rather\n // than blanket-clearing it. Empty string = the caller had no inline value here.\n const priorContainerStyle = Object.keys(CONTAINER_STYLE).map((prop) => {\n // camelCase → kebab-case for the CSSOM property API (touchAction → touch-action).\n const cssName = prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);\n return [cssName, container.style.getPropertyValue(cssName)] as const;\n });\n Object.assign(container.style, CONTAINER_STYLE);\n\n return {\n container,\n dispose() {\n if (promoteToRelative) container.style.removeProperty(\"position\");\n for (const [cssName, prior] of priorContainerStyle) {\n // Restore the caller's prior inline value, or clear the host's if the\n // caller had none.\n if (prior !== \"\") container.style.setProperty(cssName, prior);\n else container.style.removeProperty(cssName);\n }\n },\n };\n}\n","/**\n * The rAF frame loop (design-002 §1: core is scheduler-free — the DOM package\n * owns the loop; core never touches the platform).\n *\n * Drives `engine.step(now)` once per animation frame, threading the rAF\n * timestamp straight into the frame clock (so FrameInfo.dt is real wall time,\n * clamped in core). The returned stop fn cancels the pending frame AND latches\n * so a frame already in flight cannot re-schedule — calling it is idempotent.\n *\n * THE FREEZE GATE (2026-08-04). Before each frame the loop claims a step from\n * `engine.frame`. While a freeze is held the gate walks its settle and then\n * refuses, at which point the loop PARKS: it stops scheduling rAF at all\n * rather than spinning a callback that does nothing — \"the engine is stopped\"\n * should mean the browser has no work queued for it either. Thaw arrives\n * through the gate's change subscription, which schedules the next frame.\n *\n * The dt clamp is what makes resume safe: a freeze that lasted an hour hands\n * `step` an enormous `now`, and core clamps the delta (frame-info.ts), so\n * tweens advance by one ordinary frame instead of teleporting to their end.\n *\n * THE LOOP OUTLIVES A THROWING STEP (2026-08-15, petition I14). Until this fix\n * the reschedule sat on the line AFTER `engine.step(now)`, so ONE throw from a\n * publish hook (which propagate loudly by design — engine.ts) escaped the rAF\n * callback, left `handle` un-renewed, and killed the canvas PERMANENTLY: with\n * `parked` still false, `wake` early-returns forever, so even freeze/thaw could\n * not restart it. Recovery needed a fresh `startRafLoop`. The next frame is now\n * scheduled in a `finally`, so a faulting frame is loud but survivable — the\n * throw still reaches the browser's error handler; only the death is gone.\n *\n * BEFORE EACH STEP (2026-09-27, ICE M21 K1): an optional `beforeStep` — where a host reads what the\n * platform changes without an event it can hear (the device's ratio: `createDeskHost`). It runs\n * only for a step that runs (never while parked), and inside the same `try`, so a throwing hook is\n * as survivable as a throwing step.\n *\n * THE SLEEP (2026-09-27, ICE M21 K7a — `@ice/core`'s frame-control.ts): with `sleep`, the loop asks\n * the frame gate after every step when the next one is due, and SLEEPS when it is not the next\n * frame — no rAF queued, and a timer only when a registered wake names a time (a flight, a caret's\n * blink, a layer let go). A wake (an input, a write from outside a step, a document's or a room's\n * arrival, a desk's flux) restarts it in the next frame; so does a freeze, whose settle walk needs\n * steps. A desk at rest then costs the main thread nothing at all (design-015 §11.4). Without\n * `sleep` the loop steps every frame, as it always did.\n */\nimport type { Engine } from \"@ice/core\";\n\nexport interface RafLoopOptions {\n /** Sleep when the engine is quiet (K7a): no frame scheduled until a wake or a registered time. Default false. */\n readonly sleep?: boolean;\n}\n\nexport function startRafLoop(engine: Engine, beforeStep?: (now: number) => void, opts: RafLoopOptions = {}): () => void {\n const sleepy = opts.sleep === true;\n const frame = engine.frame;\n let handle = 0;\n let stopped = false;\n let parked = false;\n let asleep = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const tick = (now: number): void => {\n if (stopped) return;\n if (!frame.claimStep()) {\n // Park: no step, and deliberately no reschedule. `wake` owns the restart.\n parked = true;\n handle = 0;\n return;\n }\n try {\n beforeStep?.(now);\n engine.step(now);\n } finally {\n // Re-checked AFTER the body: a `stop()` called from inside the step\n // (a publish hook, a reflector) must still win, or the loop would\n // resurrect itself past its own stopper.\n if (!stopped) schedule();\n }\n };\n\n /** After a step: the next frame, or sleep — until a wake, or the soonest registered time. */\n const schedule = (): void => {\n if (!sleepy) {\n handle = requestAnimationFrame(tick);\n return;\n }\n const t = performance.now();\n const due = frame.nextStep(t);\n if (due <= t) {\n handle = requestAnimationFrame(tick);\n return;\n }\n handle = 0;\n asleep = true;\n frame.sleeping(true);\n if (due !== Number.POSITIVE_INFINITY) timer = setTimeout(() => rise(true), Math.max(0, due - t));\n };\n\n /** Out of the sleep: the next frame steps (`timed`: for a registered time alone). */\n const rise = (timed: boolean): void => {\n if (stopped || !asleep) return;\n asleep = false;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n frame.sleeping(false, timed);\n handle = requestAnimationFrame(tick);\n };\n\n // (a freeze taken while the loop sleeps rises it through the world's doors — the freeze writes its `FrameMode` mirror — so its\n // settle walk has the steps it needs; the sleep test pins it)\n const wake = (): void => {\n if (stopped || !parked || frame.isFrozen()) return;\n parked = false;\n handle = requestAnimationFrame(tick);\n };\n const unsubscribe = frame.onChange(wake);\n const unsubscribeWake = sleepy ? frame.onWake(() => rise(false)) : undefined;\n\n handle = requestAnimationFrame(tick);\n\n return () => {\n stopped = true;\n unsubscribe();\n unsubscribeWake?.();\n if (timer !== undefined) clearTimeout(timer);\n if (asleep) frame.sleeping(false);\n cancelAnimationFrame(handle);\n };\n}\n","/**\n * Input-ownership predicates (design-007 §4 — one predicate FAMILY, not one\n * predicate; petitions I1/I4). \"Does the widget own input here\" is answered\n * per-surface at the right scope, sharing the DOM-walk mechanics:\n *\n * - `isEditableTarget` — the 4-class editable gate (keymap default + the\n * adapter's Space-types fix). Kept narrow so UNDECLARED widgets behave\n * exactly as before (design-007 F5).\n * - `keyboardClaimOf` — the `data-canvas-keyboard` claim marker written by the\n * dom-widgets reflector for `interaction.keyboard: \"exclusive\"` widgets.\n * Read UNBOUNDED (page-global) on purpose: `document.activeElement` is one\n * per page, so engine B's keymap must stand down while you type into engine\n * A's claiming widget (design-007 F2 — only the DOM is page-global).\n * - `wheelCede` — the NARROW, DYNAMIC scroll opt-out (design-007 §3.4/§4):\n * plain wheel cedes to native scroll only when a scroller WITH ROOM in the\n * wheel's dominant axis sits inside a claim-marked / `data-canvas-\n * interactive` / editable subtree. At the scroll bounds the predicate turns\n * false and the wheel falls through to canvas pan/zoom — the familiar\n * nested-scroll feel — which also keeps scroll-chaining from ever reaching\n * the page. Deliberately NOT the full `crossesInteractive` set: hovering a\n * bare `<button>` must not kill canvas zoom (design-007 F4).\n *\n * The keymap (`@ice/react`) imports these — the two historically disagreeing\n * guards now share one implementation (the I1 unification).\n */\n\n/** The claim marker the dom-widgets reflector writes on an exclusive widget's host. */\nexport const KEYBOARD_CLAIM_ATTR = \"data-canvas-keyboard\";\n\n/** Marker value for `keyboardEscape: \"widget\"` (the widget receives even Escape). */\nexport const CLAIM_OWNS_ESCAPE = \"escape\";\n\nexport interface KeyboardClaim {\n /** The event-target chain crosses a claim marker (⇒ engine keyboard stands down). */\n readonly claimed: boolean;\n /** The claiming widget owns Escape too (`keyboardEscape: \"widget\"`). */\n readonly ownsEscape: boolean;\n}\n\nconst NO_CLAIM: KeyboardClaim = { claimed: false, ownsEscape: false };\n\n/** The keymap's editable gate — exactly INPUT/TEXTAREA/SELECT/contentEditable. */\nexport function isEditableTarget(target: EventTarget | null): boolean {\n if (target === null || !(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || target.isContentEditable;\n}\n\n/**\n * Resolve the keyboard claim for an event target (keydown targets ARE\n * `document.activeElement`, so this reads browser focus — the single source of\n * truth, design-007 §2.1). Unbounded `closest` by design (see header).\n */\nexport function keyboardClaimOf(target: EventTarget | null): KeyboardClaim {\n if (!(target instanceof Element)) return NO_CLAIM;\n const marked = target.closest(`[${KEYBOARD_CLAIM_ATTR}]`);\n if (marked === null) return NO_CLAIM;\n return { claimed: true, ownsEscape: marked.getAttribute(KEYBOARD_CLAIM_ATTR) === CLAIM_OWNS_ESCAPE };\n}\n\n/**\n * The scroll-opt-out subtree roots (design-007 §4 `crossesScrollable` — the\n * narrow set): the claim marker, the explicit interactive opt-in, and real\n * editable scrollers. NOT bare button/a/media.\n */\nconst SCROLL_OPT_OUT_SELECTOR =\n `[${KEYBOARD_CLAIM_ATTR}], [data-canvas-interactive], input, textarea, select, [contenteditable=\"\"], [contenteditable=\"true\"]`;\n\n/** Sub-pixel tolerance for fractional scroll positions. */\nconst SCROLL_EPS = 1;\n\n/** The element can consume this wheel's dominant-axis delta RIGHT NOW (overflow + room). */\nfunction hasScrollRoom(el: HTMLElement, dx: number, dy: number): boolean {\n const style = el.ownerDocument.defaultView?.getComputedStyle(el);\n if (style === undefined || style === null) return false;\n if (Math.abs(dy) >= Math.abs(dx)) {\n if (dy === 0) return false;\n const oy = style.overflowY;\n if (oy !== \"auto\" && oy !== \"scroll\") return false;\n if (el.scrollHeight <= el.clientHeight + SCROLL_EPS) return false;\n return dy > 0 ? el.scrollTop + el.clientHeight < el.scrollHeight - SCROLL_EPS : el.scrollTop > SCROLL_EPS;\n }\n if (dx === 0) return false;\n const ox = style.overflowX;\n if (ox !== \"auto\" && ox !== \"scroll\") return false;\n if (el.scrollWidth <= el.clientWidth + SCROLL_EPS) return false;\n return dx > 0 ? el.scrollLeft + el.clientWidth < el.scrollWidth - SCROLL_EPS : el.scrollLeft > SCROLL_EPS;\n}\n\n/**\n * Should this plain (non-pinch) wheel be ceded to native widget scroll?\n * Walks target → container: remembers any scroller-with-room, cedes on the\n * first opt-out root at or above it. Walking up meets scrollers BEFORE their\n * widget's marker (the marker sits on the host), so \"a scroller inside an\n * opt-out subtree\" is exactly \"saw a scroller by the time the marker matches\".\n * ctrl/pinch wheels never reach this — they are ALWAYS the canvas's zoom.\n */\nexport function wheelCede(\n target: EventTarget | null,\n container: HTMLElement,\n dx: number,\n dy: number,\n): boolean {\n if (!(target instanceof Element)) return false;\n if (dx === 0 && dy === 0) return false;\n let sawScrollableRoom = false;\n for (let el: Element | null = target; el !== null && el !== container; el = el.parentElement) {\n if (el instanceof HTMLElement && hasScrollRoom(el, dx, dy)) sawScrollableRoom = true;\n if (sawScrollableRoom && el.matches(SCROLL_OPT_OUT_SELECTOR)) return true;\n }\n return false;\n}\n","/**\n * The real L0 pointer adapter (design-003 §2–§3; replaces the M3 demo-input scaffold).\n *\n * Translates DOM pointer / wheel / keyboard events into normalized `InputEvent`s\n * and ONLY enqueues them (Law 2: adapters enqueue, they never touch the world —\n * this file imports no ECS state and writes nothing). `pointerIngest` drains the\n * queue once per tick and turns the facts into world state.\n *\n * Details that matter:\n * - stable pointer ids: `\"mouse\"` for the one mouse, `\"touch:<pointerId>\"` per\n * contact, `\"pen\"` — ingest keys pointer entities off these (design-003 §2);\n * - container-relative CSS px (the kernel screen space) via getBoundingClientRect;\n * - the button: every pointer fact carries PointerEvent `buttons`, and a down's\n * mask IS the button that pressed (a second button joins a held press as a\n * pointermove, never a down). Which press is a gesture is the stack's call,\n * not this file's — only the primary's (petition I28, core press-button.ts):\n * a secondary press is enqueued like any other, a point. `contextmenu` is\n * never listened to here — it is the host's, untouched;\n * - `setPointerCapture` on down so a drag keeps delivering outside the element;\n * - Space is the pan modifier (design-003 §4.4): tracked here and threaded onto\n * every event's `mods.space`; keydown/keyup emit a `key` fact only when the\n * modifier tuple actually changes. Space's preventDefault/latch is OWNERSHIP-\n * AWARE (design-007 §3.4, petition I1): into an editable it neither latches\n * nor preventDefaults (Space must TYPE — the §1.6 live-bug fix); into a\n * `data-canvas-keyboard` claim it cedes the pan SEMANTICS but keeps killing\n * the page-scroll default (the key still reaches the widget's own listener —\n * preventDefault is not stopPropagation); elsewhere it pans, as ever;\n * - wheel: ctrl-wheel / trackpad pinch is ALWAYS `preventDefault` + the canvas\n * zoom signal (`wheel.pinch`) — a browser page-zoom over a widget is\n * catastrophic, claimed or not. A PLAIN wheel over a scroller-with-room\n * inside a claim/`data-canvas-interactive`/editable subtree (`wheelCede`,\n * design-007 §3.4/§3.5) is ceded: NO preventDefault (native scroll\n * proceeds), the fact still lands flagged `wheelHandled` so ingest stamps\n * the one-tick `WheelHandled` and the wheel recognizers skip the tick. At\n * the scroll bounds the predicate flips and the wheel falls through to\n * canvas pan/zoom. Everything else: `preventDefault`, dx/dy (deltaMode\n * lines → px ×16) — unchanged;\n * - window blur enqueues a synthetic `cancel` for every pointer still down\n * (design-003 §8) — the browser will not send the pointerups.\n *\n * Widget opt-out (the pinned widget-event contract, design-002 §8 / design-004 §4):\n * on `pointerdown` ONLY, if the event's target chain (up to the container) crosses\n * a native interactive (input/textarea/select/button/a[href]/contenteditable/\n * media[controls]) or an explicit `[data-canvas-interactive]`, the down fact is\n * flagged `surfaceHandled: true` — the fact still lands (the one input path is\n * absolute), but recognizer-spawn/arbitration skip it (`HandledByWidget` flows\n * through L0). `stopPropagation()` inside widget content is the OTHER boundary:\n * a stopped event never reaches the container listener, so it never becomes a\n * canvas fact at all — that is the design, not a gap.\n *\n * Hover-time amendment (design-002 §8, 2026-07-18): down/move facts ALSO carry\n * `overInteractive` — the same `crossesInteractive` check, run at hover time —\n * so the world can telegraph the opt-out BEFORE a down (cursor affordances).\n * (Until design-015 D5b the GL router's hover verdict was OR'd in; the GL\n * route left with the islands it picked.) It never gates gestures:\n * `surfaceHandled` stays down-only and keeps its exact meaning. During a\n * container-captured drag every event retargets to the container, so\n * `overInteractive` reads false mid-gesture — correct: the gesture owns the\n * pointer, not the widget under it.\n */\nimport { NO_MODS, type InputEvent, type InputMods, type InputQueue } from \"@ice/core\";\nimport type { CanvasHost } from \"./host\";\nimport { isEditableTarget, keyboardClaimOf, wheelCede } from \"./input-ownership\";\n\n/** DOM_DELTA_LINE → px (one wheel \"line\" ≈ 16px; matches typical browser mapping). */\nconst WHEEL_LINE_PX = 16;\n\n/**\n * Elements whose `pointerdown` is the widget's, not the canvas's (design-002 §8).\n * `contenteditable=\"\"` (bare attribute) and `=\"true\"` are editable; `=\"false\"` is\n * not. Media only opts out WITH visible controls.\n */\nconst INTERACTIVE_SELECTOR =\n 'input, textarea, select, button, a[href], [contenteditable=\"\"], [contenteditable=\"true\"], audio[controls], video[controls], [data-canvas-interactive]';\n\n/** True if `target`'s ancestor chain, up to but excluding `container`, holds an interactive surface. */\nfunction crossesInteractive(target: EventTarget | null, container: HTMLElement): boolean {\n if (!(target instanceof Element)) return false;\n const match = target.closest(INTERACTIVE_SELECTOR);\n // `closest` walks to the document root; bound it to inside the canvas so an\n // interactive ANCESTOR of the container (unusual, but legal) does not count.\n return match !== null && container.contains(match);\n}\n\nfunction pointerIdOf(e: PointerEvent): string {\n if (e.pointerType === \"touch\") return `touch:${e.pointerId}`;\n if (e.pointerType === \"pen\") return \"pen\";\n return \"mouse\";\n}\n\nfunction deviceOf(e: PointerEvent): InputEvent[\"device\"] {\n if (e.pointerType === \"touch\") return \"touch\";\n if (e.pointerType === \"pen\") return \"pen\";\n return \"mouse\";\n}\n\nexport function attachPointerAdapter(host: CanvasHost, queue: InputQueue): () => void {\n const { container } = host;\n const view = container.ownerDocument.defaultView;\n\n let spaceHeld = false;\n // Pointers currently down — blur-cancel sweep + DEFERRED-CAPTURE state.\n // Capture timing (field report 2026-07-12, v1's click-vs-drag discrimination):\n // capturing on DOWN retargets the derived `click`/`dblclick` to the container,\n // killing every widget-body handler (todo rows, folder double-click). So:\n // - GL-claimed downs capture IMMEDIATELY (the router synthesizes its own\n // clicks; island drags must keep delivering outside the element);\n // - native-interactive downs NEVER capture (the engine ignores them);\n // - everything else captures on the FIRST held-button move past a 4px slop\n // — below every recognizer dead zone, so a drag is always captured\n // before it can activate, while a clean tap never captures at all.\n const live = new Map<\n string,\n {\n device: InputEvent[\"device\"];\n x: number;\n y: number;\n downX: number;\n downY: number;\n native: boolean;\n captured: boolean;\n }\n >();\n const CAPTURE_SLOP_PX = 4;\n\n const capture = (pointerId: number): void => {\n if (typeof container.setPointerCapture !== \"function\") return;\n try {\n container.setPointerCapture(pointerId);\n } catch {\n // capture is best-effort — a detached or unsupported target must not throw here.\n }\n };\n // Last emitted keyboard tuple — emit a `key` fact only when it actually changes.\n let lastMods = { shift: false, ctrl: false, alt: false, meta: false, space: false };\n\n const relative = (clientX: number, clientY: number): { x: number; y: number } => {\n const rect = container.getBoundingClientRect();\n return { x: clientX - rect.left, y: clientY - rect.top };\n };\n\n const pointerMods = (e: PointerEvent | WheelEvent): InputMods => ({\n shift: e.shiftKey,\n ctrl: e.ctrlKey,\n alt: e.altKey,\n meta: e.metaKey,\n space: spaceHeld,\n });\n\n const onPointerDown = (e: PointerEvent): void => {\n const id = pointerIdOf(e);\n const device = deviceOf(e);\n const { x, y } = relative(e.clientX, e.clientY);\n // Widget opt-out: down on a native interactive / [data-canvas-interactive]\n // flags the fact so recognizers skip it (design-002 §8).\n const native = crossesInteractive(e.target, container);\n live.set(id, { device, x, y, downX: x, downY: y, native, captured: false });\n const surfaceHandled = native;\n queue.enqueue({\n kind: \"down\",\n pointerId: id,\n device,\n screenX: x,\n screenY: y,\n buttons: e.buttons,\n mods: pointerMods(e),\n tMs: e.timeStamp,\n ...(surfaceHandled ? { surfaceHandled: true } : {}),\n // The down IS a hover-bearing fact: a touch's first event must seed the\n // pointer's OverInteractive truth (no prior moves exist to).\n overInteractive: surfaceHandled,\n });\n };\n\n const onPointerMove = (e: PointerEvent): void => {\n const id = pointerIdOf(e);\n const device = deviceOf(e);\n const { x, y } = relative(e.clientX, e.clientY);\n let seen = live.get(id);\n // ADOPTED-gesture hardening (ops.insertByDrag, 2026-07-19): a held-button\n // move for a pointer whose down we never saw means widget content started\n // the gesture and handed it to the engine (the tray tile stopPropagation'd\n // its down — the sanctioned boundary). Track it from HERE so the deferred\n // capture below still takes the pointer before it can leave the window\n // mid-drag, and the blur sweep knows to cancel it. Gated off interactive\n // targets: a move over opted-out content (text selection in a widget's\n // input, the tray panel itself) must never trigger a container capture.\n if (seen === undefined && e.buttons !== 0 && !crossesInteractive(e.target, container)) {\n seen = { device, x, y, downX: x, downY: y, native: false, captured: false };\n live.set(id, seen);\n }\n if (seen !== undefined) {\n seen.x = x;\n seen.y = y;\n // Deferred capture: a real drag is forming — take it before any\n // recognizer dead zone (>=8px) can activate.\n if (\n !seen.captured &&\n !seen.native &&\n e.buttons !== 0 &&\n Math.hypot(x - seen.downX, y - seen.downY) > CAPTURE_SLOP_PX\n ) {\n capture(e.pointerId);\n seen.captured = true;\n }\n }\n queue.enqueue({\n kind: \"move\",\n pointerId: id,\n device,\n screenX: x,\n screenY: y,\n buttons: e.buttons,\n mods: pointerMods(e),\n tMs: e.timeStamp,\n // Hover fact, every move: the DOM chain check.\n overInteractive: crossesInteractive(e.target, container),\n });\n };\n\n const endPointer =\n (kind: \"up\" | \"cancel\") =>\n (e: PointerEvent): void => {\n const id = pointerIdOf(e);\n const device = deviceOf(e);\n const { x, y } = relative(e.clientX, e.clientY);\n live.delete(id);\n if (typeof container.releasePointerCapture === \"function\") {\n try {\n container.releasePointerCapture(e.pointerId);\n } catch {\n // release is best-effort (capture may already be gone).\n }\n }\n queue.enqueue({\n kind,\n pointerId: id,\n device,\n screenX: x,\n screenY: y,\n buttons: e.buttons,\n mods: pointerMods(e),\n // No overInteractive stamp: a capture-retargeted up reads the container,\n // not the content under the pointer — the next real move re-truths it.\n });\n };\n const onPointerUp = endPointer(\"up\");\n const onPointerCancel = endPointer(\"cancel\");\n\n const onWheel = (e: WheelEvent): void => {\n const { x, y } = relative(e.clientX, e.clientY);\n const scale = e.deltaMode === 1 ? WHEEL_LINE_PX : 1; // DOM_DELTA_LINE → px\n if (e.ctrlKey) {\n // Pinch/ctrl-wheel: ALWAYS the canvas's zoom (design-007 F4) — the\n // browser's page pinch-zoom must never fire, claimed content or not.\n e.preventDefault();\n queue.enqueue({ kind: \"wheel\", pointerId: \"mouse\", device: \"mouse\", screenX: x, screenY: y, buttons: e.buttons, mods: pointerMods(e), wheel: { dx: 0, dy: 0, pinch: e.deltaY } });\n return;\n }\n const wheel = { dx: e.deltaX * scale, dy: e.deltaY * scale, pinch: 0 };\n if (wheelCede(e.target, container, e.deltaX, e.deltaY)) {\n // Ceded to widget scroll (I4): no preventDefault — native scroll consumes\n // the deltas. The fact still lands (the one input path is absolute),\n // flagged so recognizers treat the tick as wheel silence. The room-check\n // in wheelCede means an at-bounds scroller falls through to the branch\n // below — and scroll-chaining can never escape to the page.\n queue.enqueue({ kind: \"wheel\", pointerId: \"mouse\", device: \"mouse\", screenX: x, screenY: y, buttons: e.buttons, mods: pointerMods(e), wheel, wheelHandled: true });\n return;\n }\n e.preventDefault(); // own zoom/pan — never let the page scroll\n queue.enqueue({ kind: \"wheel\", pointerId: \"mouse\", device: \"mouse\", screenX: x, screenY: y, buttons: e.buttons, mods: pointerMods(e), wheel });\n };\n\n /** Emit a `key` fact iff the modifier tuple changed (design-003 §2 minimal keyboard). */\n const emitKeyIfChanged = (e: KeyboardEvent): void => {\n const mods: InputMods = { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey, meta: e.metaKey, space: spaceHeld };\n if (\n mods.shift === lastMods.shift &&\n mods.ctrl === lastMods.ctrl &&\n mods.alt === lastMods.alt &&\n mods.meta === lastMods.meta &&\n mods.space === lastMods.space\n ) {\n return;\n }\n lastMods = { ...mods };\n queue.enqueue({ kind: \"key\", pointerId: \"\", device: \"mouse\", screenX: 0, screenY: 0, buttons: 0, mods });\n };\n\n const isSpace = (e: KeyboardEvent): boolean => e.key === \" \" || e.code === \"Space\";\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (isSpace(e) && !e.defaultPrevented) {\n // Ownership-aware Space (design-007 §3.4; the keydown target IS the\n // focused node, so this reads browser focus — the one truth).\n if (isEditableTarget(e.target)) {\n // Space must TYPE into widget fields (§1.6 live-bug fix): no\n // preventDefault, no pan latch.\n } else if (keyboardClaimOf(e.target).claimed) {\n // Cede the pan SEMANTICS to the claiming widget; keep suppressing the\n // page-scroll default (§3.3 residual — preventDefault still lets the\n // key reach the widget's own keydown listener).\n e.preventDefault();\n } else {\n e.preventDefault(); // Space would otherwise page-scroll\n spaceHeld = true;\n }\n }\n emitKeyIfChanged(e);\n };\n const onKeyUp = (e: KeyboardEvent): void => {\n if (isSpace(e)) {\n if (!e.defaultPrevented && !isEditableTarget(e.target) && !keyboardClaimOf(e.target).claimed) {\n e.preventDefault();\n }\n // Clear UNCONDITIONALLY: the down may have latched over canvas and the\n // up can land after focus moved into a widget/editable — a skipped clear\n // here would strand the pan modifier held.\n spaceHeld = false;\n }\n emitKeyIfChanged(e);\n };\n\n /**\n * Drain every latched input to a clean slate (Law 2: enqueue only). Called on\n * window blur AND on detach: the browser delivers no more pointerups/keyups\n * once we blur, and detaching mid-gesture (unmounting the canvas while the\n * engine outlives it) must not strand an Active drag / held buttons / latched\n * modifiers in the world for a remount of the same engine to resurrect. Also\n * clears the deferred-capture `live` map so no per-pointer tracking survives.\n */\n const cancelAllInput = (): void => {\n // The browser will not deliver pointerups once we lose focus — cancel every\n // still-down pointer so no gesture is stranded Active (design-003 §8).\n for (const [id, s] of live) {\n queue.enqueue({ kind: \"cancel\", pointerId: id, device: s.device, screenX: s.x, screenY: s.y, buttons: 0, mods: { ...NO_MODS, space: spaceHeld } });\n }\n live.clear();\n // Modifier keyups are also lost on blur — ENQUEUE the clearing fact, or\n // the world's Keyboard resource stays latched (space-stuck: every canvas\n // drag pans forever). Clearing only local state was the bug: lastMods\n // already saying \"space up\" suppressed the next emitKeyIfChanged too.\n spaceHeld = false;\n if (lastMods.space || lastMods.shift || lastMods.ctrl || lastMods.alt || lastMods.meta) {\n lastMods = { ...NO_MODS };\n queue.enqueue({\n kind: \"key\",\n pointerId: \"\",\n device: \"mouse\",\n screenX: 0,\n screenY: 0,\n buttons: 0,\n mods: { ...NO_MODS },\n });\n }\n };\n\n const onBlur = (): void => {\n cancelAllInput();\n };\n\n container.addEventListener(\"pointerdown\", onPointerDown);\n container.addEventListener(\"pointermove\", onPointerMove);\n container.addEventListener(\"pointerup\", onPointerUp);\n container.addEventListener(\"pointercancel\", onPointerCancel);\n container.addEventListener(\"wheel\", onWheel, { passive: false });\n view?.addEventListener(\"keydown\", onKeyDown);\n view?.addEventListener(\"keyup\", onKeyUp);\n view?.addEventListener(\"blur\", onBlur);\n\n return () => {\n // Sweep first (a detach can land mid-gesture) — same clean-slate cancel as\n // blur; facts drain on the engine's next step. Then unwire the listeners.\n cancelAllInput();\n container.removeEventListener(\"pointerdown\", onPointerDown);\n container.removeEventListener(\"pointermove\", onPointerMove);\n container.removeEventListener(\"pointerup\", onPointerUp);\n container.removeEventListener(\"pointercancel\", onPointerCancel);\n container.removeEventListener(\"wheel\", onWheel);\n view?.removeEventListener(\"keydown\", onKeyDown);\n view?.removeEventListener(\"keyup\", onKeyUp);\n view?.removeEventListener(\"blur\", onBlur);\n };\n}\n","/**\n * The cursor reflector (design-003 §7 L4 cursor projection; v1 approach:\n * local cursor = OS cursor via one `style.cursor` write).\n *\n * `createCursorSync` resolves the cursor string into closure state every\n * `derive` tick with no observable ECS stamp (a plain string, not a\n * component/resource write), so there is nothing for `observe` to watch —\n * hence `always: true` with an internal last-value cache, mirroring the\n * \"cheap-overlay style\" carve-out `ReflectorDef.always` exists for. The cache\n * is what keeps the write ONE-per-change: `flush` runs every frame, but\n * `host.container.style.cursor` is only touched when `readCursor()` differs\n * from the last-applied value.\n *\n * Law 10: reflectors run post-notify, write output only, never read layout\n * or write ECS — this flush touches only `host.container.style`.\n */\nimport type { ReflectorDef, World } from \"@ice/core\";\nimport type { CanvasHost } from \"../host\";\n\nexport function createCursorReflector(\n host: CanvasHost,\n readCursor: () => string,\n): ReflectorDef & { writes(): number } {\n let writes = 0;\n let last: string | undefined;\n return {\n name: \"cursor\",\n always: true,\n flush(_world: World) {\n const next = readCursor();\n if (next === last) return;\n last = next;\n host.container.style.cursor = next;\n writes++;\n },\n writes: () => writes,\n };\n}\n","/**\n * P5 — remote cursors (design-004 §1: screen-space DOM, \"remote cursors are\n * always custom nodes\"; design-003 §7).\n *\n * Renders one pooled node per REMOTE cursor entity (`CursorVisual` kind\n * \"remote\" + world `Position`, derived by core's remote-cursors system from\n * presence peers; `Follows` names the peer whose `PresenceInfo` supplies\n * color + label). Screen-space by design: an acknowledged small-N\n * camera→position consumer (design-002 §5 rescope), recomputed on camera or\n * cursor dirt only. The plane mounts LAST in the container, above chrome;\n * `pointer-events: none` throughout — cursors are never hit-testable.\n */\nimport {\n Camera,\n CursorVisual,\n Follows,\n Position,\n PresenceInfo,\n defineQuery,\n type Entity,\n type ReflectorDef,\n type World,\n} from \"@ice/core\";\nimport { worldToScreen } from \"@ice/kernel\";\nimport type { CanvasHost } from \"../host\";\n\nconst remoteCursorQ = defineQuery([Position, CursorVisual]);\n\nexport interface RemoteCursorsReflector {\n readonly reflector: ReflectorDef;\n /** Remove the plane + pool (doc close / HMR). */\n destroy(): void;\n}\n\nexport function createRemoteCursorsReflector(host: CanvasHost, world: World): RemoteCursorsReflector {\n const doc = host.container.ownerDocument;\n const plane = doc.createElement(\"div\");\n Object.assign(plane.style, {\n position: \"absolute\",\n inset: \"0\",\n overflow: \"hidden\",\n pointerEvents: \"none\",\n } as Partial<CSSStyleDeclaration>);\n host.container.appendChild(plane); // last child ⇒ topmost (P5 over P4)\n\n interface Node {\n root: HTMLDivElement;\n label: HTMLDivElement;\n color: string;\n name: string;\n x: number;\n y: number;\n }\n const pool = new Map<Entity, Node>();\n\n const ensure = (e: Entity): Node => {\n let n = pool.get(e);\n if (n !== undefined) return n;\n const root = doc.createElement(\"div\");\n Object.assign(root.style, {\n position: \"absolute\",\n left: \"0\",\n top: \"0\",\n willChange: \"transform\",\n pointerEvents: \"none\",\n } as Partial<CSSStyleDeclaration>);\n // Arrow glyph + name chip (color applied on first style pass).\n const arrow = doc.createElement(\"div\");\n Object.assign(arrow.style, {\n width: \"0\",\n height: \"0\",\n borderLeft: \"6px solid transparent\",\n borderRight: \"6px solid transparent\",\n borderBottom: \"14px solid currentColor\",\n transform: \"rotate(-32deg)\",\n } as Partial<CSSStyleDeclaration>);\n const label = doc.createElement(\"div\");\n Object.assign(label.style, {\n marginTop: \"2px\",\n marginLeft: \"8px\",\n padding: \"1px 6px\",\n borderRadius: \"8px\",\n font: \"11px system-ui, sans-serif\",\n color: \"#fff\",\n whiteSpace: \"nowrap\",\n } as Partial<CSSStyleDeclaration>);\n root.appendChild(arrow);\n root.appendChild(label);\n plane.appendChild(root);\n n = { root, label, color: \"\", name: \"\", x: Number.NaN, y: Number.NaN };\n pool.set(e, n);\n return n;\n };\n\n const reflector: ReflectorDef = {\n name: \"remoteCursors\",\n observe: {\n resources: [Camera],\n queries: [{ q: remoteCursorQ, cols: [Position, CursorVisual] }],\n },\n flush: (w) => {\n const cam = w.getResource(Camera) ?? { x: 0, y: 0, zoom: 1 };\n const seen = new Set<Entity>();\n w.query(remoteCursorQ).each((b) => {\n for (const r of b) {\n const e = b.entity(r);\n if (w.get(e, CursorVisual)?.kind !== \"remote\") continue;\n seen.add(e);\n const p = w.read(e, Position);\n const s = worldToScreen(p.x, p.y, cam);\n const n = ensure(e);\n if (n.x !== s.x || n.y !== s.y) {\n n.root.style.transform = `translate(${s.x}px, ${s.y}px)`;\n n.x = s.x;\n n.y = s.y;\n }\n const peer = w.getRelation(e, Follows);\n const info = peer !== undefined ? w.get(peer, PresenceInfo) : undefined;\n const color = info?.color ?? \"#7a8aa0\";\n const name = info?.name ?? \"\";\n if (n.color !== color) {\n n.root.style.color = color;\n n.label.style.background = color;\n n.color = color;\n }\n if (n.name !== name) {\n n.label.textContent = name;\n n.label.style.display = name === \"\" ? \"none\" : \"block\";\n n.name = name;\n }\n }\n });\n for (const [e, n] of pool) {\n if (!seen.has(e)) {\n n.root.remove();\n pool.delete(e);\n }\n }\n },\n };\n\n return {\n reflector,\n destroy() {\n pool.clear();\n plane.remove();\n },\n };\n}\n","/**\n * The focus driver — acquisition + programmatic focus for keyboard-claiming\n * widgets (design-007 §2.3–§2.5, petition I1).\n *\n * The single source of truth is BROWSER focus: `document.activeElement` inside\n * a `data-canvas-keyboard` subtree is what makes every engine keyboard surface\n * stand down (input-ownership.ts). This driver's whole job is to make that\n * state reachable — it drives `.focus()`, it never mirrors focus into the\n * world (there is no Focus resource; design-007 §2.2 demoted it, and despawn\n * integrity is free: removing the node resets `activeElement`).\n *\n * Acquisition is DOM-at-event-time, not a world system: the browser's own event\n * targeting IS the pick (the down target sits inside the claiming host — since\n * design-015 D5b that host is screen-space chrome, never a widget under the\n * camera; the desk's one focused editor acquires focus on its own, the same\n * principle). A capture-phase listener keeps ordering deterministic across\n * browsers (Safari's click-focus quirks):\n *\n * - down on a natively-focusable node inside the claim (textarea, canvas\n * with tabindex, the declared proxy itself) → the browser's own mousedown\n * focus is correct; the driver does nothing.\n * - down anywhere else inside the claim → the driver focuses the claim's\n * declared `[data-canvas-focus]` proxy when one exists, else the host\n * (which carries `tabindex=\"-1\"`), with `preventScroll` — and\n * `preventDefault()`s the pointerdown so the native focus fixup cannot\n * fight the choice. Canceling a pointerdown suppresses only the compat\n * mouse events (focus fixup + text selection — the container is already\n * `user-select:none`); `click` still derives and the adapter's container\n * listener has already enqueued the down fact (one input path intact).\n * - down outside any claim → untouched; the browser blurs to body/other\n * content on its own (click-away release, design-007 §2.4).\n *\n * Release stays where the design put it: Escape in the keymap (blur), lifecycle\n * via node removal, window blur deliberately does NOT force-blur (the browser\n * restores focus on refocus — adapter's cancelAllInput keeps its hands off).\n */\nimport type { Entity } from \"@ice/core\";\nimport type { CanvasHost } from \"./host\";\nimport { KEYBOARD_CLAIM_ATTR } from \"./input-ownership\";\n\n/** The widget-declared focus target (a proxy descendant): `<div data-canvas-focus tabIndex={-1}>`. */\nexport const FOCUS_PROXY_ATTR = \"data-canvas-focus\";\n\n/**\n * Natively click-focusable content — where the browser's own mousedown focus\n * already lands inside the widget (the driver must not override it).\n */\nconst FOCUSABLE_SELECTOR =\n 'input, textarea, select, button, a[href], [contenteditable=\"\"], [contenteditable=\"true\"], [tabindex], audio[controls], video[controls], iframe';\n\n/**\n * Resolves an entity to a claiming host's content element — what `focusWidget` walks from. The\n * dom-widgets reflector was the one lookup until design-015 D5b; a host that keeps per-entity\n * DOM (none in the engine today) may still hand one in.\n */\nexport interface FocusHostLookup {\n hostFor(entity: Entity): HTMLElement | undefined;\n}\n\nexport interface WidgetFocusHandle {\n /**\n * Programmatically focus a claiming widget (its proxy, else its host).\n * Returns false when the entity has no host in the lookup (always, without\n * one) or no keyboard claim — only `keyboard: \"exclusive\"` widgets are\n * focusable (declaration drives focusability; design-007 §3.1).\n */\n focusWidget(entity: Entity): boolean;\n /** Blur whatever claim currently holds focus. False when none does. */\n blurFocus(): boolean;\n /** Remove the acquisition listener. */\n detach(): void;\n}\n\n/** The claiming host's focus node: the declared proxy, else the (tabindexed) host. */\nfunction focusNodeOf(claimHost: HTMLElement): HTMLElement {\n const proxy = claimHost.querySelector<HTMLElement>(`[${FOCUS_PROXY_ATTR}]`);\n return proxy ?? claimHost;\n}\n\nexport function attachWidgetFocus(host: CanvasHost, lookup?: FocusHostLookup): WidgetFocusHandle {\n const { container } = host;\n const doc = container.ownerDocument;\n\n const onPointerDown = (e: PointerEvent): void => {\n if (!(e.target instanceof Element)) return;\n const marked = e.target.closest(`[${KEYBOARD_CLAIM_ATTR}]`);\n if (!(marked instanceof HTMLElement) || !container.contains(marked)) return;\n // A natively-focusable node under the pointer (deeper than the host)\n // takes browser focus on its own — the common editable/terminal case.\n const focusable = e.target.closest(FOCUSABLE_SELECTOR);\n if (focusable !== null && focusable !== marked && marked.contains(focusable)) return;\n const node = focusNodeOf(marked);\n if (doc.activeElement === node) return; // already held — never disturb mid-gesture\n e.preventDefault(); // suppress the native focus fixup; the driver owns the choice\n node.focus({ preventScroll: true });\n };\n\n // Capture phase: runs before widget content's own handlers and before the\n // adapter's bubble listener — deterministic acquisition, no races.\n container.addEventListener(\"pointerdown\", onPointerDown, true);\n\n return {\n focusWidget(entity) {\n const content = lookup?.hostFor(entity);\n const hostEl = content?.parentElement;\n if (!(hostEl instanceof HTMLElement) || !hostEl.hasAttribute(KEYBOARD_CLAIM_ATTR)) return false;\n const node = focusNodeOf(hostEl);\n node.focus({ preventScroll: true });\n return doc.activeElement === node;\n },\n blurFocus() {\n const active = doc.activeElement;\n // SVG focus targets included — same rule as the keymap's Escape release.\n if (!(active instanceof HTMLElement || active instanceof SVGElement)) return false;\n if (active.closest(`[${KEYBOARD_CLAIM_ATTR}]`) === null) return false;\n active.blur();\n return true;\n },\n detach() {\n container.removeEventListener(\"pointerdown\", onPointerDown, true);\n },\n };\n}\n","/**\n * `createDeskHost` — the vanilla mount (design-015 §3, D-D15; D5b). What `<InfiniteCanvas>` did\n * for the hybrid, with the hybrid gone: ONE container, the desk's layer in it, and this package's\n * screen-space half around it — the pointer adapter (L0's one producer), the rAF loop, the OS\n * cursor, the room's other people (the remote cursors), the focus driver, the viewport sync.\n * `@ice/react`'s `<Desk>` wraps it; an app without React mounts it directly.\n *\n * THE DESK ARRIVES AS AN OPAQUE LAYER FACTORY. This package never imports `@ice/desk` (the wall\n * `nobody-imports-desk`): the factory's context and its handle are typed STRUCTURALLY here —\n * exactly as `<InfiniteCanvas ground={groundCompose(…)}>` received the ground — and\n * `@ice/desk`'s `deskLayer(opts)` (its src/host/) returns a function assignable to {@link LayerFactory}.\n * The context carries the interaction stack's seams the renderer fills (the frame pick, the nav\n * geometry, the held pose — design-014 B3b, design-015 §8–§9), the marquee buffer it draws, and the\n * stack's exact pick, which the layer's handle answers a host's screen-point pick with (petition I27).\n *\n * Boot order (registration order = reflector flush order): host → layer → reflectors [ layer ·\n * cursor · remoteCursors — unless the layer's handle says `cursors: false` (petition I26: its host\n * draws the room's people itself) ] → pointer adapter → focus → viewport (one layout read, then a\n * ResizeObserver, and the device's ratio read before every step) → rAF loop. `dispose` undoes it in reverse. The engine is NOT owned here — it\n * outlives the mount (the app disposes it). A layer factory that throws leaves nothing claimed on\n * the engine: it runs before any registration, and only the local host is undone.\n */\nimport {\n Viewport,\n writeRuntimeResource,\n type CanvasEngine,\n type EngineGpu,\n type InteractionStack,\n type ReflectorDef,\n type World,\n} from \"@ice/core\";\nimport { createCanvasHost, type CanvasHost } from \"./host\";\nimport { startRafLoop } from \"./loop\";\nimport { attachPointerAdapter } from \"./pointer-adapter\";\nimport { createCursorReflector } from \"./reflectors/cursor\";\nimport { createRemoteCursorsReflector } from \"./reflectors/remote-cursors\";\nimport { attachWidgetFocus, type WidgetFocusHandle } from \"./widget-focus\";\n\n/**\n * What the host needs of a mounted layer: its drawing reflector and its teardown. The desk's own\n * handle (`@ice/desk`'s `DeskLayerHandle`) carries far more; an app keeps that through its\n * own factory wrapper, or reads it off {@link DeskHost.layer} typed as the factory returned it.\n */\nexport interface LayerHandle {\n readonly reflector: ReflectorDef & { available(): boolean };\n dispose(): void;\n /**\n * The room's other people (petition I26): `false` — the layer's host draws its peers itself (VibeField draws each by face, from\n * `usePresencePeers`), so this host mounts no remote cursors: no plane, no reflector. Absent or `true`, it mounts them, as ever.\n * Read once, when the factory returns; the presence session, the roster and the OS cursor are the same either way.\n */\n readonly cursors?: boolean;\n}\n\n/** The mount context a layer factory receives: the host, the world and the seams the renderer fills. */\nexport interface LayerContext {\n readonly host: CanvasHost;\n readonly world: World;\n /** The frame pick slot (design-014, B3b): the renderer sets its hit test here at mount, clears it at dispose. */\n readonly framePick: InteractionStack[\"framePick\"];\n /**\n * The stack's exact pick out of the tick (petition I27; `InteractionStack.pickAt`): what a press at a screen point (CSS px of the\n * view) would touch now, through the frame pick above — the layer answers a host's screen-point pick (`handle.pick`) with it.\n */\n readonly pickAt: InteractionStack[\"pickAt\"];\n /** The nav geometry slot (design-015 §9, D2b): the renderer's word on its containers' drawn faces. */\n readonly navGeometry: InteractionStack[\"navGeometry\"];\n /** The held pose slot (design-015 §8, D4b): where the object in hand is on screen, as drawn. */\n readonly heldPose: InteractionStack[\"heldPose\"];\n /** The tray pose slot (design-017 §4, K3): where the pegboard drawer is on screen, as drawn. */\n readonly trayPose: InteractionStack[\"trayPose\"];\n /** The presentation transition coordinator: the layer registers the `ground` plane so a nav flight can prepare. */\n readonly transitions: CanvasEngine[\"transitions\"];\n /** The engine's catalog: the object types whose kinds the layer registers. */\n readonly catalog: CanvasEngine[\"catalog\"];\n /** The marquee's preview, out of the ECS (design-003 §5.7): the vellum the marks draw (design-015 D4a). */\n readonly readMarquee: () => InteractionStack[\"marqueeBuffer\"];\n /** The one spatial index (design-015 §2.5; D6): the renderer's cull rides it with a hysteresis margin — no per-entity rect test on a pan. */\n readonly spatial: InteractionStack[\"index\"];\n /** The engine's device (`engine.compositorDevice`), when the app passed one (design-015 D7): the layer draws with it — ONE device per engine. */\n readonly gpu?: EngineGpu;\n /**\n * The frame gate's sleep (K7a, `@ice/core` frame-control.ts): the layer registers when it is next due (`wakeWhen`), wakes a\n * sleeping loop for what it hears outside a step (`wake` — an asset landing, a theme, a pin), and asks whether the step in\n * progress is a registered time's alone (`settled` — then it ticks only what is due).\n */\n readonly frame?: Pick<CanvasEngine[\"engine\"][\"frame\"], \"wake\" | \"wakeWhen\" | \"settled\">;\n}\n\nexport type LayerFactory<H extends LayerHandle = LayerHandle> = (ctx: LayerContext) => H;\n\nexport interface DeskHostOptions<H extends LayerHandle = LayerHandle> {\n /** The viewport element; styled, not created, by the host. */\n readonly container: HTMLElement;\n /** A constructed engine (`createCanvasEngine(...)`); NOT disposed by the host. */\n readonly engine: CanvasEngine;\n /** The desk — `deskLayer({ … })` from `@vibecook/ice/desk` (`@ice/desk`'s src/host/), received opaquely. */\n readonly layer: LayerFactory<H>;\n /**\n * The loop SLEEPS when the engine is quiet (K7a — design-015 §11.4: a desk at rest costs the main thread nothing): no frame\n * until an input, a write, an arrival or a registered time wakes it. Default true; false steps every frame, as before K7a.\n */\n readonly sleep?: boolean;\n}\n\nexport interface DeskHost<H extends LayerHandle = LayerHandle> {\n readonly engine: CanvasEngine;\n readonly host: CanvasHost;\n /** The mounted layer's handle, as the factory returned it. */\n readonly layer: H;\n /**\n * The focus driver (design-007 §2.3): `blurFocus()` releases whatever keyboard claim holds\n * focus. Focus is VIEW state (design-007 §2.6) — it rides the mount, not `engine.ops`.\n */\n readonly focus: WidgetFocusHandle;\n /** Stop the loop, detach every adapter, unregister every reflector, dispose the layer and the host. Idempotent. */\n dispose(): void;\n}\n\nexport function createDeskHost<H extends LayerHandle>(opts: DeskHostOptions<H>): DeskHost<H> {\n const { container, engine } = opts;\n const { world, engine: core, stack } = engine;\n const host = createCanvasHost(container);\n\n let layer: H;\n try {\n layer = opts.layer({\n host,\n world,\n framePick: stack.framePick,\n pickAt: stack.pickAt,\n navGeometry: stack.navGeometry,\n heldPose: stack.heldPose,\n trayPose: stack.trayPose,\n transitions: engine.transitions,\n catalog: engine.catalog,\n readMarquee: () => stack.marqueeBuffer,\n spatial: stack.index,\n ...(engine.compositorDevice !== undefined ? { gpu: engine.compositorDevice } : {}),\n frame: core.frame,\n });\n } catch (err) {\n host.dispose();\n throw err;\n }\n\n // Registration order = flush order: the desk draws, then the OS cursor, then\n // the room's other people on top (their plane is the container's last child) —\n // none when the layer's host draws them itself (`cursors: false`, petition I26).\n const remoteCursors = layer.cursors === false ? undefined : createRemoteCursorsReflector(host, world);\n const unregister = [\n core.registerReflector(layer.reflector),\n core.registerReflector(createCursorReflector(host, stack.readCursor)),\n ...(remoteCursors !== undefined ? [core.registerReflector(remoteCursors.reflector)] : []),\n ];\n\n // The OS's reduced-motion preference, into the transition coordinator (a nav flight snaps).\n const motionQuery = container.ownerDocument.defaultView?.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const syncReducedMotion = (): void => {\n engine.transitions.setReducedMotion(motionQuery?.matches === true);\n };\n syncReducedMotion();\n motionQuery?.addEventListener(\"change\", syncReducedMotion);\n\n const detachPointer = attachPointerAdapter(host, stack.queue);\n const focus = attachWidgetFocus(host);\n\n const ratio = (): number => (typeof window !== \"undefined\" ? window.devicePixelRatio : 1);\n let synced = 0;\n const syncViewport = (): void => {\n const rect = container.getBoundingClientRect();\n synced = ratio();\n writeRuntimeResource(world, Viewport, { w: rect.width, h: rect.height, dpr: synced });\n };\n syncViewport();\n let resizeObserver: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(syncViewport);\n resizeObserver.observe(container);\n }\n\n // THE RATIO (ICE M21 K1): a change of the device's ratio alone — another display, the browser's zoom, an emulated ratio — resizes\n // nothing, so the observer never hears it and the desk kept drawing at the old ratio; an emulated one (DevTools' device mode, CDP)\n // fires not even the `(resolution)` media query (measured: `.matches` flips, no `change` over three rendered frames, no\n // device-pixel box moves). So the ratio is READ before every step — a property read — and the viewport re-synced when it moved.\n const stopLoop = startRafLoop(\n core,\n () => {\n if (ratio() !== synced) syncViewport();\n },\n { sleep: opts.sleep !== false },\n );\n // …and while the loop SLEEPS (K7a) no step reads it: the ratio is read twice a second instead — a property read, and the\n // viewport write wakes the loop through the world's doors when it moved\n const ratioPoll = opts.sleep !== false ? setInterval(() => { if (ratio() !== synced) syncViewport(); }, 500) : undefined;\n\n let disposed = false;\n return {\n engine,\n host,\n layer,\n focus,\n dispose() {\n if (disposed) return;\n disposed = true;\n stopLoop();\n if (ratioPoll !== undefined) clearInterval(ratioPoll);\n resizeObserver?.disconnect();\n focus.detach();\n detachPointer();\n motionQuery?.removeEventListener(\"change\", syncReducedMotion);\n engine.transitions.setReducedMotion(false);\n for (const unreg of unregister) unreg();\n remoteCursors?.destroy();\n layer.dispose();\n host.dispose();\n },\n };\n}\n","/**\n * @ice/dom — SCREEN SPACE ONLY (design-015 §3, D-D15; D5b): the canvas host, the pointer\n * adapter (events → queue ONLY), the rAF loop, input ownership + the editor's focus, the OS\n * cursor + the room's remote cursors, and `createDeskHost` — the vanilla mount that takes the\n * desk as an opaque layer factory.\n * Import wall: @ice/core + @ice/kernel down only — never react/three/desk (enforced).\n *\n * GONE at D5b — the WORLD-SPACE half (design-015 §1): the content and lifted planes, the\n * plane-transform reflector, the DOM widget hosts + writeback, the L1 source canvas\n * (`layoutsubtree`), the Widget Surface contract, measurement, the graybox and the P4 chrome\n * reflectors, and the pointer adapter's GL route. No DOM under the camera (DK-D2).\n */\nexport const DOM_VERSION = \"0.0.0\";\n\n// The canvas host (design-004 §1): the styled container — no planes.\nexport { createCanvasHost, type CanvasHost } from \"./host\";\n\n// The vanilla mount (design-015 §3): the desk's layer, the adapters, the reflectors, the loop.\nexport {\n createDeskHost,\n type DeskHost,\n type DeskHostOptions,\n type LayerContext,\n type LayerFactory,\n type LayerHandle,\n} from \"./desk-host\";\n\n// The rAF frame loop (design-002 §1: the platform owns the loop).\nexport { startRafLoop } from \"./loop\";\n\n// The L0 pointer adapter (design-003 §2–§3): DOM events → InputQueue, nothing more.\nexport { attachPointerAdapter } from \"./pointer-adapter\";\n\n// Input-ownership predicates (design-007 §4, petitions I1/I4): the shared\n// per-surface guard family — the keymap (@ice/react) reads these too.\nexport {\n CLAIM_OWNS_ESCAPE,\n KEYBOARD_CLAIM_ATTR,\n isEditableTarget,\n keyboardClaimOf,\n wheelCede,\n type KeyboardClaim,\n} from \"./input-ownership\";\n\n// The focus driver (design-007 §2.3–§2.5): click-to-focus acquisition for a\n// keyboard-claiming host + the programmatic blurFocus/focusWidget handle.\n// Focus is VIEW state (design-007 §2.6) — it lives here, not in core.\nexport {\n FOCUS_PROXY_ATTR,\n attachWidgetFocus,\n type FocusHostLookup,\n type WidgetFocusHandle,\n} from \"./widget-focus\";\n\n// L4 cursor projection output (design-003 §7: local cursor = OS cursor, one write).\nexport { createCursorReflector } from \"./reflectors/cursor\";\n// P5 remote cursors (design-004 §1: screen-space pooled nodes; M9 presence) — a room's other people.\nexport { createRemoteCursorsReflector, type RemoteCursorsReflector } from \"./reflectors/remote-cursors\";\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,IAAM,kBAAoD;AAAA,EACxD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AACd;AAEO,SAAS,iBAAiB,WAAoC;AASnE,QAAM,OAAO,UAAU,cAAc;AACrC,QAAM,WACJ,UAAU,MAAM,aAAa,KACzB,UAAU,MAAM,WACf,MAAM,iBAAiB,SAAS,EAAE,YAAY;AACrD,QAAM,oBAAoB,aAAa,MAAM,aAAa;AAC1D,MAAI,kBAAmB,WAAU,MAAM,WAAW;AAIlD,QAAM,sBAAsB,OAAO,KAAK,eAAe,EAAE,IAAI,CAAC,SAAS;AAErE,UAAM,UAAU,KAAK,QAAQ,UAAU,CAAC,MAAM,IAAI,EAAE,YAAY,CAAC,EAAE;AACnE,WAAO,CAAC,SAAS,UAAU,MAAM,iBAAiB,OAAO,CAAC;AAAA,EAC5D,CAAC;AACD,SAAO,OAAO,UAAU,OAAO,eAAe;AAE9C,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AACR,UAAI,kBAAmB,WAAU,MAAM,eAAe,UAAU;AAChE,iBAAW,CAAC,SAAS,KAAK,KAAK,qBAAqB;AAGlD,YAAI,UAAU,GAAI,WAAU,MAAM,YAAY,SAAS,KAAK;AAAA,YACvD,WAAU,MAAM,eAAe,OAAO;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AACF;;;ACpBO,SAAS,aAAa,QAAgB,YAAoC,OAAuB,CAAC,GAAe;AACtH,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,QAAQ,OAAO;AACrB,MAAI,SAAS;AACb,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,SAAS;AACb,MAAI;AAEJ,QAAM,OAAO,CAAC,QAAsB;AAClC,QAAI,QAAS;AACb,QAAI,CAAC,MAAM,UAAU,GAAG;AAEtB,eAAS;AACT,eAAS;AACT;AAAA,IACF;AACA,QAAI;AACF,mBAAa,GAAG;AAChB,aAAO,KAAK,GAAG;AAAA,IACjB,UAAE;AAIA,UAAI,CAAC,QAAS,UAAS;AAAA,IACzB;AAAA,EACF;AAGA,QAAM,WAAW,MAAY;AAC3B,QAAI,CAAC,QAAQ;AACX,eAAS,sBAAsB,IAAI;AACnC;AAAA,IACF;AACA,UAAM,IAAI,YAAY,IAAI;AAC1B,UAAM,MAAM,MAAM,SAAS,CAAC;AAC5B,QAAI,OAAO,GAAG;AACZ,eAAS,sBAAsB,IAAI;AACnC;AAAA,IACF;AACA,aAAS;AACT,aAAS;AACT,UAAM,SAAS,IAAI;AACnB,QAAI,QAAQ,OAAO,kBAAmB,SAAQ,WAAW,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC,CAAC;AAAA,EACjG;AAGA,QAAM,OAAO,CAAC,UAAyB;AACrC,QAAI,WAAW,CAAC,OAAQ;AACxB,aAAS;AACT,QAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,YAAQ;AACR,UAAM,SAAS,OAAO,KAAK;AAC3B,aAAS,sBAAsB,IAAI;AAAA,EACrC;AAIA,QAAM,OAAO,MAAY;AACvB,QAAI,WAAW,CAAC,UAAU,MAAM,SAAS,EAAG;AAC5C,aAAS;AACT,aAAS,sBAAsB,IAAI;AAAA,EACrC;AACA,QAAM,cAAc,MAAM,SAAS,IAAI;AACvC,QAAM,kBAAkB,SAAS,MAAM,OAAO,MAAM,KAAK,KAAK,CAAC,IAAI;AAEnE,WAAS,sBAAsB,IAAI;AAEnC,SAAO,MAAM;AACX,cAAU;AACV,gBAAY;AACZ,sBAAkB;AAClB,QAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,QAAI,OAAQ,OAAM,SAAS,KAAK;AAChC,yBAAqB,MAAM;AAAA,EAC7B;AACF;;;AClGO,IAAM,sBAAsB;AAG5B,IAAM,oBAAoB;AASjC,IAAM,WAA0B,EAAE,SAAS,OAAO,YAAY,MAAM;AAG7D,SAAS,iBAAiB,QAAqC;AACpE,MAAI,WAAW,QAAQ,EAAE,kBAAkB,aAAc,QAAO;AAChE,QAAM,MAAM,OAAO;AACnB,SAAO,QAAQ,WAAW,QAAQ,cAAc,QAAQ,YAAY,OAAO;AAC7E;AAOO,SAAS,gBAAgB,QAA2C;AACzE,MAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,QAAM,SAAS,OAAO,QAAQ,IAAI,mBAAmB,GAAG;AACxD,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO,EAAE,SAAS,MAAM,YAAY,OAAO,aAAa,mBAAmB,MAAM,kBAAkB;AACrG;AAOA,IAAM,0BACJ,IAAI,mBAAmB;AAGzB,IAAM,aAAa;AAGnB,SAAS,cAAc,IAAiB,IAAY,IAAqB;AACvE,QAAM,QAAQ,GAAG,cAAc,aAAa,iBAAiB,EAAE;AAC/D,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,KAAK,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,GAAG;AAChC,QAAI,OAAO,EAAG,QAAO;AACrB,UAAM,KAAK,MAAM;AACjB,QAAI,OAAO,UAAU,OAAO,SAAU,QAAO;AAC7C,QAAI,GAAG,gBAAgB,GAAG,eAAe,WAAY,QAAO;AAC5D,WAAO,KAAK,IAAI,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,aAAa,GAAG,YAAY;AAAA,EACjG;AACA,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,KAAK,MAAM;AACjB,MAAI,OAAO,UAAU,OAAO,SAAU,QAAO;AAC7C,MAAI,GAAG,eAAe,GAAG,cAAc,WAAY,QAAO;AAC1D,SAAO,KAAK,IAAI,GAAG,aAAa,GAAG,cAAc,GAAG,cAAc,aAAa,GAAG,aAAa;AACjG;AAUO,SAAS,UACd,QACA,WACA,IACA,IACS;AACT,MAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,MAAI,OAAO,KAAK,OAAO,EAAG,QAAO;AACjC,MAAI,oBAAoB;AACxB,WAAS,KAAqB,QAAQ,OAAO,QAAQ,OAAO,WAAW,KAAK,GAAG,eAAe;AAC5F,QAAI,cAAc,eAAe,cAAc,IAAI,IAAI,EAAE,EAAG,qBAAoB;AAChF,QAAI,qBAAqB,GAAG,QAAQ,uBAAuB,EAAG,QAAO;AAAA,EACvE;AACA,SAAO;AACT;;;AC9CA,IAAM,gBAAgB;AAOtB,IAAM,uBACJ;AAGF,SAAS,mBAAmB,QAA4B,WAAiC;AACvF,MAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,QAAM,QAAQ,OAAO,QAAQ,oBAAoB;AAGjD,SAAO,UAAU,QAAQ,UAAU,SAAS,KAAK;AACnD;AAEA,SAAS,YAAY,GAAyB;AAC5C,MAAI,EAAE,gBAAgB,QAAS,QAAO,SAAS,EAAE,SAAS;AAC1D,MAAI,EAAE,gBAAgB,MAAO,QAAO;AACpC,SAAO;AACT;AAEA,SAAS,SAAS,GAAuC;AACvD,MAAI,EAAE,gBAAgB,QAAS,QAAO;AACtC,MAAI,EAAE,gBAAgB,MAAO,QAAO;AACpC,SAAO;AACT;AAEO,SAAS,qBAAqB,MAAkB,OAA+B;AACpF,QAAM,EAAE,UAAU,IAAI;AACtB,QAAM,OAAO,UAAU,cAAc;AAErC,MAAI,YAAY;AAWhB,QAAM,OAAO,oBAAI,IAWf;AACF,QAAM,kBAAkB;AAExB,QAAM,UAAU,CAAC,cAA4B;AAC3C,QAAI,OAAO,UAAU,sBAAsB,WAAY;AACvD,QAAI;AACF,gBAAU,kBAAkB,SAAS;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,WAAW,EAAE,OAAO,OAAO,MAAM,OAAO,KAAK,OAAO,MAAM,OAAO,OAAO,MAAM;AAElF,QAAM,WAAW,CAAC,SAAiB,YAA8C;AAC/E,UAAM,OAAO,UAAU,sBAAsB;AAC7C,WAAO,EAAE,GAAG,UAAU,KAAK,MAAM,GAAG,UAAU,KAAK,IAAI;AAAA,EACzD;AAEA,QAAM,cAAc,CAAC,OAA6C;AAAA,IAChE,OAAO,EAAE;AAAA,IACT,MAAM,EAAE;AAAA,IACR,KAAK,EAAE;AAAA,IACP,MAAM,EAAE;AAAA,IACR,OAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,CAAC,MAA0B;AAC/C,UAAM,KAAK,YAAY,CAAC;AACxB,UAAM,SAAS,SAAS,CAAC;AACzB,UAAM,EAAE,GAAG,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,OAAO;AAG9C,UAAM,SAAS,mBAAmB,EAAE,QAAQ,SAAS;AACrD,SAAK,IAAI,IAAI,EAAE,QAAQ,GAAG,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,UAAU,MAAM,CAAC;AAC1E,UAAM,iBAAiB;AACvB,UAAM,QAAQ;AAAA,MACZ,MAAM;AAAA,MACN,WAAW;AAAA,MACX;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS,EAAE;AAAA,MACX,MAAM,YAAY,CAAC;AAAA,MACnB,KAAK,EAAE;AAAA,MACP,GAAI,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,MAGjD,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,QAAM,gBAAgB,CAAC,MAA0B;AAC/C,UAAM,KAAK,YAAY,CAAC;AACxB,UAAM,SAAS,SAAS,CAAC;AACzB,UAAM,EAAE,GAAG,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,OAAO;AAC9C,QAAI,OAAO,KAAK,IAAI,EAAE;AAStB,QAAI,SAAS,UAAa,EAAE,YAAY,KAAK,CAAC,mBAAmB,EAAE,QAAQ,SAAS,GAAG;AACrF,aAAO,EAAE,QAAQ,GAAG,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,OAAO,UAAU,MAAM;AAC1E,WAAK,IAAI,IAAI,IAAI;AAAA,IACnB;AACA,QAAI,SAAS,QAAW;AACtB,WAAK,IAAI;AACT,WAAK,IAAI;AAGT,UACE,CAAC,KAAK,YACN,CAAC,KAAK,UACN,EAAE,YAAY,KACd,KAAK,MAAM,IAAI,KAAK,OAAO,IAAI,KAAK,KAAK,IAAI,iBAC7C;AACA,gBAAQ,EAAE,SAAS;AACnB,aAAK,WAAW;AAAA,MAClB;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACZ,MAAM;AAAA,MACN,WAAW;AAAA,MACX;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS,EAAE;AAAA,MACX,MAAM,YAAY,CAAC;AAAA,MACnB,KAAK,EAAE;AAAA;AAAA,MAEP,iBAAiB,mBAAmB,EAAE,QAAQ,SAAS;AAAA,IACzD,CAAC;AAAA,EACH;AAEA,QAAM,aACJ,CAAC,SACD,CAAC,MAA0B;AACzB,UAAM,KAAK,YAAY,CAAC;AACxB,UAAM,SAAS,SAAS,CAAC;AACzB,UAAM,EAAE,GAAG,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,OAAO;AAC9C,SAAK,OAAO,EAAE;AACd,QAAI,OAAO,UAAU,0BAA0B,YAAY;AACzD,UAAI;AACF,kBAAU,sBAAsB,EAAE,SAAS;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS,EAAE;AAAA,MACX,MAAM,YAAY,CAAC;AAAA;AAAA;AAAA,IAGrB,CAAC;AAAA,EACH;AACF,QAAM,cAAc,WAAW,IAAI;AACnC,QAAM,kBAAkB,WAAW,QAAQ;AAE3C,QAAM,UAAU,CAAC,MAAwB;AACvC,UAAM,EAAE,GAAG,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,OAAO;AAC9C,UAAM,QAAQ,EAAE,cAAc,IAAI,gBAAgB;AAClD,QAAI,EAAE,SAAS;AAGb,QAAE,eAAe;AACjB,YAAM,QAAQ,EAAE,MAAM,SAAS,WAAW,SAAS,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM,YAAY,CAAC,GAAG,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;AAChL;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,IAAI,EAAE,SAAS,OAAO,IAAI,EAAE,SAAS,OAAO,OAAO,EAAE;AACrE,QAAI,UAAU,EAAE,QAAQ,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG;AAMtD,YAAM,QAAQ,EAAE,MAAM,SAAS,WAAW,SAAS,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM,YAAY,CAAC,GAAG,OAAO,cAAc,KAAK,CAAC;AACjK;AAAA,IACF;AACA,MAAE,eAAe;AACjB,UAAM,QAAQ,EAAE,MAAM,SAAS,WAAW,SAAS,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS,EAAE,SAAS,MAAM,YAAY,CAAC,GAAG,MAAM,CAAC;AAAA,EAC/I;AAGA,QAAM,mBAAmB,CAAC,MAA2B;AACnD,UAAM,OAAkB,EAAE,OAAO,EAAE,UAAU,MAAM,EAAE,SAAS,KAAK,EAAE,QAAQ,MAAM,EAAE,SAAS,OAAO,UAAU;AAC/G,QACE,KAAK,UAAU,SAAS,SACxB,KAAK,SAAS,SAAS,QACvB,KAAK,QAAQ,SAAS,OACtB,KAAK,SAAS,SAAS,QACvB,KAAK,UAAU,SAAS,OACxB;AACA;AAAA,IACF;AACA,eAAW,EAAE,GAAG,KAAK;AACrB,UAAM,QAAQ,EAAE,MAAM,OAAO,WAAW,IAAI,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,CAAC;AAAA,EACzG;AAEA,QAAM,UAAU,CAAC,MAA8B,EAAE,QAAQ,OAAO,EAAE,SAAS;AAE3E,QAAM,YAAY,CAAC,MAA2B;AAC5C,QAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB;AAGrC,UAAI,iBAAiB,EAAE,MAAM,GAAG;AAAA,MAGhC,WAAW,gBAAgB,EAAE,MAAM,EAAE,SAAS;AAI5C,UAAE,eAAe;AAAA,MACnB,OAAO;AACL,UAAE,eAAe;AACjB,oBAAY;AAAA,MACd;AAAA,IACF;AACA,qBAAiB,CAAC;AAAA,EACpB;AACA,QAAM,UAAU,CAAC,MAA2B;AAC1C,QAAI,QAAQ,CAAC,GAAG;AACd,UAAI,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,EAAE,MAAM,KAAK,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS;AAC5F,UAAE,eAAe;AAAA,MACnB;AAIA,kBAAY;AAAA,IACd;AACA,qBAAiB,CAAC;AAAA,EACpB;AAUA,QAAM,iBAAiB,MAAY;AAGjC,eAAW,CAAC,IAAI,CAAC,KAAK,MAAM;AAC1B,YAAM,QAAQ,EAAE,MAAM,UAAU,WAAW,IAAI,QAAQ,EAAE,QAAQ,SAAS,EAAE,GAAG,SAAS,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,OAAO,UAAU,EAAE,CAAC;AAAA,IACnJ;AACA,SAAK,MAAM;AAKX,gBAAY;AACZ,QAAI,SAAS,SAAS,SAAS,SAAS,SAAS,QAAQ,SAAS,OAAO,SAAS,MAAM;AACtF,iBAAW,EAAE,GAAG,QAAQ;AACxB,YAAM,QAAQ;AAAA,QACZ,MAAM;AAAA,QACN,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,MAAM,EAAE,GAAG,QAAQ;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,SAAS,MAAY;AACzB,mBAAe;AAAA,EACjB;AAEA,YAAU,iBAAiB,eAAe,aAAa;AACvD,YAAU,iBAAiB,eAAe,aAAa;AACvD,YAAU,iBAAiB,aAAa,WAAW;AACnD,YAAU,iBAAiB,iBAAiB,eAAe;AAC3D,YAAU,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAC/D,QAAM,iBAAiB,WAAW,SAAS;AAC3C,QAAM,iBAAiB,SAAS,OAAO;AACvC,QAAM,iBAAiB,QAAQ,MAAM;AAErC,SAAO,MAAM;AAGX,mBAAe;AACf,cAAU,oBAAoB,eAAe,aAAa;AAC1D,cAAU,oBAAoB,eAAe,aAAa;AAC1D,cAAU,oBAAoB,aAAa,WAAW;AACtD,cAAU,oBAAoB,iBAAiB,eAAe;AAC9D,cAAU,oBAAoB,SAAS,OAAO;AAC9C,UAAM,oBAAoB,WAAW,SAAS;AAC9C,UAAM,oBAAoB,SAAS,OAAO;AAC1C,UAAM,oBAAoB,QAAQ,MAAM;AAAA,EAC1C;AACF;;;AC5WO,SAAS,sBACd,MACA,YACqC;AACrC,MAAI,SAAS;AACb,MAAI;AACJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM,QAAe;AACnB,YAAM,OAAO,WAAW;AACxB,UAAI,SAAS,KAAM;AACnB,aAAO;AACP,WAAK,UAAU,MAAM,SAAS;AAC9B;AAAA,IACF;AAAA,IACA,QAAQ,MAAM;AAAA,EAChB;AACF;;;ACXA,IAAM,gBAAgB,YAAY,CAAC,UAAU,YAAY,CAAC;AAQnD,SAAS,6BAA6B,MAAkB,OAAsC;AACnG,QAAM,MAAM,KAAK,UAAU;AAC3B,QAAM,QAAQ,IAAI,cAAc,KAAK;AACrC,SAAO,OAAO,MAAM,OAAO;AAAA,IACzB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,eAAe;AAAA,EACjB,CAAiC;AACjC,OAAK,UAAU,YAAY,KAAK;AAUhC,QAAM,OAAO,oBAAI,IAAkB;AAEnC,QAAM,SAAS,CAAC,MAAoB;AAClC,QAAI,IAAI,KAAK,IAAI,CAAC;AAClB,QAAI,MAAM,OAAW,QAAO;AAC5B,UAAM,OAAO,IAAI,cAAc,KAAK;AACpC,WAAO,OAAO,KAAK,OAAO;AAAA,MACxB,UAAU;AAAA,MACV,MAAM;AAAA,MACN,KAAK;AAAA,MACL,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB,CAAiC;AAEjC,UAAM,QAAQ,IAAI,cAAc,KAAK;AACrC,WAAO,OAAO,MAAM,OAAO;AAAA,MACzB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,cAAc;AAAA,MACd,WAAW;AAAA,IACb,CAAiC;AACjC,UAAM,QAAQ,IAAI,cAAc,KAAK;AACrC,WAAO,OAAO,MAAM,OAAO;AAAA,MACzB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,cAAc;AAAA,MACd,MAAM;AAAA,MACN,OAAO;AAAA,MACP,YAAY;AAAA,IACd,CAAiC;AACjC,SAAK,YAAY,KAAK;AACtB,SAAK,YAAY,KAAK;AACtB,UAAM,YAAY,IAAI;AACtB,QAAI,EAAE,MAAM,OAAO,OAAO,IAAI,MAAM,IAAI,GAAG,OAAO,KAAK,GAAG,OAAO,IAAI;AACrE,SAAK,IAAI,GAAG,CAAC;AACb,WAAO;AAAA,EACT;AAEA,QAAM,YAA0B;AAAA,IAC9B,MAAM;AAAA,IACN,SAAS;AAAA,MACP,WAAW,CAAC,MAAM;AAAA,MAClB,SAAS,CAAC,EAAE,GAAG,eAAe,MAAM,CAAC,UAAU,YAAY,EAAE,CAAC;AAAA,IAChE;AAAA,IACA,OAAO,CAAC,MAAM;AACZ,YAAM,MAAM,EAAE,YAAY,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,EAAE;AAC3D,YAAM,OAAO,oBAAI,IAAY;AAC7B,QAAE,MAAM,aAAa,EAAE,KAAK,CAAC,MAAM;AACjC,mBAAW,KAAK,GAAG;AACjB,gBAAM,IAAI,EAAE,OAAO,CAAC;AACpB,cAAI,EAAE,IAAI,GAAG,YAAY,GAAG,SAAS,SAAU;AAC/C,eAAK,IAAI,CAAC;AACV,gBAAM,IAAI,EAAE,KAAK,GAAG,QAAQ;AAC5B,gBAAM,IAAI,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG;AACrC,gBAAM,IAAI,OAAO,CAAC;AAClB,cAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG;AAC9B,cAAE,KAAK,MAAM,YAAY,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;AACnD,cAAE,IAAI,EAAE;AACR,cAAE,IAAI,EAAE;AAAA,UACV;AACA,gBAAM,OAAO,EAAE,YAAY,GAAG,OAAO;AACrC,gBAAM,OAAO,SAAS,SAAY,EAAE,IAAI,MAAM,YAAY,IAAI;AAC9D,gBAAM,QAAQ,MAAM,SAAS;AAC7B,gBAAM,OAAO,MAAM,QAAQ;AAC3B,cAAI,EAAE,UAAU,OAAO;AACrB,cAAE,KAAK,MAAM,QAAQ;AACrB,cAAE,MAAM,MAAM,aAAa;AAC3B,cAAE,QAAQ;AAAA,UACZ;AACA,cAAI,EAAE,SAAS,MAAM;AACnB,cAAE,MAAM,cAAc;AACtB,cAAE,MAAM,MAAM,UAAU,SAAS,KAAK,SAAS;AAC/C,cAAE,OAAO;AAAA,UACX;AAAA,QACF;AAAA,MACF,CAAC;AACD,iBAAW,CAAC,GAAG,CAAC,KAAK,MAAM;AACzB,YAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,YAAE,KAAK,OAAO;AACd,eAAK,OAAO,CAAC;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AACR,WAAK,MAAM;AACX,YAAM,OAAO;AAAA,IACf;AAAA,EACF;AACF;;;AC3GO,IAAM,mBAAmB;AAMhC,IAAM,qBACJ;AA0BF,SAAS,YAAY,WAAqC;AACxD,QAAM,QAAQ,UAAU,cAA2B,IAAI,gBAAgB,GAAG;AAC1E,SAAO,SAAS;AAClB;AAEO,SAAS,kBAAkB,MAAkB,QAA6C;AAC/F,QAAM,EAAE,UAAU,IAAI;AACtB,QAAM,MAAM,UAAU;AAEtB,QAAM,gBAAgB,CAAC,MAA0B;AAC/C,QAAI,EAAE,EAAE,kBAAkB,SAAU;AACpC,UAAM,SAAS,EAAE,OAAO,QAAQ,IAAI,mBAAmB,GAAG;AAC1D,QAAI,EAAE,kBAAkB,gBAAgB,CAAC,UAAU,SAAS,MAAM,EAAG;AAGrE,UAAM,YAAY,EAAE,OAAO,QAAQ,kBAAkB;AACrD,QAAI,cAAc,QAAQ,cAAc,UAAU,OAAO,SAAS,SAAS,EAAG;AAC9E,UAAM,OAAO,YAAY,MAAM;AAC/B,QAAI,IAAI,kBAAkB,KAAM;AAChC,MAAE,eAAe;AACjB,SAAK,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,EACpC;AAIA,YAAU,iBAAiB,eAAe,eAAe,IAAI;AAE7D,SAAO;AAAA,IACL,YAAY,QAAQ;AAClB,YAAM,UAAU,QAAQ,QAAQ,MAAM;AACtC,YAAM,SAAS,SAAS;AACxB,UAAI,EAAE,kBAAkB,gBAAgB,CAAC,OAAO,aAAa,mBAAmB,EAAG,QAAO;AAC1F,YAAM,OAAO,YAAY,MAAM;AAC/B,WAAK,MAAM,EAAE,eAAe,KAAK,CAAC;AAClC,aAAO,IAAI,kBAAkB;AAAA,IAC/B;AAAA,IACA,YAAY;AACV,YAAM,SAAS,IAAI;AAEnB,UAAI,EAAE,kBAAkB,eAAe,kBAAkB,YAAa,QAAO;AAC7E,UAAI,OAAO,QAAQ,IAAI,mBAAmB,GAAG,MAAM,KAAM,QAAO;AAChE,aAAO,KAAK;AACZ,aAAO;AAAA,IACT;AAAA,IACA,SAAS;AACP,gBAAU,oBAAoB,eAAe,eAAe,IAAI;AAAA,IAClE;AAAA,EACF;AACF;;;ACHO,SAAS,eAAsC,MAAuC;AAC3F,QAAM,EAAE,WAAW,OAAO,IAAI;AAC9B,QAAM,EAAE,OAAO,QAAQ,MAAM,MAAM,IAAI;AACvC,QAAM,OAAO,iBAAiB,SAAS;AAEvC,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,aAAa,MAAM;AAAA,MACnB,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,MAChB,aAAa,MAAM,MAAM;AAAA,MACzB,SAAS,MAAM;AAAA,MACf,GAAI,OAAO,qBAAqB,SAAY,EAAE,KAAK,OAAO,iBAAiB,IAAI,CAAC;AAAA,MAChF,OAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,SAAK,QAAQ;AACb,UAAM;AAAA,EACR;AAKA,QAAM,gBAAgB,MAAM,YAAY,QAAQ,SAAY,6BAA6B,MAAM,KAAK;AACpG,QAAM,aAAa;AAAA,IACjB,KAAK,kBAAkB,MAAM,SAAS;AAAA,IACtC,KAAK,kBAAkB,sBAAsB,MAAM,MAAM,UAAU,CAAC;AAAA,IACpE,GAAI,kBAAkB,SAAY,CAAC,KAAK,kBAAkB,cAAc,SAAS,CAAC,IAAI,CAAC;AAAA,EACzF;AAGA,QAAM,cAAc,UAAU,cAAc,aAAa,WAAW,kCAAkC;AACtG,QAAM,oBAAoB,MAAY;AACpC,WAAO,YAAY,iBAAiB,aAAa,YAAY,IAAI;AAAA,EACnE;AACA,oBAAkB;AAClB,eAAa,iBAAiB,UAAU,iBAAiB;AAEzD,QAAM,gBAAgB,qBAAqB,MAAM,MAAM,KAAK;AAC5D,QAAM,QAAQ,kBAAkB,IAAI;AAEpC,QAAM,QAAQ,MAAe,OAAO,WAAW,cAAc,OAAO,mBAAmB;AACvF,MAAI,SAAS;AACb,QAAM,eAAe,MAAY;AAC/B,UAAM,OAAO,UAAU,sBAAsB;AAC7C,aAAS,MAAM;AACf,yBAAqB,OAAO,UAAU,EAAE,GAAG,KAAK,OAAO,GAAG,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtF;AACA,eAAa;AACb,MAAI;AACJ,MAAI,OAAO,mBAAmB,aAAa;AACzC,qBAAiB,IAAI,eAAe,YAAY;AAChD,mBAAe,QAAQ,SAAS;AAAA,EAClC;AAMA,QAAM,WAAW;AAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,MAAM,MAAM,OAAQ,cAAa;AAAA,IACvC;AAAA,IACA,EAAE,OAAO,KAAK,UAAU,MAAM;AAAA,EAChC;AAGA,QAAM,YAAY,KAAK,UAAU,QAAQ,YAAY,MAAM;AAAE,QAAI,MAAM,MAAM,OAAQ,cAAa;AAAA,EAAG,GAAG,GAAG,IAAI;AAE/G,MAAI,WAAW;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AACR,UAAI,SAAU;AACd,iBAAW;AACX,eAAS;AACT,UAAI,cAAc,OAAW,eAAc,SAAS;AACpD,sBAAgB,WAAW;AAC3B,YAAM,OAAO;AACb,oBAAc;AACd,mBAAa,oBAAoB,UAAU,iBAAiB;AAC5D,aAAO,YAAY,iBAAiB,KAAK;AACzC,iBAAW,SAAS,WAAY,OAAM;AACtC,qBAAe,QAAQ;AACvB,YAAM,QAAQ;AACd,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AACF;;;AC9MO,IAAM,cAAc;","names":[]}
|
package/dist/desk-kit.js
CHANGED
|
@@ -117,9 +117,9 @@ import {
|
|
|
117
117
|
visibleRect,
|
|
118
118
|
worldBounds,
|
|
119
119
|
writeChanged
|
|
120
|
-
} from "./chunk-
|
|
120
|
+
} from "./chunk-K4HUZAYJ.js";
|
|
121
121
|
import "./chunk-QXOCF56A.js";
|
|
122
|
-
import "./chunk-
|
|
122
|
+
import "./chunk-PFJN3XN2.js";
|
|
123
123
|
import "./chunk-JS6PG6YD.js";
|
|
124
124
|
export {
|
|
125
125
|
BLOB_STORE,
|
package/dist/desk-objects.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
rasterPriority,
|
|
9
9
|
stringProp,
|
|
10
10
|
writable
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-52GMQ2BN.js";
|
|
12
12
|
import "./chunk-BKV4ZEU3.js";
|
|
13
13
|
import {
|
|
14
14
|
compile,
|
|
@@ -110,7 +110,7 @@ import {
|
|
|
110
110
|
unproject,
|
|
111
111
|
worldBounds,
|
|
112
112
|
writeChanged
|
|
113
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-K4HUZAYJ.js";
|
|
114
114
|
import {
|
|
115
115
|
bindGroup,
|
|
116
116
|
bindLayout,
|
|
@@ -170,7 +170,7 @@ import {
|
|
|
170
170
|
setWidgetProps,
|
|
171
171
|
tools,
|
|
172
172
|
trayOpen
|
|
173
|
-
} from "./chunk-
|
|
173
|
+
} from "./chunk-PFJN3XN2.js";
|
|
174
174
|
import "./chunk-JS6PG6YD.js";
|
|
175
175
|
|
|
176
176
|
// ../objects/src/minimat/theme.ts
|
|
@@ -680,7 +680,7 @@ function miniMatShaders(text2) {
|
|
|
680
680
|
// ../objects/src/shaders.gen.ts
|
|
681
681
|
var WGSL = {
|
|
682
682
|
"board/board-card.wgsl": "// The whiteboard as the board pass AND the flat-card pipeline draw it (K7b, design-016 \xA76 K7): the corner's clip position and\n// the THUMBNAIL's ink, over the board's bindings by their own names \u2014 `boards`, `board_k`, `board_thumbs` \u2026 \u2014 beside the slot's\n// `u`, `gobo_tex`, `noise_tex`, which whichever entry composes them declares. (Its fragment is the pass entry's `fs`, written\n// out \u2014 the card splices the same statements, shaders.ts `BOARD_FRAGMENT`.) `shade_board` reads its ink\n// through `board_ink` and the live board's layers through `board_stroke` / `board_wet`: the pass's entry reads the pool and\n// the live layers there, else the thumbnail; the card binds neither and reads the thumbnail alone (a board with a raster, or\n// being written on, is the pass's to draw).\n\n// The thumbnails' layer side (board-pass.ts `BOARD_THUMB`).\nconst BOARD_THUMB = 512.0;\n\nconst BD_CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0),\n);\n\n// A board's ink at (uv, lod) from its THUMBNAIL \u2014 lod in its raster's chain: the layer (`tier.x`), which starts at the level k of\n// its source's chain (`tier.y`) \u2014 the chain's tail at the layer's origin, its edge carried to the layer's \u2014 at lod \u2212 k.\nfn board_thumb(B: Board, uv: vec2f, lod: f32) -> vec4f {\n let k = B.tier.y;\n let size = max(floor(B.texels / exp2(k)), vec2f(1.0));\n return textureSampleLevel(board_thumbs, board_ink_samp, uv * size / BOARD_THUMB, i32(B.tier.x), max(lod - k, 0.0));\n}\n\nfn board_quad(slot: u32, vid: u32) -> vec4f {\n let B = boards[slot];\n let zoom = max(u.cam.z, 1.0e-12);\n let lo = (B.quad.xy - u.cam.xy) * zoom; // CSS px\n let hi = (B.quad.zw - u.cam.xy) * zoom;\n if (hi.x < u.box.x || hi.y < u.box.y || lo.x > u.box.x + u.box.z || lo.y > u.box.y + u.box.w) { return vec4f(2.0, 2.0, 2.0, 1.0); } // degenerate: a culled board collapses\n let corner = BD_CORNERS[vid];\n let pos = mix(lo, hi, corner * 0.5 + 0.5);\n return vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n}\n",
|
|
683
|
-
"board/board-pass.wgsl": "// The board pass \u2014 the whiteboards: one quad per board (its extent the record's own: the slab,\n// its shadow, the tool in the hand \u2014 resolved on the CPU), one `shade_board` per covered\n// fragment, premultiplied output over the mat. Group 0 is the slot's (the mat's block, the\n// knobs, the records, the gobo, the noise, the samplers); group 1 is the SAME for every board\n// (K6a, K-L4 \u2014 a run of boards is one instanced draw): the far-LOD thumbnails' array, the pool's\n// rasters' ink (with mips), the live board's stroke and wet layers; a board's record says where\n// its ink is (`tier`). The camera, the box and the portal chain come through the mat's block, as\n// for the note and the notebook. The quad, the fragment and the thumbnail's ink are\n// board-card.wgsl's \u2014 the flat-card pipeline draws a board with the same three (K7b).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> board_k: BoardUniforms;\n@group(0) @binding(2) var<storage, read> boards: array<Board>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var board_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var board_noise_samp: sampler;\n@group(0) @binding(7) var board_ink_samp: sampler; // trilinear \u2014 the ink seen small reads its mips\n@group(0) @binding(8) var board_lin_samp: sampler; // bilinear, level 0 \u2014 the stroke and the wet layer\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index \u2192 the record's slot (persistent records, D6)\n@group(1) @binding(0) var board_thumbs: texture_2d_array<f32>; // every board's ink chain from its first level \u2264 BOARD_THUMB\xB2, a layer each\n@group(1) @binding(1) var ink0: texture_2d<f32>; // the pool: a board's raster \u2014 premultiplied linear ink, coverage in alpha\n@group(1) @binding(2) var ink1: texture_2d<f32>;\n@group(1) @binding(3) var ink2: texture_2d<f32>;\n@group(1) @binding(4) var ink3: texture_2d<f32>;\n@group(1) @binding(5) var ink4: texture_2d<f32>;\n@group(1) @binding(6) var ink5: texture_2d<f32>;\n@group(1) @binding(7) var ink6: texture_2d<f32>;\n@group(1) @binding(8) var ink7: texture_2d<f32>;\n@group(1) @binding(9) var stroke_live: texture_2d<f32>; // the live board's stroke being laid (r8)\n@group(1) @binding(10) var wet_live: texture_2d<f32>; // \u2026and how wet its ink still is (r8)\n\n// A board's ink at (uv, lod) \u2014 lod in its raster's chain. `tier`: its thumbnail's layer, the level of its source's chain the\n// layer starts at (k), its pool slot (\u22121: none), live. The slot's raster, else the layer (board-card.wgsl `board_thumb`).\nfn board_ink(B: Board, uv: vec2f, lod: f32) -> vec4f {\n switch i32(B.tier.z) {\n case 0: { return textureSampleLevel(ink0, board_ink_samp, uv, lod); }\n case 1: { return textureSampleLevel(ink1, board_ink_samp, uv, lod); }\n case 2: { return textureSampleLevel(ink2, board_ink_samp, uv, lod); }\n case 3: { return textureSampleLevel(ink3, board_ink_samp, uv, lod); }\n case 4: { return textureSampleLevel(ink4, board_ink_samp, uv, lod); }\n case 5: { return textureSampleLevel(ink5, board_ink_samp, uv, lod); }\n case 6: { return textureSampleLevel(ink6, board_ink_samp, uv, lod); }\n case 7: { return textureSampleLevel(ink7, board_ink_samp, uv, lod); }\n default: {}\n }\n return board_thumb(B, uv, lod);\n}\n\n// The live board's stroke being laid, and how wet its ink is \u2014 0 on any other board (their layers are not bound).\nfn board_stroke(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(stroke_live, board_lin_samp, uv, 0.0).r;\n}\nfn board_wet(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(wet_live, board_lin_samp, uv, 0.0).r;\n}\n\nstruct BoardOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> BoardOut {\n var out: BoardOut;\n out.idx = order[iid];\n out.clip = board_quad(out.idx, vid);\n return out;\n}\n\n// The fragment, written out: shade_board reached through one more function compiles to one LSB off on a pixel or two a scene\n// (K7b, measured) \u2014 so the flat-card pipeline splices THESE statements (shaders.ts `BOARD_FRAGMENT`, a test holds them equal).\n@fragment\nfn fs(in: BoardOut) -> @location(0) vec4f {\n let B = boards[in.idx];\n let zoom = max(u.cam.z, 1.0e-12);\n let dpr = max(u.cam.w, 1.
|
|
683
|
+
"board/board-pass.wgsl": "// The board pass \u2014 the whiteboards: one quad per board (its extent the record's own: the slab,\n// its shadow, the tool in the hand \u2014 resolved on the CPU), one `shade_board` per covered\n// fragment, premultiplied output over the mat. Group 0 is the slot's (the mat's block, the\n// knobs, the records, the gobo, the noise, the samplers); group 1 is the SAME for every board\n// (K6a, K-L4 \u2014 a run of boards is one instanced draw): the far-LOD thumbnails' array, the pool's\n// rasters' ink (with mips), the live board's stroke and wet layers; a board's record says where\n// its ink is (`tier`). The camera, the box and the portal chain come through the mat's block, as\n// for the note and the notebook. The quad, the fragment and the thumbnail's ink are\n// board-card.wgsl's \u2014 the flat-card pipeline draws a board with the same three (K7b).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> board_k: BoardUniforms;\n@group(0) @binding(2) var<storage, read> boards: array<Board>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var board_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var board_noise_samp: sampler;\n@group(0) @binding(7) var board_ink_samp: sampler; // trilinear \u2014 the ink seen small reads its mips\n@group(0) @binding(8) var board_lin_samp: sampler; // bilinear, level 0 \u2014 the stroke and the wet layer\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index \u2192 the record's slot (persistent records, D6)\n@group(1) @binding(0) var board_thumbs: texture_2d_array<f32>; // every board's ink chain from its first level \u2264 BOARD_THUMB\xB2, a layer each\n@group(1) @binding(1) var ink0: texture_2d<f32>; // the pool: a board's raster \u2014 premultiplied linear ink, coverage in alpha\n@group(1) @binding(2) var ink1: texture_2d<f32>;\n@group(1) @binding(3) var ink2: texture_2d<f32>;\n@group(1) @binding(4) var ink3: texture_2d<f32>;\n@group(1) @binding(5) var ink4: texture_2d<f32>;\n@group(1) @binding(6) var ink5: texture_2d<f32>;\n@group(1) @binding(7) var ink6: texture_2d<f32>;\n@group(1) @binding(8) var ink7: texture_2d<f32>;\n@group(1) @binding(9) var stroke_live: texture_2d<f32>; // the live board's stroke being laid (r8)\n@group(1) @binding(10) var wet_live: texture_2d<f32>; // \u2026and how wet its ink still is (r8)\n\n// A board's ink at (uv, lod) \u2014 lod in its raster's chain. `tier`: its thumbnail's layer, the level of its source's chain the\n// layer starts at (k), its pool slot (\u22121: none), live. The slot's raster, else the layer (board-card.wgsl `board_thumb`).\nfn board_ink(B: Board, uv: vec2f, lod: f32) -> vec4f {\n switch i32(B.tier.z) {\n case 0: { return textureSampleLevel(ink0, board_ink_samp, uv, lod); }\n case 1: { return textureSampleLevel(ink1, board_ink_samp, uv, lod); }\n case 2: { return textureSampleLevel(ink2, board_ink_samp, uv, lod); }\n case 3: { return textureSampleLevel(ink3, board_ink_samp, uv, lod); }\n case 4: { return textureSampleLevel(ink4, board_ink_samp, uv, lod); }\n case 5: { return textureSampleLevel(ink5, board_ink_samp, uv, lod); }\n case 6: { return textureSampleLevel(ink6, board_ink_samp, uv, lod); }\n case 7: { return textureSampleLevel(ink7, board_ink_samp, uv, lod); }\n default: {}\n }\n return board_thumb(B, uv, lod);\n}\n\n// The live board's stroke being laid, and how wet its ink is \u2014 0 on any other board (their layers are not bound).\nfn board_stroke(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(stroke_live, board_lin_samp, uv, 0.0).r;\n}\nfn board_wet(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(wet_live, board_lin_samp, uv, 0.0).r;\n}\n\nstruct BoardOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> BoardOut {\n var out: BoardOut;\n out.idx = order[iid];\n out.clip = board_quad(out.idx, vid);\n return out;\n}\n\n// The fragment, written out: shade_board reached through one more function compiles to one LSB off on a pixel or two a scene\n// (K7b, measured) \u2014 so the flat-card pipeline splices THESE statements (shaders.ts `BOARD_FRAGMENT`, a test holds them equal).\n@fragment\nfn fs(in: BoardOut) -> @location(0) vec4f {\n let B = boards[in.idx];\n let zoom = max(u.cam.z, 1.0e-12);\n let dpr = max(u.cam.w, 1.0e-6);\n let px = 1.0 / (zoom * dpr); // world per device px\n let css = 1.0 / zoom; // world per CSS px\n let p = in.clip.xy * px + u.cam.xy; // device px \u2192 world\n let c = shade_board(B, u, board_k, p, px, css, in.clip.xy, gobo_tex, board_gobo_samp, noise_tex, board_noise_samp);\n if (c.a < 0.002) { discard; }\n // Premultiplied: the presentation's opacity through the portal clip scales every channel.\n return c * (u.view.w * portal_cover(in.clip.xy / dpr, u.portals, u.clips, dpr));\n}\n",
|
|
684
684
|
"board/board.wgsl": "// THE WHITEBOARD (BOARD.md) \u2014 a desk dry-erase board under the mat's one lamp. A PURE module:\n// the record `Board`, the pass's knobs `BoardUniforms`, the mat's block `MatUniforms` and the\n// textures arrive as parameters; it names no colour.\n//\n// The board is a slab on the mat: an aluminium frame round a melamine face recessed a hair\n// below it. The camera is top-down, so the depth is the lamp's work, as for the note and the\n// notebook: the slab casts its footprint along the lamp's ground slope from every height it\n// spans, each blurred by its own height, with a contact shadow at its base; the frame's\n// profile rolls at both rims \u2014 turned toward the lamp it brightens and glints, turned away it\n// falls into shade; the frame's lip shades the melamine on the lamp's side. The melamine\n// carries the ink raster \u2014 a filter laid into its albedo before the light, so the dapple\n// crosses the drawing as it crosses the mat \u2014 its waviness, the ghost of old erasures, fresh\n// ink's wet gloss, and a SHEEN: the window the lamp shines through, mirrored in the glossy face\n// and seen by an eye over the board, so it lands on one part of it and slides as the pointer\n// moves (the research whiteboard's room, reduced to the one bright thing a desk has over it).\n// On the board lies a capped marker; while it is open, the tool in the hand is drawn at the\n// pointer \u2014 its shadow meets its tip as it presses. The colour chain is the note's: the\n// reference's grade on the sRGB byte by day, the eye's chain on its linear value by night.\n\nfn bd_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\nfn bd_line(d: f32, w: f32, px: f32) -> f32 { return bd_cov(d, px) - bd_cov(d + w, px); }\nfn bd_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage \u2014 a shadow, straight out of the field.\nfn bd_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * bd_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n// Premultiplied \"src over dst\".\nfn bd_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n// A world point raised `h` world units off the desk, in the projector's frame (the note's `paper_desk_at`).\nfn bd_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\nfn bd_encode(c: vec3f) -> vec3f {\n let x = clamp(c, vec3f(0.0), vec3f(1.0));\n return mix(x * 12.92, 1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), x));\n}\n\n// The chain, as the note's `paper_colour`: by day the reference's grade on the sRGB albedo \u2014 lit,\n// the identity, so a configured byte is the drawn byte \u2014 or, at `chain` 1, through the mat's own\n// double gamma; by night the eye's chain on the linear albedo, as the mat's; between, the cross-fade.\nfn bd_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32, chain: f32) -> vec3f {\n let day = shade_mat(u, mix(albedo, srgb_to_linear(albedo), chain), gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = night_mat(u, srgb_to_linear(albedo), gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// The lamp on a face (the notebook's `bk_lambert`): its fill, plus the lambert normalised so a\n// flat face reads 1 and capped so a face turned toward a low lamp brightens only so far.\nfn bd_lambert(n: vec3f, L: vec3f, k: BoardUniforms) -> f32 {\n let d = max(dot(n, L), 0.0) / max(L.z, 0.05);\n return k.light.x + (1.0 - k.light.x) * min(d, k.light.y);\n}\n\n// A glint: the lamp mirrored by a face toward the eye straight above (Blinn's half vector).\nfn bd_glint(n: vec3f, L: vec3f, power: f32) -> f32 {\n return pow(max(dot(n, normalize(L + vec3f(0.0, 0.0, 1.0))), 0.0), power);\n}\n\n// How bright a glint can be under the light in force: the Sun's, or the Moon's much less.\nfn bd_lit(u: MatUniforms, k: BoardUniforms) -> f32 { return mix(1.0, k.light.w, clamp(u.night.x, 0.0, 1.0)); }\n\n// A rounded box's outward gradient, by central differences.\nfn bd_grad(q: vec2f, half: vec2f, r: f32) -> vec2f {\n let e = 0.35;\n let g = vec2f(\n sdf_round_box(q + vec2f(e, 0.0), half, r) - sdf_round_box(q - vec2f(e, 0.0), half, r),\n sdf_round_box(q + vec2f(0.0, e), half, r) - sdf_round_box(q - vec2f(0.0, e), half, r));\n let l = length(g);\n return select(vec2f(0.0, -1.0), g / l, l > 1.0e-5);\n}\n\n// ---- the shadows\n\n// A SLAB (a rounded box, `q` in its frame) whose underside is `bottom` and top `top` above the\n// receiver: its footprint cast along the ground slope `S` from every height it spans \u2014 each\n// blurred by its own height, the penumbra widening as the slab rises \u2014 the union as the max;\n// with the contact shadow at its base while it rests, fading as it floats off. `grow` scales the\n// penumbra's growth (the hand's tools cast sharper); `steps` samples the sweep \u2014 enough that the\n// copies never show as steps under that penumbra.\nfn bd_slab(q: vec2f, half: vec2f, r: f32, bottom: f32, top: f32, S: vec2f, k: BoardUniforms, grow: f32, steps: i32) -> f32 {\n var sh = 0.0;\n for (var i = 0; i < steps; i++) {\n let h = mix(bottom, top, f32(i) / f32(steps - 1));\n if (h <= 0.0) { continue; }\n sh = max(sh, bd_blur(sdf_round_box(q - S * h, half, r), k.shadow.x + k.shadow.y * grow * h));\n }\n sh = sh * k.shadow.z;\n let g = max(bottom, 0.0);\n let con = bd_blur(sdf_round_box(q - S * min(g, 1.0) * 0.5, half, r), k.shadow2.x) * k.shadow.w * exp(-g / max(k.shadow2.y, 1.0e-3));\n return max(sh, con);\n}\n\n// ---- the marker\n\n// The tools in the hand cast in the sun's own sharpness \u2014 their penumbra grows at this share of the\n// desk objects' rate, their umbra this much firmer \u2014 so the shadow of a hovering nib is a clear mark\n// that slides to meet it as it presses: the one cue a hand draws by.\nconst HAND_PENUMBRA = 0.35;\nconst HAND_UMBRA = 1.35;\n\n// THE PEN seen from above, in its own frame: `m` = (u along it from the nib, w across). Its cap\n// slides with `cap`: 0 = on the nib (the marker lying capped), 1 = posted on its back (in the\n// hand), and between, travelling along the barrel as a hand uncaps and posts it. Its ends are\n// ROUND \u2014 a domed cap, a round nib, a chamfered plug \u2014 so it reads as a moulded thing, not a\n// cut-out. Returns its signed distance, the part the point is on (0 nib \xB7 1 holder \xB7 2 barrel \xB7\n// 3 stripe \xB7 4 cap \xB7 5 end \xB7 6 the cap's clip \xB7 7 the cap's rim), the local radius, and where\n// the point is on a rounded end (\u22121 \u2026 0 toward the nib, 0 \u2026 1 toward the back; 0 along the tube).\nfn bd_pen(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> vec4f {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let u = m.x;\n let w = abs(m.y);\n let nr = max(nib, 0.6);\n let c0 = mix(-2.0, L - capL * 0.55, cap); // the cap: from \u2026 to\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n var r = R;\n var part = 2.0;\n if (u < 3.2) { r = nr; part = 0.0; }\n else if (u < 14.0) { r = mix(nr + 0.8, R * 0.76, smoothstep(3.2, 14.0, u)); part = 1.0; }\n else if (u < 16.5) { r = R * 0.94; }\n else if (u > 44.0 && u < 47.0) { part = 3.0; }\n else if (u > L - 7.0) { r = R * 0.9; part = 5.0; }\n if (u >= c0 && u <= c1) {\n r = R + 0.9; part = 4.0;\n // the rim at the cap's open end (toward the barrel while it is on, toward the nib once posted), the clip along its top\n let atBack = u > c1 - 2.2;\n let atFront = u < c0 + 2.2;\n if (select(atFront, atBack, cap < 0.5)) { part = 7.0; }\n else if (w < 1.15 && u > c0 + 6.0 && u < c1 - 7.0) { part = 6.0; }\n }\n // a rounded box in (u, w): the nearer end's rounding, never past the tube's own radius\n let start = u < (u0 + u1) * 0.5;\n let rc0 = select(nr, R + 0.9, c0 < 0.5);\n let rc1 = select(2.4, R + 0.9, c1 > L - 0.5);\n let rc = min(select(rc1, rc0, start), r);\n let de = select(u - u1, u0 - u, start);\n let dw = w - r;\n let q = vec2f(de, dw) + vec2f(rc);\n let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rc;\n let s = select(clamp((u - (u1 - rc)) / max(rc, 1.0e-3), 0.0, 1.0), -clamp(((u0 + rc) - u) / max(rc, 1.0e-3), 0.0, 1.0), start);\n return vec4f(d, part, r, s);\n}\n\n// The pen's silhouette for its SHADOW: the same tube, its steps smoothed out \u2014 a shadow is soft,\n// and a step in the radius (the nib's holder, the cap's lip) would print a line across it.\nfn bd_pen_soft(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> f32 {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let c0 = mix(-2.0, L - capL * 0.55, cap);\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n let uu = clamp(m.x, u0, u1);\n let capped = smoothstep(c0 - 3.0, c0 + 3.0, uu) * (1.0 - smoothstep(c1 - 3.0, c1 + 3.0, uu));\n let tube = mix(max(nib, 0.6), R, smoothstep(0.0, 16.0, uu)) - 0.1 * R * smoothstep(L - 10.0, L - 4.0, uu);\n let r = mix(tube, R + 0.9, capped);\n return length(vec2f(m.x - uu, m.y)) - r;\n}\n\n// Its colour at a point of it, seen from above: a tube lying on its side \u2014 its across-position\n// turns the normal, and on a rounded end its along-position too; the lamp shades it and the\n// gloss of moulded plastic glints on it; the part picks the material. The clip is a little\n// half-round rod on the cap, lit on the lamp's side.\nfn bd_marker_colour(u: MatUniforms, k: BoardUniforms, sd: vec4f, across: f32, axis: vec2f, perp: vec2f, ink: vec3f, L: vec3f, gobo: f32, noise: f32) -> vec3f {\n var t = clamp(across / max(sd.z, 1.0e-3), -1.0, 1.0);\n if (sd.y == 6.0) { t = clamp(across / 1.15, -1.0, 1.0) * 0.85; }\n let s = sd.w;\n var n = normalize(vec3f(perp * t + axis * s, sqrt(max(1.0 - t * t - s * s, 0.02))));\n var albedo = k.barrel.rgb;\n var gloss = 0.3;\n if (sd.y == 0.0) { albedo = ink * 0.8; gloss = 0.0; } // the felt nib\n else if (sd.y == 1.0) { albedo = k.barrel.rgb * 0.8; } // the tip's holder, a greyer plastic\n else if (sd.y == 3.0) { albedo = ink; } // the stripe that names the ink\n else if (sd.y == 4.0) { albedo = ink; gloss = 0.45; } // the cap\n else if (sd.y == 5.0) { albedo = ink * 0.9; } // the end plug\n else if (sd.y == 6.0) { albedo = mix(ink, k.barrel.rgb, 0.18); gloss = 0.7; } // the clip\n else if (sd.y == 7.0) { albedo = ink * 0.7; } // the cap's rim\n // a small round thing wants more modelling than a flat face: the lamp's fill is less on it\n let lam = 0.32 + 0.68 * min(max(dot(n, L), 0.0) / max(L.z, 0.05), k.light.y);\n albedo = albedo * pow(lam, 1.0 / 2.2);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, noise, k.light.z);\n // the gloss: the lamp's glint where it can land, and \u2014 where it cannot (a lamp along the axis\n // has no mirror line on a tube) \u2014 the bright sky over the desk, a soft stripe along the top\n let toward = L.xy / max(length(L.xy), 1.0e-4);\n let sky = pow(max(dot(n, normalize(vec3f(toward * 0.35, 1.0))), 0.0), 18.0);\n return c + vec3f((bd_glint(n, L, 40.0) + 0.3 * bd_glint(n, L, 8.0) + 0.55 * sky) * gloss * bd_lit(u, k) * mix(0.45, 1.0, gobo));\n}\n\n// ---- the melamine's sheen\n\n// The window the lamp shines through, mirrored in the melamine and seen by an eye over the\n// board: a soft disc of sky toward the lamp, high up; Schlick's Fresnel on melamine's f0. The\n// eye rides the pointer (`B.sheen`), so the reflection slides as the hand moves.\nfn bd_sheen(B: Board, k: BoardUniforms, p: vec2f, n: vec3f) -> f32 {\n let eye = vec3f(B.centre + B.sheen * B.half, k.sheen.z * 2.0 * B.half.y);\n let V = normalize(eye - vec3f(p, 0.0));\n let R = reflect(-V, n);\n let toward = B.lamp.xy / max(length(B.lamp.xy), 1.0e-4);\n let W = normalize(vec3f(toward * 0.42, 0.91));\n let w = smoothstep(cos(k.sheen.y), 1.0, dot(R, W));\n let f = 0.045 + 0.955 * pow(1.0 - max(dot(n, V), 0.0), 5.0);\n return w * w * (f / 0.045);\n}\n\n// ---- the board\n\n// One board at a world point `p`: premultiplied colour. `px` is world units per DEVICE px, `css`\n// world units per CSS px; `frag` the framebuffer pixel (the blue noise's key).\nfn shade_board(B: Board, u: MatUniforms, k: BoardUniforms, p: vec2f, px: f32, css: f32, frag: vec2f,\n gobo_tex: texture_2d<f32>, gobo_samp: sampler, noise_tex: texture_2d<f32>, noise_samp: sampler) -> vec4f {\n let q = p - B.centre;\n let L = B.lamp.xyz;\n let S = B.slope;\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n let dOut = sdf_round_box(q, B.half, B.radius);\n var acc = vec4f(0.0);\n // what a shadow is made of: the world's darkest value by day (the design's cast), the dark's own grey by night\n let shadowInk = mix(k.castTint.rgb, bd_encode(u.eigengrau.xyz), clamp(u.night.x, 0.0, 1.0));\n\n // The slab's shadow on the mat, outside its footprint.\n if (dOut > -px) {\n let sh = bd_slab(q, B.half, B.radius, B.lift, B.lift + B.thick, S, k, 1.0, 6) * (1.0 - bd_cov(dOut, px));\n acc = vec4f(shadowInk * sh, sh);\n }\n\n let top = B.lift + B.thick; // the frame's top off the mat\n let face = top - B.recess; // the melamine's\n if (dOut < px) {\n let cov = bd_cov(dOut, px);\n let dIn = sdf_round_box(q, B.inner, B.innerR);\n var col: vec3f;\n if (dIn > 0.0) {\n // THE FRAME \u2014 a rounded bar: across the band the rims roll away, outward at the edge,\n // inward at the lip; brushed along its length; glinting where it turns to the lamp.\n let t = clamp(dIn / max(dIn - dOut, 1.0e-4), 0.0, 1.0);\n let e = 2.0 * t - 1.0;\n // a half-round bar: the normal turns steadily across it, steepest at the rims\n let theta = k.lip.w * sign(e) * pow(abs(e), 0.85);\n let g = bd_grad(q, B.half, B.radius);\n let n = normalize(vec3f(g * sin(theta), cos(theta)));\n let along = dot(q, vec2f(-g.y, g.x));\n let across = dot(q, g);\n let brushed = (felt_noise(vec2f(along * 0.045, across * 2.2)) + 0.5 * felt_noise(vec2f(along * 0.13 + 7.0, across * 6.1))) * k.lip.z;\n var albedo = B.metal.rgb + vec3f(brushed);\n albedo = albedo * pow(bd_lambert(n, L, k), 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, top), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n // the metal: a tight glint where the bar turns the lamp to the eye, and a broad sheen round it\n let lit = bd_lit(u, k) * mix(0.2, 1.0, gobo);\n col += vec3f((bd_glint(n, L, 90.0) + 0.6 * bd_glint(n, L, 14.0)) * k.glint.x * lit);\n // metal mirrors the sky: a face turned up and out catches it \u2014 the bar's shaded half is never dead\n col += vec3f(0.06 * smoothstep(0.2, 1.0, n.z) * bd_lit(u, k));\n } else {\n // THE MELAMINE \u2014 the raster over it: uv across the face, the ink's mip by the screen's density.\n let uv = (q + B.inner) / (2.0 * B.inner);\n let lod = max(log2(max(B.density * px / max(B.scale, 1.0e-3), 1.0e-6)), 0.0);\n var ink = board_ink(B, uv, lod); // (the entry's: where this board's ink is bound \u2014 K6a)\n if (B.laying > 0.5) {\n // the stroke being laid, shown as it will land\n let sc = board_stroke(B, uv);\n if (B.tool.w < 0.5) { ink = vec4f(ink.rgb * (1.0 - sc) + B.tool.rgb * sc, ink.a * (1.0 - sc) + sc); }\n else { ink = ink * (1.0 - sc); }\n }\n let cov2 = ink.a;\n var wet = 0.0;\n if (B.wet > 0.5) { wet = board_wet(B, uv) * smoothstep(0.0, 0.3, cov2); }\n\n // the face's normal: a slow waviness, and the ink film standing a few microns proud of it\n let dt = exp2(lod) / B.texels;\n let ax = board_ink(B, uv + vec2f(dt.x, 0.0), lod).a - board_ink(B, uv - vec2f(dt.x, 0.0), lod).a;\n let ay = board_ink(B, uv + vec2f(0.0, dt.y), lod).a - board_ink(B, uv - vec2f(0.0, dt.y), lod).a;\n let w1 = felt_noised(q * (1.0 / 80.0) + vec2f(3.1, 7.7)).yz;\n let w2 = felt_noised(q * (1.0 / 31.0) + vec2f(11.0, 5.0)).yz;\n let slope = (w1 + 0.5 * w2) * 0.006 + vec2f(ax, ay) * k.mel.w;\n let n = normalize(vec3f(-slope, 1.0));\n\n // the albedo: the melamine's tone and grain, the ghost of old erasures, the ink as a filter (linear) over it\n let tone = felt_noise(q * (1.0 / 140.0) + vec2f(20.0, 4.0)) * k.mel.x;\n let grain = (bn.x - 0.5) * k.mel.y;\n let ghost = smoothstep(0.1, 0.45, felt_fbm(q * (1.0 / 21.0) + vec2f(40.0, 3.0))) * k.mel.z;\n let base = (B.surface.rgb * (1.0 + tone) + vec3f(grain)) * (1.0 - ghost);\n let filt = vec3f(1.0 - cov2) + ink.rgb * mix(1.0, k.sheen.w, wet);\n var albedo = bd_encode(srgb_to_linear(clamp(base, vec3f(0.0), vec3f(1.0))) * filt);\n\n // the light on it: the lamp over the ink's relief, the frame's lip on the lamp's side, the corner's occlusion\n let dl = sdf_round_box(q - S * B.recess, B.inner, B.innerR);\n let lip = bd_blur(-dl, k.lip.y) * k.lip.x;\n let ao = 1.0 - 0.07 * (1.0 - smoothstep(0.0, 3.0, -dIn));\n albedo = albedo * pow(bd_lambert(n, L, k) * (1.0 - lip) * ao, 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n\n // the window in the glossy face \u2014 stronger on wet ink; it shows most where the face is darkest\n // (in the leaves' shade, on the ink), as a reflection does\n let sheen = bd_sheen(B, k, p, n) * k.sheen.x * (1.0 + 1.5 * wet) * bd_lit(u, k);\n col = col + vec3f(sheen) * (vec3f(1.0) - col * 0.55);\n }\n acc = bd_over(vec4f(col * cov, cov), acc);\n }\n\n // THE PEN \u2014 one marker in whatever pose the host gives it: lying capped on the melamine, in the\n // hand at the pointer with its cap posted, or on its way between (BOARD.md \xA75). Its shadow first\n // \u2014 the axis point at u stands h0 + u\xB7rise over the melamine and casts along the lamp's slope,\n // softened by that height; a lying pen has its contact line \u2014 then the pen itself.\n if (B.pen.w > 0.001) {\n let a = vec2f(cos(B.pen.z), sin(B.pen.z));\n let perp = vec2f(-a.y, a.x);\n let tip = B.pen.xy;\n let h0 = B.penPose.x;\n let rise = B.penPose.y;\n let cap = B.penPose.z;\n let nib = B.penPose.w;\n let pres = B.pen.w;\n if (distance(p, tip) < k.pen.x * 1.3 + 90.0) {\n let D = a + S * rise;\n let A0 = tip + S * h0;\n let c0 = mix(-2.0, k.pen.x - k.pen.z * 0.55, cap);\n let tt = clamp(dot(p - A0, D) / max(dot(D, D), 1.0e-6), min(0.0, c0), max(k.pen.x, c0 + k.pen.z));\n let hs = h0 + tt * rise;\n let rs = p - (A0 + D * tt);\n let msd = bd_pen_soft(vec2f(tt, length(rs)), cap, nib, k);\n let shade = bd_blur(msd, k.shadow.x + k.shadow.y * HAND_PENUMBRA * hs) * k.shadow.z * HAND_UMBRA;\n let rc = p - tip;\n let mc = vec2f(dot(rc, a), dot(rc, perp));\n // lying, it touches the melamine along a line: the contact shadow, gone as it lifts\n let lying = exp(-max(h0 - k.pen.y, 0.0) / 2.5) * (1.0 - smoothstep(0.0, 0.05, rise));\n let contact = bd_blur(bd_pen_soft(mc, cap, nib, k) + k.pen.y * 0.45, k.shadow2.x * 1.4) * 0.42 * lying;\n let msh = min(max(shade, contact), 1.0) * pres;\n acc = bd_over(vec4f(shadowInk * msh, msh), acc);\n let sd = bd_pen(mc, cap, nib, k);\n let mcov = bd_cov(sd.x, px) * pres;\n if (mcov > 0.0) {\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + h0 + max(mc.x, 0.0) * rise + k.pen.y), bn.z);\n let c = bd_marker_colour(u, k, sd, mc.y, a, perp, B.penInk.rgb, L, gobo, bn.y);\n acc = bd_over(vec4f(c * mcov, mcov), acc);\n }\n }\n }\n\n // THE ERASER in the hand: a felt block with a wooden back, square to its angle.\n if (B.eraser.w > 0.001 && distance(p, B.eraser.xy) < 120.0) {\n let ang = k.block2.x;\n let a = vec2f(cos(ang), sin(ang));\n let perp = vec2f(-a.y, a.x);\n let rc = p - B.eraser.xy;\n let qe = vec2f(dot(rc, a), dot(rc, perp));\n let half = k.block.xy;\n let gap = B.eraser.z;\n let hTop = gap + k.block.z + k.block.w;\n let pres = B.eraser.w;\n let sh = min(bd_slab(qe, half, 3.0, gap, hTop, vec2f(dot(S, a), dot(S, perp)), k, HAND_PENUMBRA, 14) * HAND_UMBRA, 1.0) * pres; // the slope, in the block's frame\n acc = bd_over(vec4f(shadowInk * sh, sh), acc);\n let dFelt = sdf_round_box(qe, half, 3.0);\n let fcov = bd_cov(dFelt, px) * pres;\n if (fcov > 0.0) {\n let dWood = sdf_round_box(qe, half - vec2f(1.5), 2.4);\n let gw = bd_grad(qe, half - vec2f(1.5), 2.4);\n let bevel = 1.0 - smoothstep(0.0, 2.2, -dWood);\n let gw3 = vec2f(gw.x * a.x - gw.y * a.y, gw.x * a.y + gw.y * a.x); // back to world axes\n let n = normalize(vec3f(gw3 * sin(bevel * 0.9), cos(bevel * 0.9)));\n let grain = felt_noise(vec2f(qe.x * 0.06, qe.y * 1.3)) * 0.05 + felt_noise(vec2f(qe.x * 0.21, qe.y * 3.7)) * 0.025;\n var albedo = k.felt.rgb;\n if (dWood < 0.0) {\n // the wooden back, its grain along the block, a finger groove routed round its top\n let groove = 1.0 - smoothstep(0.25, 0.9, abs(sdf_round_box(qe, half - vec2f(6.5, 3.4), 1.8)));\n albedo = (k.wood.rgb + vec3f(grain)) * pow(bd_lambert(n, L, k), 1.0 / 2.2) * (1.0 - 0.2 * groove);\n }\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + hTop), bn.z);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n acc = bd_over(vec4f(c * fcov, fcov), acc);\n }\n }\n\n // The sole-selection ring, inside the outer edge, in the theme's select.\n let ring = bd_line(dOut, k.shadow2.z * css, px) * B.ring;\n acc = bd_over(vec4f(k.select.rgb * ring, ring), acc);\n return acc * B.alpha;\n}\n",
|
|
685
685
|
"board/felt.wgsl": "// The FELT's noise (BOARD.md) \u2014 a PURE module: the research whiteboard's gradient noise\n// (research/whiteboard noise.wgsl) with its analytic derivatives, an integer hash so it is\n// the same on every GPU, prefixed `felt_` beside the mat's own `hash12` / `value_noise`. The\n// pen's fibre lanes read it in the stamp; the melamine's waviness and orange peel, the\n// frame's brushing and the eraser's wood read it on the desk.\n\nfn felt_hash(p: vec2u) -> u32 {\n var v = p.x * 1664525u + 1013904223u;\n v = v ^ (p.y * 22695477u + 374761393u);\n v = v ^ (v >> 16u);\n v = v * 0x7feb352du;\n v = v ^ (v >> 15u);\n v = v * 0x846ca68bu;\n v = v ^ (v >> 16u);\n return v;\n}\n\nfn felt_grad(i: vec2i) -> vec2f {\n let h = felt_hash(bitcast<vec2u>(i));\n let a = f32(h & 0xffffu) * (6.283185307 / 65536.0);\n return vec2f(cos(a), sin(a));\n}\n\n// Gradient noise with its derivatives: (value, d/dx, d/dy); the value roughly in [\u22120.7, 0.7].\nfn felt_noised(p: vec2f) -> vec3f {\n let i = vec2i(floor(p));\n let f = fract(p);\n let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n let du = 30.0 * f * f * (f * (f - 2.0) + 1.0);\n let ga = felt_grad(i);\n let gb = felt_grad(i + vec2i(1, 0));\n let gc = felt_grad(i + vec2i(0, 1));\n let gd = felt_grad(i + vec2i(1, 1));\n let va = dot(ga, f);\n let vb = dot(gb, f - vec2f(1.0, 0.0));\n let vc = dot(gc, f - vec2f(0.0, 1.0));\n let vd = dot(gd, f - vec2f(1.0, 1.0));\n let k = va - vb - vc + vd;\n let v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * k;\n let d = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)\n + du * (u.yx * k + vec2f(vb, vc) - va);\n return vec3f(v, d.x, d.y);\n}\n\nfn felt_noise(p: vec2f) -> f32 { return felt_noised(p).x; }\n\nfn felt_fbm(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var q = p;\n for (var i = 0; i < 4; i++) {\n v = v + a * felt_noise(q);\n q = q * 2.03 + vec2f(1.7, 9.2);\n a = a * 0.5;\n }\n return v;\n}\n",
|
|
686
686
|
"board/ink.wgsl": `// The INK \u2014 fullscreen passes over a board's raster (the research whiteboard's
|
|
@@ -3642,7 +3642,7 @@ function boardCard(text2) {
|
|
|
3642
3642
|
var BOARD_FRAGMENT = [
|
|
3643
3643
|
" let B = boards[slot];",
|
|
3644
3644
|
" let zoom = max(u.cam.z, 1.0e-12);",
|
|
3645
|
-
" let dpr = max(u.cam.w, 1.
|
|
3645
|
+
" let dpr = max(u.cam.w, 1.0e-6);",
|
|
3646
3646
|
" let px = 1.0 / (zoom * dpr); // world per device px",
|
|
3647
3647
|
" let css = 1.0 / zoom; // world per CSS px",
|
|
3648
3648
|
" let p = in.clip.xy * px + u.cam.xy; // device px \u2192 world",
|
|
@@ -3670,7 +3670,7 @@ var BoardKind = class _BoardKind {
|
|
|
3670
3670
|
prepare(_encoder, s, records, extra) {
|
|
3671
3671
|
const own = records.find((r) => r.materials !== void 0)?.materials;
|
|
3672
3672
|
if (own !== void 0 && this.pass.look !== own) this.pass.look = own;
|
|
3673
|
-
return this.pass.prepare(s.view, s.fadeIn, s.cfg, s.frame, records, s.present, s.light, s.theme, extra?.keys, s.target === "hand");
|
|
3673
|
+
return this.pass.prepare(s.view, s.fadeIn, s.cfg, s.frame, records, s.present, s.light, s.theme, extra?.keys, s.target === "hand" || s.target === "capture");
|
|
3674
3674
|
}
|
|
3675
3675
|
dropped() {
|
|
3676
3676
|
return this.pass.dropped;
|
|
@@ -7025,7 +7025,10 @@ function calendarKind(opts = {}) {
|
|
|
7025
7025
|
{ id: "today", label: "Today", kind: "action", keys: ["t"], hint: "T", glyph: "today", run: (api) => homeOf(api) },
|
|
7026
7026
|
{ id: "pen", label: "The pen", kind: "mode", keys: ["p"], hint: "P", glyph: "pen" }
|
|
7027
7027
|
],
|
|
7028
|
-
tool: () => "pen"
|
|
7028
|
+
tool: () => "pen",
|
|
7029
|
+
// the WORD in hand (I22 — MC-D9): the month the pad is turned to, by its name — the document's (a turn of the bar lands at once),
|
|
7030
|
+
// or with none chosen the month of the pad's today, as the sheet shows it
|
|
7031
|
+
readout: (c) => MONTH_NAMES[monthOf(monthOfKey(stringProp(c.props(), "month", "")) ?? monthOfDay(c.local?.today() ?? today())).m - 1]
|
|
7029
7032
|
},
|
|
7030
7033
|
resolve(ctx) {
|
|
7031
7034
|
const pads = ctx.local;
|
|
@@ -10447,7 +10450,10 @@ function notebookKind(opts = {}) {
|
|
|
10447
10450
|
page: (c) => c.local?.state(c.entity).face ?? 0,
|
|
10448
10451
|
tools: NOTEBOOK_TOOLS,
|
|
10449
10452
|
tool: () => penToolId(DEFAULT_PEN),
|
|
10450
|
-
swatches: (look) => look.swatches
|
|
10453
|
+
swatches: (look) => look.swatches,
|
|
10454
|
+
// the WORD in hand (I22 — MC-D9): the page the book is open at as the bar's ‹ › count it — "Page 1" at its first spread, one on
|
|
10455
|
+
// for each turn — from the spread a turn is heading for (its transaction not landed yet), else the document's
|
|
10456
|
+
readout: (c) => `Page ${(c.local?.state(c.entity).pending ?? clamp3(Math.round(numberProp(c.props(), "spread", 0)), 0, sheets)) + 1}`
|
|
10451
10457
|
},
|
|
10452
10458
|
theme(palette, _name) {
|
|
10453
10459
|
const n = palette.notebooks;
|
|
@@ -11854,7 +11860,7 @@ var PhotoKind = class _PhotoKind {
|
|
|
11854
11860
|
}
|
|
11855
11861
|
/** The pass's own `prepare`, argument for argument: the slot's camera, grid, clocks, the objects' presence, the light, the lamp — and the records' keys (D6). */
|
|
11856
11862
|
prepare(_encoder, s, records, extra) {
|
|
11857
|
-
return this.pass.prepare(s.view, s.fadeIn, s.cfg, s.frame, records, s.present, s.light, s.lit, extra?.keys, s.target === "hand");
|
|
11863
|
+
return this.pass.prepare(s.view, s.fadeIn, s.cfg, s.frame, records, s.present, s.light, s.lit, extra?.keys, s.target === "hand" || s.target === "capture");
|
|
11858
11864
|
}
|
|
11859
11865
|
dropped() {
|
|
11860
11866
|
return this.pass.dropped;
|