@vibecook/ice 0.16.0 → 0.17.0

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.
Files changed (52) hide show
  1. package/README.md +10 -5
  2. package/dist/{chunk-ZTY2VRIS.js → chunk-6W2FPXR7.js} +13 -7
  3. package/dist/chunk-6W2FPXR7.js.map +1 -0
  4. package/dist/{chunk-Y7K6CHSF.js → chunk-LAZIY3J7.js} +286 -189
  5. package/dist/chunk-LAZIY3J7.js.map +1 -0
  6. package/dist/{chunk-REJTEK3V.js → chunk-M2HUDENL.js} +288 -318
  7. package/dist/chunk-M2HUDENL.js.map +1 -0
  8. package/dist/{chunk-BH5AFWJX.js → chunk-VZEZ7CYW.js} +9 -13
  9. package/dist/chunk-VZEZ7CYW.js.map +1 -0
  10. package/dist/desk-kit.js +2 -2
  11. package/dist/desk-objects.js +45 -14
  12. package/dist/desk-objects.js.map +1 -1
  13. package/dist/desk.js +3 -3
  14. package/dist/devtools.js +1 -1
  15. package/dist/dom.js +2 -2
  16. package/dist/index.js +7 -1
  17. package/dist/react.js +2 -2
  18. package/dist/types/packages/core/src/canvas/engine-catalog.d.ts +6 -0
  19. package/dist/types/packages/core/src/catalog/desk.d.ts +6 -6
  20. package/dist/types/packages/core/src/catalog/gesture.d.ts +2 -0
  21. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +2 -0
  22. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +8 -4
  23. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +11 -5
  24. package/dist/types/packages/core/src/index.d.ts +1 -1
  25. package/dist/types/packages/core/src/ops/selection-drag.d.ts +8 -0
  26. package/dist/types/packages/core/src/settings/defaults.d.ts +2 -0
  27. package/dist/types/packages/core/src/systems/camera-sim.d.ts +13 -0
  28. package/dist/types/packages/core/src/systems/tray.d.ts +5 -4
  29. package/dist/types/packages/core/src/tools/define-tool.d.ts +2 -2
  30. package/dist/types/packages/core/src/widget/define-widget.d.ts +2 -1
  31. package/dist/types/packages/desk/src/ground.d.ts +4 -3
  32. package/dist/types/packages/desk/src/host/layer.d.ts +1 -3
  33. package/dist/types/packages/desk/src/mat/layout.d.ts +45 -5
  34. package/dist/types/packages/desk/src/mat/mat-pass.d.ts +7 -0
  35. package/dist/types/packages/desk/src/mat/ruler-regions.d.ts +17 -0
  36. package/dist/types/packages/desk/src/shaders.gen.d.ts +4 -4
  37. package/dist/types/packages/desk/src/theme.d.ts +0 -15
  38. package/dist/types/packages/desk/src/tray/drawer.d.ts +10 -8
  39. package/dist/types/packages/desk/src/tray/flux.d.ts +2 -3
  40. package/dist/types/packages/desk/src/tray/layout.d.ts +1 -6
  41. package/dist/types/packages/desk/src/tray/look.d.ts +0 -9
  42. package/dist/types/packages/desk/src/tray/pass.d.ts +7 -18
  43. package/dist/types/packages/desk/src/tray/specimens.d.ts +2 -19
  44. package/dist/types/packages/dom/src/pointer-adapter.d.ts +1 -1
  45. package/dist/types/packages/objects/src/minimat/layout.d.ts +1 -1
  46. package/dist/types/packages/objects/src/photo/photo.d.ts +2 -2
  47. package/dist/types/packages/objects/src/preset.d.ts +5 -3
  48. package/package.json +15 -16
  49. package/dist/chunk-BH5AFWJX.js.map +0 -1
  50. package/dist/chunk-REJTEK3V.js.map +0 -1
  51. package/dist/chunk-Y7K6CHSF.js.map +0 -1
  52. package/dist/chunk-ZTY2VRIS.js.map +0 -1
@@ -82,7 +82,7 @@ import {
82
82
  spring,
83
83
  themeFrom,
84
84
  tilted
85
- } from "./chunk-BH5AFWJX.js";
85
+ } from "./chunk-VZEZ7CYW.js";
86
86
  import {
87
87
  BLEND_OVER,
88
88
  bindGroup,
@@ -169,7 +169,7 @@ import {
169
169
  trayTakeProps,
170
170
  widgetSpawnInits,
171
171
  widgetTypeFor
172
- } from "./chunk-Y7K6CHSF.js";
172
+ } from "./chunk-LAZIY3J7.js";
173
173
  import {
174
174
  clientToScreen,
175
175
  cubicBezierEase,
@@ -626,13 +626,13 @@ var DRAWER = {
626
626
  /**
627
627
  * The HEADER (design-018 R4 — James, 2026-09-28: "make the fade stronger and more gap, and design the filters nicely at that empty
628
628
  * safe top space instead"): a CLEAR band this high under the face's top edge (inside the arris) where what hangs on the board — the
629
- * specimens, their shadows, their accessories and their tags — and the holes are wholly gone: the plain face, on which the bar lays
629
+ * specimens, their shadows and their tags — and the holes are wholly gone: the plain face, on which the bar lays
630
630
  * its category chips as label tape (`<TrayBar>`, @ice/react — the handle's `anchor().drawer.header`). The face's portal clip starts
631
631
  * this far down, so nothing of the content is even drawn above it.
632
632
  */
633
633
  header: 48,
634
634
  /**
635
- * The CONTENT's fade under the header (design-018 §4, R4): the specimens, their shadows, their accessories, their tags and the holes
635
+ * The CONTENT's fade under the header (design-018 §4, R4): the specimens, their shadows, their tags and the holes
636
636
  * fade in over this band (CSS px, 0.8 pitch) — `smoothstep(top + header, top + header + fade, y)`, `top` the face's top edge — the
637
637
  * feather on the face's portal clip (R1's 28 px band at the edge itself, stretched and moved down: D-R4.1). The board does not fade.
638
638
  */
@@ -783,22 +783,8 @@ var TrayUniforms = defineStruct("TrayUniforms", [
783
783
  // …its 210 (bump) · 42 · 105 (flecks)…
784
784
  ["keepEdge", "vec4f"],
785
785
  // …and the punched fibre's 9 · 18 · 36
786
- ["accessory", "vec4f"],
787
- // K5a: the accessories' powder coat, linear, and their shadows' strength on the board
788
786
  ["fade", "vec4f"]
789
- // the content's fade under the header (design-018 §4, R4): its band F, the clear header H above it, CSS px; a host's foot inset (I21; 0 none); —
790
- ]);
791
- var TrayAccessoryStruct = defineStruct("TrayAccessory", [
792
- ["box", "vec4f"],
793
- // the quad: x0, y0, x1, y1
794
- ["kind", "vec4f"],
795
- // the accessory (0 hook · 1 shelf · 2 clip · 3 rail), its pegs, how far it stands off the board (px), —
796
- ["rect", "vec4f"],
797
- // the specimen: x0, y0, x1, y1
798
- ["pegs0", "vec4f"],
799
- // pegs 0 and 1: x, y
800
- ["pegs1", "vec4f"]
801
- // pegs 2 and 3
787
+ // the content's fade under the header (design-018 §4, R4): its band F, the clear header H above it, CSS px; a host's foot inset (I21; 0 none); its fade band, CSS px
802
788
  ]);
803
789
 
804
790
  // ../desk/src/tray/shaders.ts
@@ -806,7 +792,7 @@ var TRAY_SHADER_FILES = { tray: "tray/tray.wgsl", trayPass: "tray/tray-pass.wgsl
806
792
  function trayShaders(text2) {
807
793
  const t = text2(TRAY_SHADER_FILES);
808
794
  return kitWgsl(["view", "sdf", "light"], {
809
- structs: [TrayUniforms, TrayAccessoryStruct],
795
+ structs: [TrayUniforms],
810
796
  modules: [{ label: "tray/tray.wgsl", text: t.tray }],
811
797
  entry: { label: "tray/tray-pass.wgsl", text: t.trayPass }
812
798
  }, text2);
@@ -1386,6 +1372,40 @@ var HoldPass = class _HoldPass {
1386
1372
  }
1387
1373
  };
1388
1374
 
1375
+ // ../desk/src/mat/ruler-regions.ts
1376
+ function rulerRegions(view, ruler2, previous) {
1377
+ const { width, height, dpr } = view;
1378
+ const { margin, band: band2, line } = ruler2;
1379
+ if (previous && previous.width === width && previous.height === height && previous.dpr === dpr && previous.margin === margin && previous.band === band2 && previous.line === line) return previous;
1380
+ const w = Math.max(1, Math.round(width * dpr));
1381
+ const h = Math.max(1, Math.round(height * dpr));
1382
+ const pad = line * dpr / 2 + 0.5;
1383
+ const clamp2 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
1384
+ const left = clamp2(Math.floor(margin * dpr - pad), 0, w);
1385
+ const top = clamp2(Math.floor(margin * dpr - pad), 0, h);
1386
+ const right = clamp2(Math.ceil((width - margin) * dpr + pad), left, w);
1387
+ const bottom = clamp2(Math.ceil((height - margin) * dpr + pad), top, h);
1388
+ const innerLeft = clamp2(Math.ceil((margin + band2) * dpr + pad), left, right);
1389
+ const innerTop = clamp2(Math.ceil((margin + band2) * dpr + pad), top, bottom);
1390
+ const innerRight = clamp2(Math.floor((width - margin) * dpr - pad), innerLeft, right);
1391
+ const innerBottom = clamp2(Math.floor((height - margin) * dpr - pad), innerTop, bottom);
1392
+ const surface2 = [
1393
+ [0, 0, w, top],
1394
+ [0, bottom, w, h - bottom],
1395
+ [0, top, left, bottom - top],
1396
+ [right, top, w - right, bottom - top],
1397
+ [innerLeft, innerTop, innerRight - innerLeft, innerBottom - innerTop]
1398
+ ];
1399
+ const edges = [
1400
+ [left, top, right - left, innerTop - top],
1401
+ [left, innerTop, innerLeft - left, innerBottom - innerTop],
1402
+ [innerRight, innerTop, right - innerRight, innerBottom - innerTop],
1403
+ [left, innerBottom, right - left, bottom - innerBottom]
1404
+ ];
1405
+ const nonempty = (r) => r[2] > 0 && r[3] > 0;
1406
+ return { width, height, dpr, margin, band: band2, line, surface: surface2.filter(nonempty), edges: edges.filter(nonempty) };
1407
+ }
1408
+
1389
1409
  // ../desk/src/mat/mat-pass.ts
1390
1410
  var PLATE_NAMES = ["c", "b"];
1391
1411
  var CuttingMat = class _CuttingMat {
@@ -1406,6 +1426,7 @@ var CuttingMat = class _CuttingMat {
1406
1426
  /** The view block as last uploaded (K7a: uploaded only when its bytes moved; a layered kind compares a layer's view by it). */
1407
1427
  sentView = new Uint8Array(MatUniforms.size);
1408
1428
  viewSent = false;
1429
+ ruler = null;
1409
1430
  device;
1410
1431
  shared;
1411
1432
  constructor(device, shared) {
@@ -1585,6 +1606,7 @@ var CuttingMat = class _CuttingMat {
1585
1606
  * wind pass if the silhouette it holds is stale. Returns whether it ran.
1586
1607
  */
1587
1608
  prepare(encoder, view, fadeIn, cfg, frame, present, light = DAY_LIGHT, lit) {
1609
+ this.ruler = cfg.ruler.on ? rulerRegions(view, cfg.ruler, this.ruler) : null;
1588
1610
  const plate = PLATE_NAMES.includes(cfg.gobo.plate) ? cfg.gobo.plate : "c";
1589
1611
  const strength = MAT_GRID.gobo.plates[plate].strength;
1590
1612
  this.uniforms.set(matUniformValues(view, fadeIn, { ...cfg, gobo: { ...cfg.gobo, plate } }, frame, strength, present, light, this.shared.glyphMeta, lit));
@@ -1625,6 +1647,37 @@ var CuttingMat = class _CuttingMat {
1625
1647
  pass.setBindGroup(0, this.matGroup);
1626
1648
  pass.draw(3);
1627
1649
  }
1650
+ /** The mat beneath objects; enabled ruler strips are reserved for the foreground. */
1651
+ drawSurface(pass, clip) {
1652
+ if (!this.ruler) {
1653
+ this.draw(pass);
1654
+ return;
1655
+ }
1656
+ this.drawRegions(pass, this.ruler.surface, clip);
1657
+ }
1658
+ /** The edge bands and frame above every object in this slot, within its portal. */
1659
+ drawRulers(pass, clip) {
1660
+ if (this.ruler) this.drawRegions(pass, this.ruler.edges, clip);
1661
+ }
1662
+ drawRegions(pass, regions, clip) {
1663
+ const [cx, cy, cw, ch] = clip;
1664
+ let bound = false;
1665
+ for (const [rx, ry, rw, rh] of regions) {
1666
+ const x = Math.max(cx, rx);
1667
+ const y = Math.max(cy, ry);
1668
+ const right = Math.min(cx + cw, rx + rw);
1669
+ const bottom = Math.min(cy + ch, ry + rh);
1670
+ if (right <= x || bottom <= y) continue;
1671
+ if (!bound) {
1672
+ pass.setPipeline(this.shared.matPipeline);
1673
+ pass.setBindGroup(0, this.matGroup);
1674
+ bound = true;
1675
+ }
1676
+ pass.setScissorRect(x, y, right - x, bottom - y);
1677
+ pass.draw(3);
1678
+ }
1679
+ if (bound) pass.setScissorRect(cx, cy, cw, ch);
1680
+ }
1628
1681
  /** This slot's buffers; the shared plates go with the last slot standing. */
1629
1682
  dispose() {
1630
1683
  this.uniformBuf.destroy();
@@ -2143,14 +2196,212 @@ var TRAY_LOOK = {
2143
2196
  })(),
2144
2197
  /** The lamp's angular radius, rad. */
2145
2198
  lampSize: TRAY.lampSize,
2146
- /** K5a: the accessories' powder coat, linear; each accessory's standing-off (pitches); their shadows' strength. */
2147
- accessory: linear(rgb(TRAY.accessory.css)),
2148
- accessoryHeight: TRAY.accessory.height,
2149
- accessoryShadow: TRAY.accessory.shadow,
2150
2199
  /** K5a: a hovered specimen's lift — a share of its kind's hold. */
2151
2200
  hoverLift: TRAY.hoverLift
2152
2201
  };
2153
2202
 
2203
+ // ../desk/src/tray/pass.ts
2204
+ var BLEND_PREMUL5 = {
2205
+ color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
2206
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
2207
+ };
2208
+ function pcg(v) {
2209
+ const s = Math.imul(v >>> 0, 747796405) + 2891336453 >>> 0;
2210
+ const w = Math.imul((s >>> (s >>> 28) + 4 ^ s) >>> 0, 277803737) >>> 0;
2211
+ return (w >>> 22 ^ w) >>> 0;
2212
+ }
2213
+ var HASH_SIZE = 256;
2214
+ function hashTexels() {
2215
+ const N = HASH_SIZE;
2216
+ const h = new Uint8Array(N * N);
2217
+ for (let j = 0; j < N; j++) {
2218
+ const r = pcg(j);
2219
+ for (let i = 0; i < N; i++) h[j * N + i] = pcg((i ^ r) >>> 0) >>> 24;
2220
+ }
2221
+ const out = new Uint8Array(N * N * 4);
2222
+ for (let j = 0; j < N; j++) {
2223
+ for (let i = 0; i < N; i++) {
2224
+ const i1 = (i + 1) % N;
2225
+ const j1 = (j + 1) % N;
2226
+ const o = (j * N + i) * 4;
2227
+ out[o] = h[j * N + i];
2228
+ out[o + 1] = h[j * N + i1];
2229
+ out[o + 2] = h[j1 * N + i];
2230
+ out[o + 3] = h[j1 * N + i1];
2231
+ }
2232
+ }
2233
+ return out;
2234
+ }
2235
+ function keep(fp, F) {
2236
+ const x = Math.min(Math.max((fp * F - 0.25) / 0.25, 0), 1);
2237
+ return 1 - x * x * (3 - 2 * x);
2238
+ }
2239
+ var TrayPass = class _TrayPass {
2240
+ device;
2241
+ mat;
2242
+ pipeline;
2243
+ /** The VEIL (design-018 rev 5): the plain board laid over everything the drawer holds at its top — its own entries on the same layout. */
2244
+ veilPipeline;
2245
+ layout;
2246
+ sampler;
2247
+ trayBuf;
2248
+ lightBuf;
2249
+ hash;
2250
+ tray = TrayUniforms.alloc(1);
2251
+ light = MatUniforms.alloc(1);
2252
+ sentTray = new Uint8Array(TrayUniforms.size);
2253
+ sentLight = new Uint8Array(MatUniforms.size);
2254
+ sent = false;
2255
+ /** The quads the last prepare laid: 0 while the drawer is shut (p 0) — then it draws nothing. */
2256
+ quads = 0;
2257
+ /** A host's foot inset this frame (I21): the veil lays a second quad, over the foot, in its one draw. */
2258
+ foot = 0;
2259
+ group = null;
2260
+ boundAssets = -1;
2261
+ last = null;
2262
+ constructor(device, mat, pipelines, layout) {
2263
+ this.device = device;
2264
+ this.mat = mat;
2265
+ [this.pipeline, this.veilPipeline] = pipelines;
2266
+ this.layout = layout;
2267
+ this.sampler = device.createSampler({ label: "tray/pegboard/noise", magFilter: "linear", minFilter: "linear", addressModeU: "repeat", addressModeV: "repeat" });
2268
+ this.trayBuf = uniformBuffer(device, TrayUniforms.size, "tray/pegboard/uniforms");
2269
+ this.lightBuf = uniformBuffer(device, MatUniforms.size, "tray/pegboard/light");
2270
+ this.hash = device.createTexture({ label: "tray/pegboard/hash", size: [HASH_SIZE, HASH_SIZE], format: "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST });
2271
+ device.queue.writeTexture({ texture: this.hash }, hashTexels(), { bytesPerRow: HASH_SIZE * 4 }, [HASH_SIZE, HASH_SIZE]);
2272
+ }
2273
+ /** The pipeline on `format`, sampling `mat`'s blue noise (the root's: its assets are every slot's). */
2274
+ static async create(device, format, src, mat) {
2275
+ const module = await compile(device, compose(src));
2276
+ const layout = bindLayout(device, [
2277
+ { binding: 0, stages: ["fragment"], buffer: "uniform" },
2278
+ { binding: 1, stages: ["vertex", "fragment"], buffer: "uniform" },
2279
+ { binding: 2, stages: ["fragment"], texture: "float" },
2280
+ { binding: 3, stages: ["fragment"], sampler: "filtering" },
2281
+ { binding: 4, stages: ["fragment"], texture: "float" }
2282
+ ], "tray/pegboard");
2283
+ const pl = device.createPipelineLayout({ label: "tray/pegboard", bindGroupLayouts: [layout] });
2284
+ const pipelines = await Promise.all([
2285
+ renderPipeline(device, { label: "tray/pegboard", layout: pl, module, format, blend: BLEND_PREMUL5 }),
2286
+ renderPipeline(device, { label: "tray/pegboard/veil", layout: pl, module, format, blend: BLEND_PREMUL5, vertex: "vs_veil", fragment: "fs_veil" })
2287
+ ]);
2288
+ return new _TrayPass(device, mat, pipelines, layout);
2289
+ }
2290
+ /**
2291
+ * Lay the drawer out for this frame and upload what changed (before the frame's pass begins): the view, the theme's light, the
2292
+ * root grid's grade, the frame's noise offset. Returns the quads that will draw: one (the view whole while it dims) — none while
2293
+ * the drawer is shut (p 0: it and its shadows lie below the view — nothing is uploaded, nothing drawn; design-018 §5).
2294
+ */
2295
+ prepare(view, theme, grid, frame, inputs) {
2296
+ const P = DRAWER.pitch;
2297
+ const p = Math.min(Math.max(inputs.p, 0), 1);
2298
+ const rect = drawerRect(view.width, view.height, p);
2299
+ const { rowBase, frac } = carry(inputs.scroll, P);
2300
+ const foot = inputs.foot !== void 0 && inputs.foot > 0 ? inputs.foot : 0;
2301
+ this.foot = foot;
2302
+ if (p <= 0) {
2303
+ this.last = { rect, rowBase, frac, dim: 0, ...foot > 0 ? { foot } : {} };
2304
+ this.quads = 0;
2305
+ return 0;
2306
+ }
2307
+ const L = TRAY_LOOK.lamp;
2308
+ const g = Math.hypot(L[0], L[1]) || 1;
2309
+ const S = DRAWER.shadow.lamp;
2310
+ const Rm = DRAWER.shadow.room;
2311
+ const fp = 1 / (P * view.dpr);
2312
+ const dim = (DRAWER.dim.day + (DRAWER.dim.night - DRAWER.dim.day) * Math.min(Math.max(theme.matLight.night, 0), 1)) * p;
2313
+ const T = TRAY_LOOK;
2314
+ const values = {
2315
+ view: [view.width, view.height, view.dpr, P],
2316
+ rect: [rect.x, rect.y, rect.w, rect.h],
2317
+ rowBase,
2318
+ frac,
2319
+ fp,
2320
+ dim,
2321
+ shape: [DRAWER.radius, DRAWER.arris, DRAWER.inner.alpha, DRAWER.inner.width],
2322
+ hole: [PEG.holeR, PEG.holeHalf, PEG.rimK, PEG.border],
2323
+ depth: [PEG.thick, PEG.gap, PEG.colPhase, PEG.rowPhase],
2324
+ lamp: [L[0], L[1], L[2], T.lampSize],
2325
+ room: [Rm.sigma, Rm.alpha, 0, 0],
2326
+ shadow: [S.sigma, S.alpha, -L[0] / g * S.push, -L[1] / g * S.push],
2327
+ face: [...T.face, 0],
2328
+ faceSrgb: [...T.faceSrgb, 0],
2329
+ edge: [...T.edge, 0],
2330
+ cavity: [T.cavity.edge, T.cavity.heart, T.cavity.width, 0],
2331
+ keepFace: [keep(fp, 6 * Math.sqrt(10)), 0, keep(fp, 85), keep(fp, 190)],
2332
+ // the flecks fade by their cell; their strokes are anti-aliased in the shader (tray.wgsl `peg_flecks`)
2333
+ keepFine: [keep(fp, 210), keep(fp, 42), keep(fp, 105), 0],
2334
+ keepEdge: [keep(fp, 9), keep(fp, 18), keep(fp, 36), 0],
2335
+ fade: [DRAWER.fade, DRAWER.header, foot, Math.max(0, inputs.footFade ?? DRAWER.fade)]
2336
+ };
2337
+ this.tray.set(values);
2338
+ const gobo = grid.mat.gobo;
2339
+ const noise = frame?.noise ?? [0, 0];
2340
+ const light = {
2341
+ gobo: [gobo.opacity, gobo.blurTexels, gobo.shadeMix, gobo.darkFloor],
2342
+ grade: [gobo.saturate, 0, 0, 0],
2343
+ noise: [noise[0], noise[1], 1 / NOISE_SIZE, 0],
2344
+ ...lightValues(theme.matLight)
2345
+ };
2346
+ this.light.set(light);
2347
+ this.upload(this.trayBuf, this.tray.view(), this.sentTray);
2348
+ this.upload(this.lightBuf, this.light.view(), this.sentLight);
2349
+ this.sent = true;
2350
+ this.last = { rect, rowBase, frac, dim, ...foot > 0 ? { foot } : {} };
2351
+ this.quads = 1;
2352
+ return 1;
2353
+ }
2354
+ /** A block to the GPU only when its bytes moved since the last upload (a drawer at rest costs no write). */
2355
+ upload(buf, bytes, sent) {
2356
+ let same = this.sent;
2357
+ for (let i = 0; same && i < bytes.length; i++) if (bytes[i] !== sent[i]) same = false;
2358
+ if (same) return;
2359
+ sent.set(bytes);
2360
+ this.device.queue.writeBuffer(buf, 0, bytes);
2361
+ }
2362
+ /** A pipeline and the group, bound (the group remade when the mat's assets change). */
2363
+ bind(pass, pipeline) {
2364
+ if (this.group === null || this.boundAssets !== this.mat.assetVersion) {
2365
+ this.group = bindGroup(this.device, this.layout, [this.lightBuf, this.trayBuf, this.mat.noiseTexture.createView(), this.sampler, this.hash.createView()], "tray/pegboard");
2366
+ this.boundAssets = this.mat.assetVersion;
2367
+ }
2368
+ pass.setPipeline(pipeline);
2369
+ pass.setBindGroup(0, this.group);
2370
+ }
2371
+ /**
2372
+ * The drawer UNDER its specimens into the open pass — over everything drawn so far (the caller left the scissor on the whole view):
2373
+ * the dim, the drawer's shadows, and the board to its edge.
2374
+ */
2375
+ draw(pass) {
2376
+ if (this.quads === 0) return;
2377
+ pass.pushDebugGroup("tray/pegboard");
2378
+ this.bind(pass, this.pipeline);
2379
+ pass.draw(6, 1, 0, 0);
2380
+ pass.popDebugGroup();
2381
+ }
2382
+ /**
2383
+ * THE VEIL (design-018 rev 5) — LAST, over everything the drawer holds (the caller left the scissor on the whole view): the plain board
2384
+ * whole in the header and fading out over its ramp, so what hangs there and the holes behind it fade into the board together — a
2385
+ * specimen never turns see-through over a hole. One quad — and with a host's foot (I21) a second, over the foot, in the same draw.
2386
+ */
2387
+ drawVeil(pass) {
2388
+ if (this.quads === 0) return;
2389
+ pass.pushDebugGroup("tray/pegboard/veil");
2390
+ this.bind(pass, this.veilPipeline);
2391
+ pass.draw(this.foot > 0 ? 12 : 6, 1, 0, 0);
2392
+ pass.popDebugGroup();
2393
+ }
2394
+ /** What the last prepare laid (a rig's witness). */
2395
+ get laid() {
2396
+ return this.last;
2397
+ }
2398
+ dispose() {
2399
+ this.trayBuf.destroy();
2400
+ this.lightBuf.destroy();
2401
+ this.hash.destroy();
2402
+ }
2403
+ };
2404
+
2154
2405
  // ../desk/src/tray/carry.ts
2155
2406
  var CARRY = {
2156
2407
  /** The copy at its full lift is this much larger than its specimen, about the grab point. */
@@ -2385,8 +2636,6 @@ function specimenFrames(specimens, drawn, env) {
2385
2636
  };
2386
2637
  const record = recordOf(s.kind, ctx, env.faults);
2387
2638
  if (record === void 0) continue;
2388
- const hx = x0 + s.rect.w / 2;
2389
- const hy = s.accessory === "shelf" ? y0 + s.rect.h : y0;
2390
2639
  out.push({
2391
2640
  type: s.type,
2392
2641
  kind: s.kind.name,
@@ -2396,53 +2645,11 @@ function specimenFrames(specimens, drawn, env) {
2396
2645
  grid,
2397
2646
  screen: { x0, y0, x1: x0 + s.rect.w, y1: y0 + s.rect.h },
2398
2647
  object: { x0: drawn.rect.x + fit.x, y0: drawn.rect.y + fit.y - drawn.scroll, x1: drawn.rect.x + fit.x + fit.w, y1: drawn.rect.y + fit.y + fit.h - drawn.scroll },
2399
- pegs: s.pegs.map(([dx, dy]) => [hx + dx * P, hy + dy * P]),
2400
- accessory: s.accessory,
2401
2648
  label: s.label
2402
2649
  });
2403
2650
  }
2404
2651
  return out;
2405
2652
  }
2406
- var ACCESSORY_INDEX = { hook: 0, shelf: 1, clip: 2, rail: 3 };
2407
- function accessoryOf(f) {
2408
- const P = DRAWER.pitch;
2409
- const lift = TRAY_LOOK.accessoryHeight[f.accessory] * P;
2410
- const L = TRAY_LOOK.lamp;
2411
- const blur = TRAY_LOOK.lampSize * lift / L[2] + 2;
2412
- const pushX = Math.max(0, -L[0] / L[2] * lift);
2413
- const pushY = Math.max(0, -L[1] / L[2] * lift);
2414
- const r = f.screen;
2415
- let x0 = Number.POSITIVE_INFINITY;
2416
- let y0 = Number.POSITIVE_INFINITY;
2417
- let x1 = Number.NEGATIVE_INFINITY;
2418
- let y1 = Number.NEGATIVE_INFINITY;
2419
- const add = (a, b, c, d) => {
2420
- x0 = Math.min(x0, a);
2421
- y0 = Math.min(y0, b);
2422
- x1 = Math.max(x1, c);
2423
- y1 = Math.max(y1, d);
2424
- };
2425
- for (const [x, y] of f.pegs) {
2426
- add(x - 0.35 * P, y - 0.4 * P, x + 0.35 * P, y + 0.4 * P);
2427
- if (f.accessory === "hook") add(x - 0.15 * P, y, x + 0.15 * P, y + 0.7 * P);
2428
- if (f.accessory === "clip") add(x - 0.3 * P, y - 0.3 * P, x + 0.3 * P, Math.max(y, r.y0) + 0.6 * P);
2429
- if (f.accessory === "shelf") add(x - 0.15 * P, Math.min(y, r.y1), x + 0.15 * P, Math.max(y, r.y1) + 0.1 * P);
2430
- }
2431
- if (f.accessory === "shelf") add(r.x0 - 0.35 * P, r.y1 - 0.05 * P, r.x1 + 0.35 * P, r.y1 + 0.3 * P);
2432
- if (f.accessory === "rail" && f.pegs.length > 0) {
2433
- const xs = f.pegs.map((q) => q[0]);
2434
- const y = f.pegs[0][1];
2435
- add(Math.min(...xs) - 0.45 * P, y - 0.2 * P, Math.max(...xs) + 0.45 * P, y + 0.2 * P);
2436
- }
2437
- const peg = (i) => f.pegs[i] ?? [0, 0];
2438
- return {
2439
- box: [x0 - blur, y0 - blur, x1 + pushX + blur, y1 + pushY + blur],
2440
- kind: [ACCESSORY_INDEX[f.accessory], Math.min(f.pegs.length, 4), lift, 0],
2441
- rect: [r.x0, r.y0, r.x1, r.y1],
2442
- pegs0: [...peg(0), ...peg(1)],
2443
- pegs1: [...peg(2), ...peg(3)]
2444
- };
2445
- }
2446
2653
  var carrySlot = (type) => `carry:${type}`;
2447
2654
  function carriedFrame(pose, src, env) {
2448
2655
  const v = carryView(pose, src.rect);
@@ -2611,247 +2818,6 @@ var TraySlots = class {
2611
2818
  }
2612
2819
  };
2613
2820
 
2614
- // ../desk/src/tray/pass.ts
2615
- var BLEND_PREMUL5 = {
2616
- color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
2617
- alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
2618
- };
2619
- function pcg(v) {
2620
- const s = Math.imul(v >>> 0, 747796405) + 2891336453 >>> 0;
2621
- const w = Math.imul((s >>> (s >>> 28) + 4 ^ s) >>> 0, 277803737) >>> 0;
2622
- return (w >>> 22 ^ w) >>> 0;
2623
- }
2624
- var HASH_SIZE = 256;
2625
- function hashTexels() {
2626
- const N = HASH_SIZE;
2627
- const h = new Uint8Array(N * N);
2628
- for (let j = 0; j < N; j++) {
2629
- const r = pcg(j);
2630
- for (let i = 0; i < N; i++) h[j * N + i] = pcg((i ^ r) >>> 0) >>> 24;
2631
- }
2632
- const out = new Uint8Array(N * N * 4);
2633
- for (let j = 0; j < N; j++) {
2634
- for (let i = 0; i < N; i++) {
2635
- const i1 = (i + 1) % N;
2636
- const j1 = (j + 1) % N;
2637
- const o = (j * N + i) * 4;
2638
- out[o] = h[j * N + i];
2639
- out[o + 1] = h[j * N + i1];
2640
- out[o + 2] = h[j1 * N + i];
2641
- out[o + 3] = h[j1 * N + i1];
2642
- }
2643
- }
2644
- return out;
2645
- }
2646
- function keep(fp, F) {
2647
- const x = Math.min(Math.max((fp * F - 0.25) / 0.25, 0), 1);
2648
- return 1 - x * x * (3 - 2 * x);
2649
- }
2650
- var TrayPass = class _TrayPass {
2651
- device;
2652
- mat;
2653
- pipeline;
2654
- /** K5a: the accessories' own fragment entry on the same layout (a branch in one entry slowed the board). */
2655
- accPipeline;
2656
- /** The VEIL (design-018 rev 5): the plain board laid over everything the drawer holds at its top — its own entries on the same layout. */
2657
- veilPipeline;
2658
- layout;
2659
- sampler;
2660
- trayBuf;
2661
- lightBuf;
2662
- hash;
2663
- tray = TrayUniforms.alloc(1);
2664
- light = MatUniforms.alloc(1);
2665
- sentTray = new Uint8Array(TrayUniforms.size);
2666
- sentLight = new Uint8Array(MatUniforms.size);
2667
- sent = false;
2668
- /** The quads the last prepare laid: 0 while the drawer is shut (p 0) — then it draws nothing. */
2669
- quads = 0;
2670
- /** A host's foot inset this frame (I21): the veil lays a second quad, over the foot, in its one draw. */
2671
- foot = 0;
2672
- group = null;
2673
- boundAssets = -1;
2674
- last = null;
2675
- // the accessories (K5a): one record per specimen, grown by doubling; uploaded only when their bytes moved
2676
- acc = TrayAccessoryStruct.alloc(8);
2677
- accBuf;
2678
- accSent = new Uint8Array(0);
2679
- accCount = 0;
2680
- constructor(device, mat, pipelines, layout) {
2681
- this.device = device;
2682
- this.mat = mat;
2683
- [this.pipeline, this.accPipeline, this.veilPipeline] = pipelines;
2684
- this.layout = layout;
2685
- this.sampler = device.createSampler({ label: "tray/pegboard/noise", magFilter: "linear", minFilter: "linear", addressModeU: "repeat", addressModeV: "repeat" });
2686
- this.trayBuf = uniformBuffer(device, TrayUniforms.size, "tray/pegboard/uniforms");
2687
- this.lightBuf = uniformBuffer(device, MatUniforms.size, "tray/pegboard/light");
2688
- this.accBuf = storageBuffer(device, TrayAccessoryStruct.size * 8, "tray/pegboard/accessories");
2689
- this.hash = device.createTexture({ label: "tray/pegboard/hash", size: [HASH_SIZE, HASH_SIZE], format: "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST });
2690
- device.queue.writeTexture({ texture: this.hash }, hashTexels(), { bytesPerRow: HASH_SIZE * 4 }, [HASH_SIZE, HASH_SIZE]);
2691
- }
2692
- /** The pipeline on `format`, sampling `mat`'s blue noise (the root's: its assets are every slot's). */
2693
- static async create(device, format, src, mat) {
2694
- const module = await compile(device, compose(src));
2695
- const layout = bindLayout(device, [
2696
- { binding: 0, stages: ["fragment"], buffer: "uniform" },
2697
- { binding: 1, stages: ["vertex", "fragment"], buffer: "uniform" },
2698
- { binding: 2, stages: ["fragment"], texture: "float" },
2699
- { binding: 3, stages: ["fragment"], sampler: "filtering" },
2700
- { binding: 4, stages: ["fragment"], texture: "float" },
2701
- { binding: 5, stages: ["vertex", "fragment"], buffer: "read-only-storage" }
2702
- ], "tray/pegboard");
2703
- const pl = device.createPipelineLayout({ label: "tray/pegboard", bindGroupLayouts: [layout] });
2704
- const pipelines = await Promise.all([
2705
- renderPipeline(device, { label: "tray/pegboard", layout: pl, module, format, blend: BLEND_PREMUL5 }),
2706
- renderPipeline(device, { label: "tray/pegboard/accessories", layout: pl, module, format, blend: BLEND_PREMUL5, fragment: "fs_accessory" }),
2707
- renderPipeline(device, { label: "tray/pegboard/veil", layout: pl, module, format, blend: BLEND_PREMUL5, vertex: "vs_veil", fragment: "fs_veil" })
2708
- ]);
2709
- return new _TrayPass(device, mat, pipelines, layout);
2710
- }
2711
- /**
2712
- * Lay the drawer out for this frame and upload what changed (before the frame's pass begins): the view, the theme's light, the
2713
- * root grid's grade, the frame's noise offset. Returns the quads that will draw: one (the view whole while it dims) — none while
2714
- * the drawer is shut (p 0: it and its shadows lie below the view — nothing is uploaded, nothing drawn; design-018 §5).
2715
- */
2716
- prepare(view, theme, grid, frame, inputs) {
2717
- const P = DRAWER.pitch;
2718
- const p = Math.min(Math.max(inputs.p, 0), 1);
2719
- const rect = drawerRect(view.width, view.height, p);
2720
- const { rowBase, frac } = carry(inputs.scroll, P);
2721
- const foot = inputs.foot !== void 0 && inputs.foot > 0 ? inputs.foot : 0;
2722
- this.foot = foot;
2723
- if (p <= 0) {
2724
- this.last = { rect, rowBase, frac, dim: 0, accessories: [], ...foot > 0 ? { foot } : {} };
2725
- this.quads = 0;
2726
- return 0;
2727
- }
2728
- const L = TRAY_LOOK.lamp;
2729
- const g = Math.hypot(L[0], L[1]) || 1;
2730
- const S = DRAWER.shadow.lamp;
2731
- const Rm = DRAWER.shadow.room;
2732
- const fp = 1 / (P * view.dpr);
2733
- const dim = (DRAWER.dim.day + (DRAWER.dim.night - DRAWER.dim.day) * Math.min(Math.max(theme.matLight.night, 0), 1)) * p;
2734
- const T = TRAY_LOOK;
2735
- const values = {
2736
- view: [view.width, view.height, view.dpr, P],
2737
- rect: [rect.x, rect.y, rect.w, rect.h],
2738
- rowBase,
2739
- frac,
2740
- fp,
2741
- dim,
2742
- shape: [DRAWER.radius, DRAWER.arris, DRAWER.inner.alpha, DRAWER.inner.width],
2743
- hole: [PEG.holeR, PEG.holeHalf, PEG.rimK, PEG.border],
2744
- depth: [PEG.thick, PEG.gap, PEG.colPhase, PEG.rowPhase],
2745
- lamp: [L[0], L[1], L[2], T.lampSize],
2746
- room: [Rm.sigma, Rm.alpha, 0, 0],
2747
- shadow: [S.sigma, S.alpha, -L[0] / g * S.push, -L[1] / g * S.push],
2748
- face: [...T.face, 0],
2749
- faceSrgb: [...T.faceSrgb, 0],
2750
- edge: [...T.edge, 0],
2751
- cavity: [T.cavity.edge, T.cavity.heart, T.cavity.width, 0],
2752
- keepFace: [keep(fp, 6 * Math.sqrt(10)), 0, keep(fp, 85), keep(fp, 190)],
2753
- // the flecks fade by their cell; their strokes are anti-aliased in the shader (tray.wgsl `peg_flecks`)
2754
- keepFine: [keep(fp, 210), keep(fp, 42), keep(fp, 105), 0],
2755
- keepEdge: [keep(fp, 9), keep(fp, 18), keep(fp, 36), 0],
2756
- accessory: [...T.accessory, T.accessoryShadow],
2757
- fade: [DRAWER.fade, DRAWER.header, foot, 0]
2758
- };
2759
- this.tray.set(values);
2760
- const gobo = grid.mat.gobo;
2761
- const noise = frame?.noise ?? [0, 0];
2762
- const light = {
2763
- gobo: [gobo.opacity, gobo.blurTexels, gobo.shadeMix, gobo.darkFloor],
2764
- grade: [gobo.saturate, 0, 0, 0],
2765
- noise: [noise[0], noise[1], 1 / NOISE_SIZE, 0],
2766
- ...lightValues(theme.matLight)
2767
- };
2768
- this.light.set(light);
2769
- this.upload(this.trayBuf, this.tray.view(), this.sentTray);
2770
- this.upload(this.lightBuf, this.light.view(), this.sentLight);
2771
- this.sent = true;
2772
- const accessories = (inputs.specimens ?? []).map(accessoryOf);
2773
- this.layAccessories(accessories);
2774
- this.last = { rect, rowBase, frac, dim, accessories, ...foot > 0 ? { foot } : {} };
2775
- this.quads = 1;
2776
- return 1;
2777
- }
2778
- /** The accessories' records to the GPU — grown by doubling, written only when their bytes moved (a drawer at rest costs no write). */
2779
- layAccessories(list) {
2780
- if (list.length > this.acc.count) {
2781
- let n = this.acc.count;
2782
- while (n < list.length) n *= 2;
2783
- this.acc = TrayAccessoryStruct.alloc(n);
2784
- this.accBuf.destroy();
2785
- this.accBuf = storageBuffer(this.device, TrayAccessoryStruct.size * n, "tray/pegboard/accessories");
2786
- this.accSent = new Uint8Array(0);
2787
- this.group = null;
2788
- }
2789
- for (let i = 0; i < list.length; i++) this.acc.set(list[i], i);
2790
- this.accCount = list.length;
2791
- if (list.length === 0) return;
2792
- const bytes = this.acc.view(list.length);
2793
- let same = bytes.length === this.accSent.length;
2794
- for (let i = 0; same && i < bytes.length; i++) if (bytes[i] !== this.accSent[i]) same = false;
2795
- if (same) return;
2796
- this.accSent = bytes.slice();
2797
- this.device.queue.writeBuffer(this.accBuf, 0, bytes);
2798
- }
2799
- /** A block to the GPU only when its bytes moved since the last upload (a drawer at rest costs no write). */
2800
- upload(buf, bytes, sent) {
2801
- let same = this.sent;
2802
- for (let i = 0; same && i < bytes.length; i++) if (bytes[i] !== sent[i]) same = false;
2803
- if (same) return;
2804
- sent.set(bytes);
2805
- this.device.queue.writeBuffer(buf, 0, bytes);
2806
- }
2807
- /** A pipeline and the group, bound (the group remade when the mat's assets or the accessories' buffer changed). */
2808
- bind(pass, pipeline) {
2809
- if (this.group === null || this.boundAssets !== this.mat.assetVersion) {
2810
- this.group = bindGroup(this.device, this.layout, [this.lightBuf, this.trayBuf, this.mat.noiseTexture.createView(), this.sampler, this.hash.createView(), this.accBuf], "tray/pegboard");
2811
- this.boundAssets = this.mat.assetVersion;
2812
- }
2813
- pass.setPipeline(pipeline);
2814
- pass.setBindGroup(0, this.group);
2815
- }
2816
- /**
2817
- * The drawer UNDER its specimens into the open pass — over everything drawn so far (the caller left the scissor on the whole view):
2818
- * the dim, the drawer's shadows, the board to its edge; then each specimen's accessory with its shadow on the board (K5a).
2819
- */
2820
- draw(pass) {
2821
- if (this.quads === 0) return;
2822
- pass.pushDebugGroup("tray/pegboard");
2823
- this.bind(pass, this.pipeline);
2824
- pass.draw(6, 1, 0, 0);
2825
- if (this.accCount > 0) {
2826
- pass.setPipeline(this.accPipeline);
2827
- pass.draw(6, this.accCount, 0, 1);
2828
- }
2829
- pass.popDebugGroup();
2830
- }
2831
- /**
2832
- * THE VEIL (design-018 rev 5) — LAST, over everything the drawer holds (the caller left the scissor on the whole view): the plain board
2833
- * whole in the header and fading out over its ramp, so what hangs there and the holes behind it fade into the board together — a
2834
- * specimen never turns see-through over a hole. One quad — and with a host's foot (I21) a second, over the foot, in the same draw.
2835
- */
2836
- drawVeil(pass) {
2837
- if (this.quads === 0) return;
2838
- pass.pushDebugGroup("tray/pegboard/veil");
2839
- this.bind(pass, this.veilPipeline);
2840
- pass.draw(this.foot > 0 ? 12 : 6, 1, 0, 0);
2841
- pass.popDebugGroup();
2842
- }
2843
- /** What the last prepare laid (a rig's witness). */
2844
- get laid() {
2845
- return this.last;
2846
- }
2847
- dispose() {
2848
- this.trayBuf.destroy();
2849
- this.lightBuf.destroy();
2850
- this.accBuf.destroy();
2851
- this.hash.destroy();
2852
- }
2853
- };
2854
-
2855
2821
  // ../desk/src/ground.ts
2856
2822
  function closeScope(b, kinds) {
2857
2823
  b.device.popErrorScope().then((e) => {
@@ -3021,10 +2987,11 @@ var SlotPool = class {
3021
2987
  };
3022
2988
  function drawSlot(pass, size, dpr, slot) {
3023
2989
  if (!visible(slot.present)) return false;
3024
- const [x, y, w, h] = scissorOf(slot.present, dpr, size);
2990
+ const scissor = scissorOf(slot.present, dpr, size);
2991
+ const [x, y, w, h] = scissor;
3025
2992
  if (w === 0 || h === 0) return false;
3026
2993
  pass.setScissorRect(x, y, w, h);
3027
- if (slot.bare !== true) slot.mat.draw(pass);
2994
+ if (slot.bare !== true) slot.mat.drawSurface(pass, scissor);
3028
2995
  if (slot.underlays?.length) {
3029
2996
  for (const u of slot.underlays) u.draw(pass);
3030
2997
  pass.setScissorRect(x, y, w, h);
@@ -3123,6 +3090,7 @@ function drawSlot(pass, size, dpr, slot) {
3123
3090
  }
3124
3091
  }
3125
3092
  for (const children of insides.values()) for (const child of children) inside(child);
3093
+ if (slot.bare !== true) slot.mat.drawRulers(pass, scissor);
3126
3094
  return true;
3127
3095
  }
3128
3096
  function drawFrame(pass, size, dpr, incoming, outgoing) {
@@ -3560,7 +3528,7 @@ var Ground = class _Ground {
3560
3528
  };
3561
3529
  function tagsOf(tray) {
3562
3530
  const P = DRAWER.pitch;
3563
- return (tray?.specimens ?? []).map((f) => ({ label: f.label, x: (f.screen.x0 + f.screen.x1) / 2, y: f.screen.y1 + (f.accessory === "shelf" ? 0.24 * P : 0) + 0.45 * P }));
3531
+ return (tray?.specimens ?? []).map((f) => ({ label: f.label, x: (f.screen.x0 + f.screen.x1) / 2, y: f.screen.y1 + 0.45 * P }));
3564
3532
  }
3565
3533
  function renderHeldFrame(device, hold, root, pool, grid, into, inputs, held, cache, label = "hold") {
3566
3534
  const size = into.size();
@@ -5714,6 +5682,7 @@ function createPickSource(builder, opts = {}) {
5714
5682
  var EPS2 = 1e-4;
5715
5683
  function createTrayFlux(opts = {}) {
5716
5684
  const foot = opts.foot ?? 0;
5685
+ const footFade = opts.footFade ?? DRAWER.fade;
5717
5686
  let facts = null;
5718
5687
  let p = 0;
5719
5688
  let from = 0;
@@ -5788,7 +5757,7 @@ function createTrayFlux(opts = {}) {
5788
5757
  const pb = P?.band ?? shown;
5789
5758
  const rect = drawerRect(vw, vh, pp);
5790
5759
  drawn = { x: rect.x, y: rect.y, w: rect.w, h: rect.h, p: pp, max: scrollRange(vw, vh, f.bottom ?? 0, foot), pitch: DRAWER.pitch, scroll: f.scroll + pb, face: rect.h, head: DRAWER.arris + DRAWER.header, ...foot > 0 ? { foot } : {} };
5791
- return { p: pp, scroll: f.scroll + pb, ...foot > 0 ? { foot } : {} };
5760
+ return { p: pp, scroll: f.scroll + pb, ...foot > 0 ? { foot, footFade } : {} };
5792
5761
  },
5793
5762
  live: () => moving && pinned === null,
5794
5763
  frame: () => drawn,
@@ -5839,7 +5808,7 @@ function createDeskReflector(opts) {
5839
5808
  let gridGen = 0;
5840
5809
  let pinGen = 0;
5841
5810
  const wakes = { world: 0, removed: 0, reset: 0, order: 0, hover: 0, marks: 0, camera: 0, viewport: 0, nav: 0, theme: 0, grid: 0, pin: 0, ambient: 0, live: 0, ink: 0, tray: 0 };
5842
- const tray = createTrayFlux(opts.tray?.foot !== void 0 ? { foot: opts.tray.foot } : {});
5811
+ const tray = createTrayFlux(opts.tray);
5843
5812
  let drawnSpecimens = [];
5844
5813
  const carry2 = createTrayCarry();
5845
5814
  let drawnCarried = [];
@@ -7613,6 +7582,7 @@ function deskLayer(opts) {
7613
7582
  looksOf(objectKinds, opts.palette, opts.theme);
7614
7583
  const hold = holdOf(opts.hold);
7615
7584
  const foot = hostNumber("tray.foot", opts.tray?.foot);
7585
+ const footFade = hostNumber("tray.footFade", opts.tray?.footFade);
7616
7586
  const cursors = opts.cursors !== false;
7617
7587
  const canvas = doc.createElement("canvas");
7618
7588
  canvas.style.position = "absolute";
@@ -7843,7 +7813,7 @@ function deskLayer(opts) {
7843
7813
  grid,
7844
7814
  locals,
7845
7815
  ...opts.maxDpr !== void 0 ? { maxDpr: opts.maxDpr } : {},
7846
- ...foot !== void 0 ? { tray: { foot } } : {},
7816
+ ...foot !== void 0 || footFade !== void 0 ? { tray: { ...foot !== void 0 ? { foot } : {}, ...footFade !== void 0 ? { footFade } : {} } } : {},
7847
7817
  ...opts.name !== void 0 ? { name: opts.name } : {},
7848
7818
  // a frame asked for outside the flush — a pin, an ink landing, the ground arriving, a theme: a sleeping loop wakes (K7a)
7849
7819
  onWake: (reason) => frame?.wake(`desk:${reason}`)
@@ -8369,8 +8339,8 @@ function deskLayer(opts) {
8369
8339
  ...compose2.tray.state(),
8370
8340
  frame: compose2.tray.frame(),
8371
8341
  laid: ground?.tray?.laid ?? null,
8372
- // K5a: the specimens as the last frame drew them — each on screen, its pegs on screen, the scale its kind drew it at — its tags, its slots
8373
- specimens: compose2.traySpecimens().map((f) => ({ type: f.type, kind: f.kind, label: f.label, accessory: f.accessory, screen: f.screen, pegs: f.pegs, zoom: f.view.zoom, object: f.object })),
8342
+ // K5a: the specimens as the last frame drew them — each on screen, the scale its kind drew it at — its tags, its slots
8343
+ specimens: compose2.traySpecimens().map((f) => ({ type: f.type, kind: f.kind, label: f.label, screen: f.screen, zoom: f.view.zoom, object: f.object })),
8374
8344
  tags: ground?.marks?.tagsLaid.length ?? 0,
8375
8345
  slots: ground?.traySlots?.size ?? 0,
8376
8346
  // K5b: what the tray carried last frame — the copy, a ghost growing or flying home — and the ghosts it drew for the desk
@@ -9112,4 +9082,4 @@ export {
9112
9082
  STILL_PALETTE,
9113
9083
  createStill
9114
9084
  };
9115
- //# sourceMappingURL=chunk-REJTEK3V.js.map
9085
+ //# sourceMappingURL=chunk-M2HUDENL.js.map