@autono/pinbox-toolbar 0.8.0 → 0.10.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.
@@ -289,6 +289,448 @@ var Pinbox = (function(exports) {
289
289
  return `${open.map((p) => block(p, threads.get(p.id) ?? [])).join("\n\n")}\n`;
290
290
  }
291
291
  //#endregion
292
+ //#region src/motion/spring.ts
293
+ /** Bar ⇄ puck morphs and the post-drag settle. */
294
+ const MORPH_SPRING = {
295
+ k: 300,
296
+ c: 30
297
+ };
298
+ /** The morph surface chasing the pointer mid-drag. */
299
+ const FOLLOW_SPRING = {
300
+ k: 600,
301
+ c: 38
302
+ };
303
+ /** Integration substep — small enough that MORPH/FOLLOW stay stable at any dt. */
304
+ const SUBSTEP = 1 / 240;
305
+ /** A property this close to target, moving this slowly, counts as settled. */
306
+ const SETTLE = .5;
307
+ function mkSpring(init) {
308
+ const cur = { ...init };
309
+ const tgt = { ...init };
310
+ const keys = Object.keys(init);
311
+ const vel = {};
312
+ for (const key of keys) vel[key] = 0;
313
+ let cfg = { ...MORPH_SPRING };
314
+ const curN = cur;
315
+ const tgtN = tgt;
316
+ function integrate(h) {
317
+ for (const key of keys) {
318
+ const x = curN[key] ?? 0;
319
+ const v = vel[key] ?? 0;
320
+ const nextV = v + (-cfg.k * (x - (tgtN[key] ?? 0)) - cfg.c * v) * h;
321
+ vel[key] = nextV;
322
+ curN[key] = x + nextV * h;
323
+ }
324
+ }
325
+ return {
326
+ cur,
327
+ tgt,
328
+ snap(values) {
329
+ Object.assign(cur, values);
330
+ Object.assign(tgt, values);
331
+ for (const key of Object.keys(vel)) vel[key] = 0;
332
+ },
333
+ to(values, nextCfg) {
334
+ Object.assign(tgt, values);
335
+ if (nextCfg) cfg = { ...nextCfg };
336
+ },
337
+ step(dt) {
338
+ let remaining = dt;
339
+ while (remaining > 1e-6) {
340
+ const h = Math.min(SUBSTEP, remaining);
341
+ remaining -= h;
342
+ integrate(h);
343
+ }
344
+ for (const key of keys) {
345
+ const v = vel[key] ?? 0;
346
+ const gap = (curN[key] ?? 0) - (tgtN[key] ?? 0);
347
+ if (Math.abs(v) > SETTLE || Math.abs(gap) > SETTLE) return false;
348
+ }
349
+ this.snap({ ...tgt });
350
+ return true;
351
+ }
352
+ };
353
+ }
354
+ //#endregion
355
+ //#region src/minimize.ts
356
+ const PUCK = 48;
357
+ const MARGIN = 16;
358
+ /** Movement that begins a drag… */
359
+ const DRAG_START = 8;
360
+ /** …but a release under this much TOTAL travel is still a tap. */
361
+ const TAP_MAX = 12;
362
+ /** Spring hold while the surface swap crossfades (ms). */
363
+ const HOLD_MINIMIZE = 90;
364
+ const HOLD_RESTORE = 70;
365
+ /** Arrival: real element fades in first, morph layer fades this much later. */
366
+ const SWAP_FADE = 100;
367
+ /** …and is display:none'd once its own fade has finished. */
368
+ const LAYER_HIDE = 140;
369
+ const BAR_RADIUS = 4;
370
+ /** The carrier icon rides the surface only while it is puck-like (< this width). */
371
+ const CARRIER_MAX_W = 260;
372
+ function createMinimize(host) {
373
+ const { win, bar, ui } = host;
374
+ const main = mkSpring({
375
+ x: 0,
376
+ y: 0,
377
+ w: 0,
378
+ h: 0,
379
+ r: 0
380
+ });
381
+ let mode = "bar";
382
+ let puckPos = null;
383
+ let dock = loadDock();
384
+ let pdown = null;
385
+ let keyboardToggle = false;
386
+ let raf = 0;
387
+ let last = 0;
388
+ let holdTimer = 0;
389
+ let fadeTimer = 0;
390
+ let hideTimer = 0;
391
+ function loadDock() {
392
+ try {
393
+ const raw = host.storage?.getItem(`${host.storagePrefix}:dock`);
394
+ if (raw == null) return null;
395
+ const parsed = JSON.parse(raw);
396
+ if (typeof parsed.x !== "number" || typeof parsed.y !== "number") return null;
397
+ return {
398
+ x: parsed.x,
399
+ y: parsed.y
400
+ };
401
+ } catch {
402
+ return null;
403
+ }
404
+ }
405
+ function persist() {
406
+ try {
407
+ if (dock) host.storage?.setItem(`${host.storagePrefix}:dock`, JSON.stringify(dock));
408
+ host.storage?.setItem(`${host.storagePrefix}:minimized`, mode === "bar" ? "0" : "1");
409
+ } catch {}
410
+ }
411
+ function loadMinimized() {
412
+ try {
413
+ const raw = host.storage?.getItem(`${host.storagePrefix}:minimized`);
414
+ return raw == null ? host.initialMinimized : raw === "1";
415
+ } catch {
416
+ return host.initialMinimized;
417
+ }
418
+ }
419
+ const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
420
+ function clampPos(p) {
421
+ return {
422
+ x: clamp(p.x, MARGIN, win.innerWidth - MARGIN - PUCK),
423
+ y: clamp(p.y, MARGIN, win.innerHeight - MARGIN - PUCK)
424
+ };
425
+ }
426
+ function defaultDock() {
427
+ const r = bar.getBoundingClientRect();
428
+ return {
429
+ x: r.left + r.width / 2 - PUCK / 2,
430
+ y: r.top + (r.height - PUCK) / 2
431
+ };
432
+ }
433
+ function placePuck(x, y) {
434
+ puckPos = {
435
+ x,
436
+ y
437
+ };
438
+ ui.puck.style.transform = `translate(${x}px, ${y}px)`;
439
+ }
440
+ function render() {
441
+ const m = main.cur;
442
+ ui.surface.style.width = `${m.w}px`;
443
+ ui.surface.style.height = `${m.h}px`;
444
+ ui.surface.style.borderRadius = `${m.r}px`;
445
+ ui.surface.style.transform = `translate(${m.x}px, ${m.y}px)`;
446
+ ui.carrier.style.transform = `translate(${m.x + m.w / 2 - PUCK / 2}px, ${m.y + m.h / 2 - PUCK / 2}px)`;
447
+ const iconOn = mode === "drag" || mode === "settle" || (mode === "toPuck" || mode === "toBar") && m.w < CARRIER_MAX_W;
448
+ ui.carrier.classList.toggle("show", iconOn);
449
+ }
450
+ function showMorph() {
451
+ win.clearTimeout(hideTimer);
452
+ win.clearTimeout(fadeTimer);
453
+ ui.morphWrap.hidden = false;
454
+ ui.morphWrap.offsetWidth;
455
+ ui.morphWrap.classList.add("on");
456
+ }
457
+ function hideMorph() {
458
+ ui.morphWrap.classList.remove("on");
459
+ ui.carrier.classList.remove("show");
460
+ win.clearTimeout(hideTimer);
461
+ hideTimer = win.setTimeout(() => {
462
+ ui.morphWrap.hidden = true;
463
+ }, LAYER_HIDE);
464
+ }
465
+ function tick(now) {
466
+ const dt = Math.min((now - last) / 1e3, 1 / 30);
467
+ last = now;
468
+ const done = main.step(dt);
469
+ render();
470
+ if (done && mode !== "drag") {
471
+ raf = 0;
472
+ if (mode === "toPuck" || mode === "settle") finishPuck();
473
+ else if (mode === "toBar") finishBar();
474
+ } else raf = win.requestAnimationFrame(tick);
475
+ }
476
+ function ensureLoop() {
477
+ if (raf === 0) {
478
+ last = win.performance.now();
479
+ raf = win.requestAnimationFrame(tick);
480
+ }
481
+ }
482
+ function finishPuck() {
483
+ placePuck(main.cur.x, main.cur.y);
484
+ ui.puck.classList.remove("pb-ghost");
485
+ win.clearTimeout(fadeTimer);
486
+ fadeTimer = win.setTimeout(hideMorph, SWAP_FADE);
487
+ mode = "puck";
488
+ persist();
489
+ host.onSettled(true, keyboardToggle);
490
+ }
491
+ function finishBar() {
492
+ bar.classList.remove("pb-ghost");
493
+ win.clearTimeout(fadeTimer);
494
+ fadeTimer = win.setTimeout(hideMorph, SWAP_FADE);
495
+ mode = "bar";
496
+ persist();
497
+ host.onSettled(false, keyboardToggle);
498
+ }
499
+ function minimize(keyboard = false) {
500
+ if (mode !== "bar") return;
501
+ pdown = null;
502
+ keyboardToggle = keyboard;
503
+ const r = bar.getBoundingClientRect();
504
+ const d = clampPos(dock ?? defaultDock());
505
+ bar.classList.add("pb-ghost");
506
+ if (host.reduced) {
507
+ placePuck(d.x, d.y);
508
+ ui.puck.classList.remove("pb-ghost");
509
+ mode = "puck";
510
+ persist();
511
+ host.onSettled(true, keyboard);
512
+ return;
513
+ }
514
+ main.snap({
515
+ x: r.left,
516
+ y: r.top,
517
+ w: r.width,
518
+ h: r.height,
519
+ r: BAR_RADIUS
520
+ });
521
+ mode = "toPuck";
522
+ render();
523
+ showMorph();
524
+ win.clearTimeout(holdTimer);
525
+ holdTimer = win.setTimeout(() => {
526
+ main.to({
527
+ x: d.x,
528
+ y: d.y,
529
+ w: PUCK,
530
+ h: PUCK,
531
+ r: PUCK / 2
532
+ }, MORPH_SPRING);
533
+ ensureLoop();
534
+ }, HOLD_MINIMIZE);
535
+ }
536
+ function restore(keyboard = false) {
537
+ if (mode !== "puck" || puckPos === null) return;
538
+ pdown = null;
539
+ keyboardToggle = keyboard;
540
+ dock = { ...puckPos };
541
+ ui.puck.classList.add("pb-ghost");
542
+ if (host.reduced) {
543
+ bar.classList.remove("pb-ghost");
544
+ mode = "bar";
545
+ persist();
546
+ host.onSettled(false, keyboard);
547
+ return;
548
+ }
549
+ const r = bar.getBoundingClientRect();
550
+ main.snap({
551
+ x: puckPos.x,
552
+ y: puckPos.y,
553
+ w: PUCK,
554
+ h: PUCK,
555
+ r: PUCK / 2
556
+ });
557
+ mode = "toBar";
558
+ render();
559
+ showMorph();
560
+ win.clearTimeout(holdTimer);
561
+ holdTimer = win.setTimeout(() => {
562
+ main.to({
563
+ x: r.left,
564
+ y: r.top,
565
+ w: r.width,
566
+ h: r.height,
567
+ r: BAR_RADIUS
568
+ }, MORPH_SPRING);
569
+ ensureLoop();
570
+ }, HOLD_RESTORE);
571
+ }
572
+ function onPointerDown(e) {
573
+ if (mode !== "puck" || e.button !== 0) return;
574
+ try {
575
+ ui.puck.setPointerCapture(e.pointerId);
576
+ } catch {}
577
+ if (puckPos === null) return;
578
+ pdown = {
579
+ px: e.clientX,
580
+ py: e.clientY,
581
+ ox: puckPos.x,
582
+ oy: puckPos.y,
583
+ lx: e.clientX,
584
+ ly: e.clientY,
585
+ moved: false
586
+ };
587
+ }
588
+ function onPointerMove(e) {
589
+ if (pdown === null) return;
590
+ pdown.lx = e.clientX;
591
+ pdown.ly = e.clientY;
592
+ const dx = e.clientX - pdown.px;
593
+ const dy = e.clientY - pdown.py;
594
+ if (!pdown.moved) {
595
+ if (mode !== "puck") {
596
+ pdown = null;
597
+ return;
598
+ }
599
+ if (Math.hypot(dx, dy) < DRAG_START) return;
600
+ pdown.moved = true;
601
+ mode = "drag";
602
+ if (!host.reduced) {
603
+ ui.puck.classList.add("pb-ghost");
604
+ main.snap({
605
+ x: pdown.ox,
606
+ y: pdown.oy,
607
+ w: PUCK,
608
+ h: PUCK,
609
+ r: PUCK / 2
610
+ });
611
+ render();
612
+ showMorph();
613
+ }
614
+ }
615
+ const next = {
616
+ x: pdown.ox + dx,
617
+ y: pdown.oy + dy
618
+ };
619
+ if (host.reduced) {
620
+ const p = clampPos(next);
621
+ placePuck(p.x, p.y);
622
+ } else {
623
+ main.to({
624
+ ...next,
625
+ w: PUCK,
626
+ h: PUCK,
627
+ r: PUCK / 2
628
+ }, FOLLOW_SPRING);
629
+ ensureLoop();
630
+ }
631
+ }
632
+ function onPointerUp() {
633
+ if (pdown === null) return;
634
+ const total = Math.hypot(pdown.lx - pdown.px, pdown.ly - pdown.py);
635
+ const wasDrag = pdown.moved && total >= TAP_MAX;
636
+ const origin = {
637
+ x: pdown.ox,
638
+ y: pdown.oy
639
+ };
640
+ const startedDrag = pdown.moved;
641
+ pdown = null;
642
+ if (!wasDrag) {
643
+ if (startedDrag && mode === "drag") {
644
+ main.snap({
645
+ x: origin.x,
646
+ y: origin.y,
647
+ w: PUCK,
648
+ h: PUCK,
649
+ r: PUCK / 2
650
+ });
651
+ placePuck(origin.x, origin.y);
652
+ ui.puck.classList.remove("pb-ghost");
653
+ hideMorph();
654
+ mode = "puck";
655
+ }
656
+ restore(false);
657
+ return;
658
+ }
659
+ if (host.reduced) {
660
+ if (puckPos) {
661
+ const p = clampPos(puckPos);
662
+ placePuck(p.x, p.y);
663
+ dock = { ...p };
664
+ }
665
+ mode = "puck";
666
+ persist();
667
+ return;
668
+ }
669
+ const p = clampPos({
670
+ x: main.tgt.x,
671
+ y: main.tgt.y
672
+ });
673
+ main.to({
674
+ ...p,
675
+ w: PUCK,
676
+ h: PUCK,
677
+ r: PUCK / 2
678
+ }, MORPH_SPRING);
679
+ mode = "settle";
680
+ ensureLoop();
681
+ }
682
+ /** Keyboard/AT activation is a synthesized click (detail 0) with no pointer events. */
683
+ function onClick(e) {
684
+ if (e.detail === 0) restore(true);
685
+ }
686
+ function onResize() {
687
+ if (mode === "puck" && puckPos !== null) {
688
+ const p = clampPos(puckPos);
689
+ placePuck(p.x, p.y);
690
+ }
691
+ if (dock !== null) dock = clampPos(dock);
692
+ }
693
+ ui.puck.addEventListener("pointerdown", onPointerDown);
694
+ ui.puck.addEventListener("pointermove", onPointerMove);
695
+ ui.puck.addEventListener("pointerup", onPointerUp);
696
+ ui.puck.addEventListener("pointercancel", onPointerUp);
697
+ ui.puck.addEventListener("click", onClick);
698
+ win.addEventListener("resize", onResize);
699
+ return {
700
+ minimized: () => mode !== "bar",
701
+ mode: () => mode,
702
+ minimize,
703
+ restore,
704
+ applyInitial() {
705
+ if (!loadMinimized()) return;
706
+ const apply = () => {
707
+ if (mode !== "bar") return;
708
+ const d = clampPos(dock ?? defaultDock());
709
+ bar.classList.add("pb-ghost");
710
+ placePuck(d.x, d.y);
711
+ ui.puck.classList.remove("pb-ghost");
712
+ mode = "puck";
713
+ host.onSettled(true, false);
714
+ };
715
+ if (host.reduced) apply();
716
+ else win.requestAnimationFrame(apply);
717
+ },
718
+ destroy() {
719
+ ui.puck.removeEventListener("pointerdown", onPointerDown);
720
+ ui.puck.removeEventListener("pointermove", onPointerMove);
721
+ ui.puck.removeEventListener("pointerup", onPointerUp);
722
+ ui.puck.removeEventListener("pointercancel", onPointerUp);
723
+ ui.puck.removeEventListener("click", onClick);
724
+ win.removeEventListener("resize", onResize);
725
+ if (raf !== 0) win.cancelAnimationFrame(raf);
726
+ raf = 0;
727
+ win.clearTimeout(holdTimer);
728
+ win.clearTimeout(fadeTimer);
729
+ win.clearTimeout(hideTimer);
730
+ }
731
+ };
732
+ }
733
+ //#endregion
292
734
  //#region src/screenshot.ts
293
735
  const WEBP_QUALITY = .7;
294
736
  const PLACEHOLDER_MAX = 32;
@@ -420,7 +862,8 @@ var Pinbox = (function(exports) {
420
862
  activePinId: null,
421
863
  inboxOpen: false,
422
864
  connection: "connecting",
423
- queuedIds: /* @__PURE__ */ new Set()
865
+ queuedIds: /* @__PURE__ */ new Set(),
866
+ minimized: false
424
867
  };
425
868
  }
426
869
  function createStore() {
@@ -1115,6 +1558,7 @@ var Pinbox = (function(exports) {
1115
1558
  const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8 1.6a6.4 6.4 0 100 12.8A5 5 0 018 1.6z\"/></svg>";
1116
1559
  const COPY_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1\"/><path d=\"M10.5 3.5v-1a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1h1\"/></svg>";
1117
1560
  const IDENT_ICON = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"var(--pb-amber)\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"var(--pb-amber)\" stroke=\"none\"/></svg>";
1561
+ const MIN_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M6.5 2.5v4h-4\"/><path d=\"M9.5 13.5v-4h4\"/></svg>";
1118
1562
  const CONNECTION_LABEL = {
1119
1563
  connecting: "PINBOX",
1120
1564
  live: "PINBOX",
@@ -1124,7 +1568,7 @@ var Pinbox = (function(exports) {
1124
1568
  function createBar(doc, on) {
1125
1569
  const root = doc.createElement("div");
1126
1570
  root.className = "pb-bar";
1127
- root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button>`;
1571
+ root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button><button type="button" class="pb-tb sq" data-ref="min" title="Minimize (M)" aria-label="Minimize toolbar">${MIN_ICON}</button>`;
1128
1572
  const ref = (name) => root.querySelector(`[data-ref="${name}"]`);
1129
1573
  const label = ref("label");
1130
1574
  const pinBtn = ref("pin");
@@ -1135,6 +1579,7 @@ var Pinbox = (function(exports) {
1135
1579
  ref("copy").addEventListener("click", on.onCopy);
1136
1580
  ref("theme").addEventListener("click", on.onTheme);
1137
1581
  ref("help").addEventListener("click", on.onHelp);
1582
+ ref("min").addEventListener("click", (e) => on.onMinimize(e.detail === 0));
1138
1583
  return {
1139
1584
  root,
1140
1585
  update(state) {
@@ -1631,6 +2076,43 @@ var Pinbox = (function(exports) {
1631
2076
  if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(visible.length + 1, null));
1632
2077
  }
1633
2078
  //#endregion
2079
+ //#region src/ui/puck.ts
2080
+ /** The bar's ident mark, sized up for the 48px puck face. */
2081
+ const PUCK_ICON = "<svg width=\"17\" height=\"17\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"currentColor\" stroke=\"none\"/></svg>";
2082
+ function createMinimizeUi(doc) {
2083
+ const puck = doc.createElement("button");
2084
+ puck.type = "button";
2085
+ puck.className = "pb-puck pb-ghost";
2086
+ puck.setAttribute("aria-label", "Restore Pinbox toolbar");
2087
+ puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
2088
+ const morphWrap = doc.createElement("div");
2089
+ morphWrap.className = "pb-morph-wrap";
2090
+ morphWrap.hidden = true;
2091
+ const surface = doc.createElement("div");
2092
+ surface.className = "pb-morph";
2093
+ const carrier = doc.createElement("div");
2094
+ carrier.className = "pb-carrier";
2095
+ carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
2096
+ morphWrap.appendChild(surface);
2097
+ morphWrap.appendChild(carrier);
2098
+ const badges = [puck.querySelector("[data-ref=\"count\"]"), carrier.querySelector("[data-ref=\"count\"]")];
2099
+ return {
2100
+ puck,
2101
+ morphWrap,
2102
+ surface,
2103
+ carrier,
2104
+ update(state) {
2105
+ const open = String(state.pins.filter((p) => p.status !== "resolved").length);
2106
+ for (const badge of badges) {
2107
+ if (badge.textContent !== open) badge.textContent = open;
2108
+ badge.hidden = open === "0";
2109
+ }
2110
+ const degraded = state.connection === "offline" || state.connection === "incompatible";
2111
+ puck.classList.toggle("degraded", degraded);
2112
+ }
2113
+ };
2114
+ }
2115
+ //#endregion
1634
2116
  //#region src/ui/reticle.ts
1635
2117
  function createReticle(doc) {
1636
2118
  const crosshair = doc.createElement("div");
@@ -1686,6 +2168,7 @@ var Pinbox = (function(exports) {
1686
2168
  ["Send comment", "⌘ ↵"],
1687
2169
  ["Mark pin resolved", "R"],
1688
2170
  ["Copy open pins", "C"],
2171
+ ["Minimize toolbar", "M"],
1689
2172
  ["Cancel", "ESC"]
1690
2173
  ];
1691
2174
  function createShortcutsModal(doc, onClose) {
@@ -1896,6 +2379,27 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
1896
2379
  .pb-item .mm .sdot { width: 5px; height: 5px; border-radius: 999px; }
1897
2380
  .pb-empty { padding: 26px 16px; font-size: 12.5px; color: var(--pb-fg3); }
1898
2381
 
2382
+ /* minimize: floating puck + morph layer (ui/puck.ts, minimize.ts) — v3 design.
2383
+ The morph surface is styled identically to the bar so endpoint handoffs are
2384
+ pixel-invisible. Ghosting keeps layout (getBoundingClientRect still measures
2385
+ morph targets) while dropping the element from paint and the tab order. */
2386
+ .pb-ghost { opacity: 0 !important; pointer-events: none !important; visibility: hidden; transition: opacity 90ms linear, visibility 0s 90ms; }
2387
+ .pb-bar { transition: opacity 90ms linear; }
2388
+ .pb-morph-wrap { position: fixed; inset: 0; z-index: 89; pointer-events: none; opacity: 0; transition: opacity 90ms linear; }
2389
+ .pb-morph-wrap.on { opacity: 1; }
2390
+ .pb-morph { position: absolute; left: 0; top: 0; background: var(--pb-bar); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--pb-line-2); box-shadow: var(--pb-shadow); will-change: transform, width, height; }
2391
+ .pb-carrier { position: absolute; left: 0; top: 0; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; color: var(--pb-amber); opacity: 0; transition: opacity 140ms linear; will-change: transform; }
2392
+ .pb-carrier.show { opacity: 1; }
2393
+ /* Above the bar (90) and drawer (85), below the aim layer (100) and modal (120). */
2394
+ .pb-puck { position: fixed; left: 0; top: 0; width: 48px; height: 48px; z-index: 95; border-radius: 999px; background: var(--pb-bar); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--pb-line-2); box-shadow: var(--pb-shadow); display: flex; align-items: center; justify-content: center; color: var(--pb-amber); cursor: grab; touch-action: none; transition: opacity 90ms linear; }
2395
+ .pb-puck:active { cursor: grabbing; }
2396
+ .pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
2397
+ .pb-puck:hover .in { transform: scale(1.12); }
2398
+ .pb-puck .badge, .pb-carrier .badge { position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--pb-amber); color: var(--pb-amber-ink); font-family: var(--pb-font-mono); font-size: 9px; font-weight: 500; display: flex; align-items: center; justify-content: center; }
2399
+ /* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
2400
+ .pb-puck .cdot { position: absolute; bottom: -1px; right: -1px; width: 9px; height: 9px; border-radius: 999px; background: var(--pb-amber); border: 2px solid var(--pb-canvas); display: none; }
2401
+ .pb-puck.degraded .cdot { display: block; }
2402
+
1899
2403
  /* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
1900
2404
  .pb-modal { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center; justify-content: center; background: var(--pb-scrim); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); animation: pb-fade 200ms ease-out both; }
1901
2405
  .pb-modal .mx { width: 430px; background: var(--pb-elev); border: 1px solid var(--pb-line-2); border-radius: 4px; box-shadow: var(--pb-shadow); animation: pb-in 280ms var(--pb-ease) both; }
@@ -1951,6 +2455,8 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
1951
2455
  /** Pending viewport-refresh frame, 0 when none is queued. */
1952
2456
  #viewportFrame = 0;
1953
2457
  #modal = null;
2458
+ #minUi = null;
2459
+ #min = null;
1954
2460
  #helpOpen = false;
1955
2461
  #pageStyle = null;
1956
2462
  #unsubscribe = null;
@@ -1991,6 +2497,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
1991
2497
  document.head.appendChild(style);
1992
2498
  this.#pageStyle = style;
1993
2499
  if (this.#aim === null) this.#mountAim();
2500
+ if (this.#min === null) this.#mountMinimize();
1994
2501
  document.addEventListener("mousemove", this.#onMouseMove);
1995
2502
  window.addEventListener("scroll", this.#onViewportChange, { passive: true });
1996
2503
  window.addEventListener("resize", this.#onViewportChange);
@@ -2036,6 +2543,8 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2036
2543
  this.#viewportFrame = 0;
2037
2544
  this.#aim?.destroy();
2038
2545
  this.#aim = null;
2546
+ this.#min?.destroy();
2547
+ this.#min = null;
2039
2548
  this.#unsubscribe?.();
2040
2549
  this.#unsubscribe = null;
2041
2550
  this.#pageStyle?.remove();
@@ -2089,9 +2598,13 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2089
2598
  onInbox: () => this.store.update({ inboxOpen: !this.store.get().inboxOpen }),
2090
2599
  onTheme: () => this.#toggleTheme(),
2091
2600
  onHelp: () => this.#toggleHelp(),
2092
- onCopy: () => this.#copyOpenPins()
2601
+ onCopy: () => this.#copyOpenPins(),
2602
+ onMinimize: (keyboard) => this.minimize(keyboard)
2093
2603
  });
2094
2604
  shadow.appendChild(this.#bar.root);
2605
+ this.#minUi = createMinimizeUi(document);
2606
+ shadow.appendChild(this.#minUi.morphWrap);
2607
+ shadow.appendChild(this.#minUi.puck);
2095
2608
  this.#drawer = createDrawer(document, {
2096
2609
  onActivate: (pinId) => this.#activateFromInbox(pinId),
2097
2610
  onClose: () => this.store.update({ inboxOpen: false })
@@ -2102,6 +2615,52 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2102
2615
  shadow.appendChild(this.#modal.root);
2103
2616
  this.#pinsLayer.addEventListener("click", (e) => this.#onChipClick(e));
2104
2617
  }
2618
+ /** Same lifetime split as #mountAim: the controller's window listeners and timers die on
2619
+ * disconnect, so it is (re)built on connect and re-applies the persisted resting state. */
2620
+ #mountMinimize() {
2621
+ const ui = this.#minUi;
2622
+ const bar = this.#bar;
2623
+ if (ui === null || bar === null) return;
2624
+ this.#min = createMinimize({
2625
+ win: window,
2626
+ bar: bar.root,
2627
+ ui,
2628
+ storage: globalThis.localStorage ?? null,
2629
+ storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
2630
+ reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
2631
+ initialMinimized: this.config?.minimized === true,
2632
+ onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
2633
+ });
2634
+ this.#min.applyInitial();
2635
+ }
2636
+ #onMinimizeSettled(minimized, keyboard) {
2637
+ if (this.store.get().minimized !== minimized) this.store.update({ minimized });
2638
+ this.dispatchEvent(new CustomEvent(minimized ? "pinbox:minimize" : "pinbox:restore", {
2639
+ bubbles: true,
2640
+ composed: true
2641
+ }));
2642
+ if (!keyboard) return;
2643
+ if (minimized) this.#minUi?.puck.focus();
2644
+ else this.#bar?.root.querySelector("[data-ref=\"min\"]")?.focus();
2645
+ }
2646
+ /** Public: collapse the bar to the floating puck. Bar surfaces close first —
2647
+ * the morph must never sweep over an open card, drawer, or armed reticle. */
2648
+ minimize(keyboard = false) {
2649
+ const state = this.store.get();
2650
+ if (state.mode === "placing" || state.activePinId !== null || state.draft !== null) this.#dismiss();
2651
+ if (state.inboxOpen) this.store.update({ inboxOpen: false });
2652
+ this.#setHelp(false);
2653
+ this.#min?.minimize(keyboard);
2654
+ }
2655
+ /** Public: bring the bar back from the puck. */
2656
+ restore(keyboard = false) {
2657
+ this.#min?.restore(keyboard);
2658
+ }
2659
+ /** Bar-surface shortcuts pressed while minimized restore the bar, then run. */
2660
+ #surfaced(run) {
2661
+ if (this.#min?.minimized() === true) this.restore(false);
2662
+ run();
2663
+ }
2105
2664
  /**
2106
2665
  * Task 8 wiring: WS events mutate the store, connection state renders in the
2107
2666
  * bar, card actions hit the hub. The mirror seeds pins so an offline reload
@@ -2325,14 +2884,15 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2325
2884
  if (state.inboxOpen) this.store.update({ inboxOpen: false });
2326
2885
  if (state.activePinId || state.draft) this.#dismiss();
2327
2886
  };
2328
- /** Prototype keyboard map (v2-command-bar.html lines 701–712). */
2887
+ /** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
2329
2888
  #shortcuts = {
2330
2889
  escape: () => this.#dismiss(),
2331
- p: () => this.#togglePlacing(),
2332
- i: () => this.#toggleInbox(),
2890
+ p: () => this.#surfaced(() => this.#togglePlacing()),
2891
+ i: () => this.#surfaced(() => this.#toggleInbox()),
2333
2892
  d: () => this.#toggleTheme(),
2334
2893
  r: () => this.#resolveActive(),
2335
2894
  c: () => this.#copyOpenPins(),
2895
+ m: () => this.#min?.minimized() === true ? this.restore(true) : this.minimize(true),
2336
2896
  "?": () => this.#toggleHelp()
2337
2897
  };
2338
2898
  #onKeyDown = (e) => {
@@ -2370,6 +2930,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2370
2930
  if (this.shadowRoot) renderCard(this.shadowRoot, state, this.#cardActions);
2371
2931
  this.#drawer?.update(state);
2372
2932
  this.#bar?.update(state);
2933
+ this.#minUi?.update(state);
2373
2934
  }
2374
2935
  };
2375
2936
  //#endregion
package/dist/vue.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { d as PinboxToolbarElement, n as PinboxConfig } from "./index-BAoi0bRT.js";
1
+ import { d as PinboxToolbarElement, n as PinboxConfig } from "./index-DK_kVd2Z.js";
2
2
  import { VNode } from "vue";
3
3
  //#region src/vue.d.ts
4
4
  /** The slice of the Vue component instance this wrapper touches. */
package/dist/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-D1qn1iet.js";
1
+ import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-ChTPrRo5.js";
2
2
  import { h } from "vue";
3
3
  //#region src/vue.ts
4
4
  /**