@merittdev/horus-lens 0.3.0 → 0.5.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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LENS_OVERLAY_HOST_ID,
3
3
  LensClient
4
- } from "./chunk-LIYEDZV7.js";
4
+ } from "./chunk-2G5FEWPU.js";
5
5
 
6
6
  // ../browser/dist/index.js
7
7
  var DEFAULT_PARAM_NAME = "lens";
@@ -124,6 +124,113 @@ function el(tag, props = {}, children = []) {
124
124
  function clear(node) {
125
125
  while (node.firstChild) node.removeChild(node.firstChild);
126
126
  }
127
+ var MARGIN = 6;
128
+ var DRAG_THRESHOLD = 3;
129
+ var savedPositions = /* @__PURE__ */ new Map();
130
+ function isInteractiveTarget(target) {
131
+ return target instanceof Element && target.closest(
132
+ 'button, a, input, textarea, select, [role="switch"], [role="button"], [contenteditable="true"]'
133
+ ) !== null;
134
+ }
135
+ function clampToViewport(element, left, top) {
136
+ const rect = element.getBoundingClientRect();
137
+ const vw = window.innerWidth;
138
+ const vh = window.innerHeight;
139
+ const maxLeft = Math.max(MARGIN, vw - rect.width - MARGIN);
140
+ const maxTop = Math.max(MARGIN, vh - rect.height - MARGIN);
141
+ return {
142
+ left: Math.min(Math.max(left, MARGIN), maxLeft),
143
+ top: Math.min(Math.max(top, MARGIN), maxTop)
144
+ };
145
+ }
146
+ function makeDraggable(element, opts) {
147
+ const { handle, key } = opts;
148
+ const ignore = opts.ignore ?? isInteractiveTarget;
149
+ let startX = 0;
150
+ let startY = 0;
151
+ let originLeft = 0;
152
+ let originTop = 0;
153
+ let dragging = false;
154
+ let armed = false;
155
+ let pointerId = null;
156
+ const apply = (left, top) => {
157
+ const p = clampToViewport(element, left, top);
158
+ element.style.left = `${p.left}px`;
159
+ element.style.top = `${p.top}px`;
160
+ element.style.right = "auto";
161
+ element.style.bottom = "auto";
162
+ element.style.transform = "none";
163
+ savedPositions.set(key, p);
164
+ opts.onMove?.();
165
+ };
166
+ const onPointerDown = (e) => {
167
+ if (e.button !== 0 && e.pointerType === "mouse") return;
168
+ if (ignore(e.target)) return;
169
+ armed = true;
170
+ dragging = false;
171
+ pointerId = e.pointerId;
172
+ startX = e.clientX;
173
+ startY = e.clientY;
174
+ const rect = element.getBoundingClientRect();
175
+ originLeft = rect.left;
176
+ originTop = rect.top;
177
+ window.addEventListener("pointermove", onPointerMove, true);
178
+ window.addEventListener("pointerup", onPointerUp, true);
179
+ window.addEventListener("pointercancel", onPointerUp, true);
180
+ };
181
+ const onPointerMove = (e) => {
182
+ if (!armed || e.pointerId !== pointerId) return;
183
+ const dx = e.clientX - startX;
184
+ const dy = e.clientY - startY;
185
+ if (!dragging) {
186
+ if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD) return;
187
+ dragging = true;
188
+ element.classList.add("is-dragging");
189
+ }
190
+ e.preventDefault();
191
+ apply(originLeft + dx, originTop + dy);
192
+ };
193
+ const onPointerUp = (e) => {
194
+ if (e.pointerId !== pointerId) return;
195
+ armed = false;
196
+ if (dragging) {
197
+ dragging = false;
198
+ element.classList.remove("is-dragging");
199
+ }
200
+ pointerId = null;
201
+ window.removeEventListener("pointermove", onPointerMove, true);
202
+ window.removeEventListener("pointerup", onPointerUp, true);
203
+ window.removeEventListener("pointercancel", onPointerUp, true);
204
+ };
205
+ const onResize = () => {
206
+ const p = savedPositions.get(key);
207
+ if (p) apply(p.left, p.top);
208
+ };
209
+ handle.classList.add("lens-drag-handle");
210
+ handle.addEventListener("pointerdown", onPointerDown);
211
+ window.addEventListener("resize", onResize);
212
+ const stored = savedPositions.get(key);
213
+ if (stored) requestAnimationFrame(() => apply(stored.left, stored.top));
214
+ return {
215
+ reclamp: onResize,
216
+ reset() {
217
+ savedPositions.delete(key);
218
+ element.style.left = "";
219
+ element.style.top = "";
220
+ element.style.right = "";
221
+ element.style.bottom = "";
222
+ element.style.transform = "";
223
+ element.classList.remove("is-dragging");
224
+ },
225
+ destroy() {
226
+ handle.removeEventListener("pointerdown", onPointerDown);
227
+ window.removeEventListener("pointermove", onPointerMove, true);
228
+ window.removeEventListener("pointerup", onPointerUp, true);
229
+ window.removeEventListener("pointercancel", onPointerUp, true);
230
+ window.removeEventListener("resize", onResize);
231
+ }
232
+ };
233
+ }
127
234
  var THIN_MIN_DIST = 3;
128
235
  var MAX_POINTS = 2e3;
129
236
  var HIGHLIGHTER_WIDTH = 14;
@@ -274,6 +381,8 @@ var iconTrash = svg(
274
381
  '<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>'
275
382
  );
276
383
  var iconAnnotate = iconPencil;
384
+ var iconGrip = `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="9" cy="6" r="1.6"/><circle cx="15" cy="6" r="1.6"/><circle cx="9" cy="12" r="1.6"/><circle cx="15" cy="12" r="1.6"/><circle cx="9" cy="18" r="1.6"/><circle cx="15" cy="18" r="1.6"/></svg>`;
385
+ var iconReport = svg('<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z"/>');
277
386
  var SVG_NS = "http://www.w3.org/2000/svg";
278
387
  var FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
279
388
  var MIN_DRAG = 6;
@@ -302,6 +411,7 @@ var AnnotationEditor = class {
302
411
  svg = null;
303
412
  bg = null;
304
413
  toolbar = null;
414
+ toolbarDrag = null;
305
415
  textInputEl = null;
306
416
  textAnchor = null;
307
417
  textColor = COLORS[0];
@@ -347,6 +457,7 @@ var AnnotationEditor = class {
347
457
  this.markNodes = [];
348
458
  this.annotations = [];
349
459
  this.stepCounter = 0;
460
+ this.sessionBase = Math.min(this.sessionBase, this.annotations.length);
350
461
  if (!this.active) this.teardownLayer();
351
462
  this.syncActionButtons();
352
463
  }
@@ -361,6 +472,7 @@ var AnnotationEditor = class {
361
472
  this.sizeLayer();
362
473
  this.updateTransform();
363
474
  svg2.style.pointerEvents = "auto";
475
+ if (this.bg) this.bg.style.pointerEvents = "all";
364
476
  svg2.style.touchAction = "none";
365
477
  svg2.style.cursor = "crosshair";
366
478
  svg2.addEventListener("pointerdown", this.onPointerDown);
@@ -381,6 +493,7 @@ var AnnotationEditor = class {
381
493
  if (svg2) {
382
494
  svg2.removeEventListener("pointerdown", this.onPointerDown);
383
495
  svg2.style.pointerEvents = "none";
496
+ if (this.bg) this.bg.style.pointerEvents = "none";
384
497
  svg2.style.touchAction = "";
385
498
  svg2.style.cursor = "";
386
499
  }
@@ -390,15 +503,8 @@ var AnnotationEditor = class {
390
503
  this.onExit = null;
391
504
  cb?.();
392
505
  }
393
- /** Cancel — discard marks drawn during this session, then exit. */
394
- cancel() {
395
- this.cancelTextInput();
396
- const removed = this.markNodes.splice(this.sessionBase);
397
- for (const node of removed) node.remove();
398
- this.annotations.splice(this.sessionBase);
399
- this.exit();
400
- }
401
506
  undo() {
507
+ if (this.markNodes.length <= this.sessionBase) return;
402
508
  const node = this.markNodes.pop();
403
509
  if (!node) return;
404
510
  node.remove();
@@ -614,39 +720,45 @@ var AnnotationEditor = class {
614
720
  on: { click: () => this.clear() }
615
721
  });
616
722
  const editGroup = el("div", { class: "lens-tool-group" }, [this.undoBtn, this.clearBtn]);
617
- const doneGroup = el("div", { class: "lens-tool-group" }, [
618
- el("button", {
619
- class: "lens-pick-chip__btn",
620
- text: "Cancel",
621
- attrs: { type: "button" },
622
- on: { click: () => this.cancel() }
623
- }),
624
- el("button", {
625
- class: "lens-pick-chip__btn lens-pick-chip__btn--primary",
626
- text: "Done",
627
- attrs: { type: "button" },
628
- on: { click: () => this.exit() }
629
- })
723
+ const closeBtn = el("button", {
724
+ class: "lens-tool-close",
725
+ ariaLabel: "Done",
726
+ title: "Done",
727
+ html: iconClose,
728
+ attrs: { type: "button" },
729
+ on: { click: () => this.exit() }
730
+ });
731
+ const grip = el("span", {
732
+ class: "lens-tool-grip",
733
+ html: iconGrip,
734
+ title: "Drag to move toolbar",
735
+ ariaLabel: "Drag to move toolbar"
736
+ });
737
+ const track = el("div", { class: "lens-tool-track" }, [
738
+ toolGroup,
739
+ el("span", { class: "lens-tool-sep" }),
740
+ colorGroup,
741
+ el("span", { class: "lens-tool-sep" }),
742
+ widthGroup,
743
+ el("span", { class: "lens-tool-sep" }),
744
+ editGroup
630
745
  ]);
631
746
  this.toolbar = el(
632
747
  "div",
633
748
  { class: "lens-annotate-toolbar", attrs: { role: "toolbar", "aria-label": "Annotation tools" } },
634
- [
635
- toolGroup,
636
- el("span", { class: "lens-tool-sep" }),
637
- colorGroup,
638
- el("span", { class: "lens-tool-sep" }),
639
- widthGroup,
640
- el("span", { class: "lens-tool-sep" }),
641
- editGroup,
642
- el("span", { class: "lens-tool-sep" }),
643
- doneGroup
644
- ]
749
+ [grip, track, closeBtn]
645
750
  );
646
751
  this.host.append(this.toolbar);
752
+ this.toolbarDrag = makeDraggable(this.toolbar, {
753
+ handle: grip,
754
+ key: "lens-annotate-toolbar",
755
+ ignore: isInteractiveTarget
756
+ });
647
757
  this.syncActionButtons();
648
758
  }
649
759
  removeToolbar() {
760
+ this.toolbarDrag?.destroy();
761
+ this.toolbarDrag = null;
650
762
  this.toolbar?.remove();
651
763
  this.toolbar = null;
652
764
  this.toolBtns.clear();
@@ -683,7 +795,7 @@ var AnnotationEditor = class {
683
795
  }
684
796
  syncActionButtons() {
685
797
  const empty = this.markNodes.length === 0;
686
- if (this.undoBtn) this.undoBtn.disabled = empty;
798
+ if (this.undoBtn) this.undoBtn.disabled = this.markNodes.length <= this.sessionBase;
687
799
  if (this.clearBtn) this.clearBtn.disabled = empty;
688
800
  }
689
801
  // --------------------------------------------------------------------------
@@ -697,6 +809,7 @@ var AnnotationEditor = class {
697
809
  }
698
810
  onPointerDown = (e) => {
699
811
  if (!this.active) return;
812
+ if (this.drag) return;
700
813
  if (e.button !== void 0 && e.button !== 0) return;
701
814
  e.preventDefault();
702
815
  const p = this.toDoc(e);
@@ -746,6 +859,7 @@ var AnnotationEditor = class {
746
859
  this.drag = drag;
747
860
  window.addEventListener("pointermove", this.onPointerMove, true);
748
861
  window.addEventListener("pointerup", this.onPointerUp, true);
862
+ window.addEventListener("pointercancel", this.onPointerCancel, true);
749
863
  };
750
864
  onPointerMove = (e) => {
751
865
  const d = this.drag;
@@ -855,9 +969,18 @@ var AnnotationEditor = class {
855
969
  d.node.removeAttribute("data-lens-draft");
856
970
  this.commitMark(annotation, d.node);
857
971
  };
972
+ /** Pointer capture was cancelled (OS gesture, page hidden, etc.): drop the
973
+ * in-progress draft node and detach listeners so nothing leaks. */
974
+ onPointerCancel = () => {
975
+ const d = this.drag;
976
+ this.detachDragListeners();
977
+ this.drag = null;
978
+ d?.node.remove();
979
+ };
858
980
  detachDragListeners() {
859
981
  window.removeEventListener("pointermove", this.onPointerMove, true);
860
982
  window.removeEventListener("pointerup", this.onPointerUp, true);
983
+ window.removeEventListener("pointercancel", this.onPointerCancel, true);
861
984
  }
862
985
  commitMark(annotation, node) {
863
986
  this.annotations.push(annotation);
@@ -1051,29 +1174,31 @@ var AnnotationEditor = class {
1051
1174
  return `lens-${++this.idSeq}`;
1052
1175
  }
1053
1176
  };
1054
- var INCLUDE_ITEMS = [
1055
- { key: "replay", label: "Replay" },
1056
- { key: "console", label: "Console" },
1057
- { key: "network", label: "Network" },
1058
- { key: "errors", label: "Errors" },
1059
- { key: "screenshot", label: "Screenshot" }
1060
- ];
1061
1177
  var Panel = class {
1062
1178
  constructor(cb) {
1063
1179
  this.cb = cb;
1064
1180
  this.body = el("div", { class: "lens-panel__body" });
1065
- this.root = el("div", { class: "lens-panel", attrs: { role: "dialog", "aria-label": "Report an issue" } }, [
1066
- el("div", { class: "lens-panel__head" }, [
1067
- el("div", { class: "lens-panel__title", text: "Report an issue" }),
1068
- el("button", {
1069
- class: "lens-panel__x",
1070
- ariaLabel: "Close",
1071
- html: iconClose,
1072
- on: { click: () => this.cb.onClose() }
1073
- })
1074
- ]),
1075
- this.body
1181
+ const head = el("div", { class: "lens-panel__head" }, [
1182
+ el("span", {
1183
+ class: "lens-panel__grip",
1184
+ html: iconGrip,
1185
+ title: "Drag to move",
1186
+ ariaLabel: "Drag to move panel"
1187
+ }),
1188
+ el("div", { class: "lens-panel__title", text: "Report an issue" }),
1189
+ el("button", {
1190
+ class: "lens-panel__x",
1191
+ ariaLabel: "Close",
1192
+ html: iconClose,
1193
+ on: { click: () => this.cb.onClose() }
1194
+ })
1076
1195
  ]);
1196
+ this.root = el(
1197
+ "div",
1198
+ { class: "lens-panel", attrs: { role: "dialog", "aria-label": "Report an issue" } },
1199
+ [head, this.body]
1200
+ );
1201
+ this.drag = makeDraggable(this.root, { handle: head, key: "lens-panel" });
1077
1202
  this.renderForm();
1078
1203
  }
1079
1204
  cb;
@@ -1081,6 +1206,7 @@ var Panel = class {
1081
1206
  body;
1082
1207
  textarea;
1083
1208
  submitBtn;
1209
+ drag = null;
1084
1210
  autoCloseTimer = null;
1085
1211
  submitting = false;
1086
1212
  /** Selected capture toggles; every flag starts ON. Persists across refresh. */
@@ -1100,6 +1226,8 @@ var Panel = class {
1100
1226
  }
1101
1227
  destroy() {
1102
1228
  if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer);
1229
+ this.drag?.destroy();
1230
+ this.drag = null;
1103
1231
  this.root.remove();
1104
1232
  }
1105
1233
  renderForm() {
@@ -1114,7 +1242,6 @@ var Panel = class {
1114
1242
  this.body.append(
1115
1243
  this.textarea,
1116
1244
  this.buildContext(),
1117
- this.buildIncludes(),
1118
1245
  this.buildChips(),
1119
1246
  this.buildActions()
1120
1247
  );
@@ -1134,39 +1261,6 @@ var Panel = class {
1134
1261
  });
1135
1262
  return el("div", { class: "lens-context", title: s.url }, nodes);
1136
1263
  }
1137
- /** Real toggles ("switch"): which captured data to attach. All ON by default. */
1138
- buildIncludes() {
1139
- const s = this.cb.getStats();
1140
- const counts = {
1141
- console: s.console,
1142
- network: s.network,
1143
- errors: s.errors
1144
- };
1145
- const toggles = INCLUDE_ITEMS.map((item) => this.buildToggle(item.key, item.label, counts[item.key]));
1146
- return el("div", { class: "lens-includes" }, [
1147
- el("span", { class: "lens-includes__label", text: "Include:" }),
1148
- ...toggles
1149
- ]);
1150
- }
1151
- buildToggle(key, label, count) {
1152
- const on = this.include[key];
1153
- const btn = el("button", {
1154
- class: `lens-tgl${on ? " is-on" : ""}`,
1155
- attrs: { type: "button", role: "switch", "aria-checked": String(on) },
1156
- html: `<span class="lens-tgl__check" aria-hidden="true">${iconCheck}</span>`
1157
- });
1158
- btn.append(el("span", { class: "lens-tgl__label", text: label }));
1159
- if (typeof count === "number" && count > 0) {
1160
- btn.append(el("span", { class: "lens-tgl__count", text: String(count) }));
1161
- }
1162
- btn.addEventListener("click", () => {
1163
- const next = !this.include[key];
1164
- this.include[key] = next;
1165
- btn.classList.toggle("is-on", next);
1166
- btn.setAttribute("aria-checked", String(next));
1167
- });
1168
- return btn;
1169
- }
1170
1264
  /** Removable context chips: annotations, retained recording, picked element. */
1171
1265
  buildChips() {
1172
1266
  const row = el("div", { class: "lens-chips" });
@@ -1471,14 +1565,28 @@ var OVERLAY_CSS = (
1471
1565
  `
1472
1566
  :host {
1473
1567
  all: initial;
1474
- --lens-accent: #6366f1;
1475
- --lens-accent-strong: #4f46e5;
1568
+ /* Black & white, mirroring the Horus Cloud dashboard: the accent is near-black
1569
+ in light and near-white in dark; the always-dark pill/toolbar use explicit
1570
+ light highlights (defined at their rules). Annotation swatch colors and
1571
+ status red/green are content, not brand, and keep their hues. */
1572
+ --lens-accent: #18181b;
1573
+ --lens-accent-strong: #000000;
1574
+ --lens-on-accent: #ffffff;
1575
+ --lens-focus: rgba(0, 0, 0, 0.16);
1476
1576
  --lens-radius: 8px;
1477
1577
  --lens-font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
1478
1578
  --lens-z: 2147483000;
1479
1579
  font-family: var(--lens-font);
1480
1580
  line-height: 1.4;
1481
1581
  }
1582
+ @media (prefers-color-scheme: dark) {
1583
+ :host {
1584
+ --lens-accent: #fafafa;
1585
+ --lens-accent-strong: #ffffff;
1586
+ --lens-on-accent: #18181b;
1587
+ --lens-focus: rgba(255, 255, 255, 0.22);
1588
+ }
1589
+ }
1482
1590
 
1483
1591
  *, *::before, *::after { box-sizing: border-box; }
1484
1592
 
@@ -1498,7 +1606,7 @@ var OVERLAY_CSS = (
1498
1606
  left: 50%;
1499
1607
  transform: translateX(-50%);
1500
1608
  max-width: calc(100vw - 16px);
1501
- flex-wrap: wrap;
1609
+ flex-wrap: nowrap;
1502
1610
  justify-content: center;
1503
1611
  display: inline-flex;
1504
1612
  align-items: center;
@@ -1522,7 +1630,7 @@ var OVERLAY_CSS = (
1522
1630
  height: 26px;
1523
1631
  margin-left: 4px;
1524
1632
  border-radius: 999px;
1525
- color: #a5b4fc;
1633
+ color: #f4f4f5;
1526
1634
  }
1527
1635
  .lens-btn {
1528
1636
  display: inline-flex;
@@ -1541,11 +1649,14 @@ var OVERLAY_CSS = (
1541
1649
  transition: background 150ms ease, color 150ms ease;
1542
1650
  }
1543
1651
  .lens-btn:hover { background: rgba(255, 255, 255, 0.1); }
1544
- .lens-btn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
1545
- .lens-btn--primary { background: var(--lens-accent); color: #fff; }
1546
- .lens-btn--primary:hover { background: var(--lens-accent-strong); }
1652
+ .lens-btn:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.7); outline-offset: 1px; }
1653
+ .lens-btn--primary { background: #fff; color: #18181b; }
1654
+ .lens-btn--primary:hover { background: #e4e4e7; }
1655
+ .lens-btn--report svg { flex: none; }
1656
+ .lens-btn__label { white-space: nowrap; }
1547
1657
  .lens-btn--icon { width: 30px; padding: 0; justify-content: center; }
1548
- .lens-btn--icon.is-active { background: var(--lens-accent); color: #fff; }
1658
+ /* Always-dark pill: an active control reads as a light (white) highlight. */
1659
+ .lens-btn--icon.is-active { background: #fff; color: #18181b; }
1549
1660
  .lens-pill__sep {
1550
1661
  width: 1px;
1551
1662
  height: 18px;
@@ -1559,7 +1670,10 @@ var OVERLAY_CSS = (
1559
1670
  z-index: calc(var(--lens-z) - 2);
1560
1671
  pointer-events: none;
1561
1672
  border: 2px solid var(--lens-accent);
1562
- background: rgba(99, 102, 241, 0.14);
1673
+ /* A light outer ring so the (black-in-light / white-in-dark) border stays
1674
+ visible over any page background. */
1675
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6), 0 0 0 3px rgba(0, 0, 0, 0.15);
1676
+ background: rgba(127, 127, 127, 0.14);
1563
1677
  border-radius: 3px;
1564
1678
  transition: all 60ms linear;
1565
1679
  display: none;
@@ -1571,7 +1685,7 @@ var OVERLAY_CSS = (
1571
1685
  padding: 1px 6px;
1572
1686
  border-radius: 4px;
1573
1687
  background: var(--lens-accent);
1574
- color: #fff;
1688
+ color: var(--lens-on-accent);
1575
1689
  font: 500 11px/1.5 var(--lens-font);
1576
1690
  white-space: nowrap;
1577
1691
  }
@@ -1650,7 +1764,7 @@ var OVERLAY_CSS = (
1650
1764
  padding: 14px 16px;
1651
1765
  border-bottom: 1px solid rgba(0, 0, 0, 0.07);
1652
1766
  }
1653
- .lens-panel__title { font-size: 14px; font-weight: 600; }
1767
+ .lens-panel__title { font-size: 14px; font-weight: 600; margin-right: auto; }
1654
1768
  .lens-panel__x {
1655
1769
  display: inline-flex;
1656
1770
  align-items: center;
@@ -1688,7 +1802,7 @@ var OVERLAY_CSS = (
1688
1802
  .lens-textarea:focus {
1689
1803
  outline: none;
1690
1804
  border-color: var(--lens-accent);
1691
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);
1805
+ box-shadow: 0 0 0 3px var(--lens-focus);
1692
1806
  }
1693
1807
  /* ---- Context row: static, non-interactive page facts (muted, dot-joined) ---- */
1694
1808
  .lens-context {
@@ -1703,49 +1817,6 @@ var OVERLAY_CSS = (
1703
1817
  .lens-context__item { white-space: nowrap; }
1704
1818
  .lens-context__dot { opacity: 0.55; }
1705
1819
 
1706
- /* ---- Include row: real toggles (role=switch) for which data to attach ---- */
1707
- .lens-includes {
1708
- display: flex;
1709
- flex-wrap: wrap;
1710
- align-items: center;
1711
- gap: 6px;
1712
- }
1713
- .lens-includes__label {
1714
- font-size: 11.5px;
1715
- font-weight: 600;
1716
- color: #71717a;
1717
- margin-right: 2px;
1718
- }
1719
- .lens-tgl {
1720
- display: inline-flex;
1721
- align-items: center;
1722
- gap: 5px;
1723
- padding: 4px 10px;
1724
- border-radius: 999px;
1725
- border: 1px solid rgba(0, 0, 0, 0.14);
1726
- background: #fafafa;
1727
- color: #a1a1aa;
1728
- font-family: var(--lens-font);
1729
- font-size: 11.5px;
1730
- font-weight: 600;
1731
- white-space: nowrap;
1732
- cursor: pointer;
1733
- opacity: 0.75;
1734
- transition: background 150ms ease, border-color 150ms ease, color 150ms ease, opacity 150ms ease;
1735
- }
1736
- .lens-tgl:hover { opacity: 1; }
1737
- .lens-tgl:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
1738
- .lens-tgl__check { display: none; align-items: center; }
1739
- .lens-tgl__check svg { width: 13px; height: 13px; }
1740
- .lens-tgl__count { font-weight: 500; opacity: 0.75; font-variant-numeric: tabular-nums; }
1741
- .lens-tgl.is-on {
1742
- opacity: 1;
1743
- color: var(--lens-accent-strong);
1744
- border-color: rgba(99, 102, 241, 0.5);
1745
- background: #eef2ff;
1746
- }
1747
- .lens-tgl.is-on .lens-tgl__check { display: inline-flex; }
1748
-
1749
1820
  .lens-chips { display: flex; flex-wrap: wrap; gap: 6px; }
1750
1821
  .lens-chip {
1751
1822
  display: inline-flex;
@@ -1766,9 +1837,9 @@ var OVERLAY_CSS = (
1766
1837
  .lens-chip--ok { color: #15803d; background: #f0fdf4; border-color: rgba(21, 128, 61, 0.18); }
1767
1838
  .lens-chip--target {
1768
1839
  max-width: 100%;
1769
- background: #eef2ff;
1770
- border-color: rgba(99, 102, 241, 0.25);
1771
- color: var(--lens-accent-strong);
1840
+ background: rgba(0, 0, 0, 0.05);
1841
+ border-color: rgba(0, 0, 0, 0.16);
1842
+ color: #18181b;
1772
1843
  }
1773
1844
  .lens-chip__code {
1774
1845
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
@@ -1816,7 +1887,7 @@ var OVERLAY_CSS = (
1816
1887
  .lens-abtn svg { flex: none; }
1817
1888
  .lens-abtn--ghost { background: transparent; border-color: rgba(0, 0, 0, 0.14); color: #3f3f46; }
1818
1889
  .lens-abtn--ghost:hover { background: rgba(0, 0, 0, 0.04); }
1819
- .lens-abtn--primary { background: var(--lens-accent); color: #fff; }
1890
+ .lens-abtn--primary { background: var(--lens-accent); color: var(--lens-on-accent); }
1820
1891
  .lens-abtn--primary:hover { background: var(--lens-accent-strong); }
1821
1892
  .lens-abtn:disabled { opacity: 0.55; cursor: default; }
1822
1893
  .lens-abtn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
@@ -1870,12 +1941,9 @@ var OVERLAY_CSS = (
1870
1941
  .lens-panel__x:hover { background: rgba(255, 255, 255, 0.08); }
1871
1942
  .lens-textarea { background: #26262a; border-color: rgba(255, 255, 255, 0.14); color: #f4f4f5; }
1872
1943
  .lens-context { color: #a1a1aa; }
1873
- .lens-includes__label { color: #a1a1aa; }
1874
- .lens-tgl { background: #26262a; border-color: rgba(255, 255, 255, 0.12); color: #a1a1aa; }
1875
- .lens-tgl.is-on { background: rgba(99, 102, 241, 0.18); border-color: rgba(99, 102, 241, 0.55); color: #c7d2fe; }
1876
1944
  .lens-chip { background: #26262a; border-color: rgba(255, 255, 255, 0.08); color: #d4d4d8; }
1877
1945
  .lens-chip--ok { color: #4ade80; background: rgba(34, 197, 94, 0.12); border-color: rgba(74, 222, 128, 0.22); }
1878
- .lens-chip--target { background: rgba(99, 102, 241, 0.16); color: #c7d2fe; }
1946
+ .lens-chip--target { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.18); color: #f4f4f5; }
1879
1947
  .lens-abtn--ghost { border-color: rgba(255, 255, 255, 0.16); color: #d4d4d8; }
1880
1948
  .lens-abtn--ghost:hover { background: rgba(255, 255, 255, 0.06); }
1881
1949
  .lens-state__msg { color: #a1a1aa; }
@@ -1909,7 +1977,9 @@ var OVERLAY_CSS = (
1909
1977
  .lens-annotate-chips { z-index: var(--lens-z); }
1910
1978
 
1911
1979
  /* ---------------- Annotation toolbar ---------------- */
1912
- /* Same floating top-center anchor as the chips row; wraps on narrow screens. */
1980
+ /* Floating top-center anchor. A fixed drag grip sits left of a tools "track":
1981
+ the track wraps on desktop and becomes a single horizontal-scroll row on
1982
+ narrow screens, so the bar stays one slim height instead of a bulky block. */
1913
1983
  .lens-annotate-toolbar {
1914
1984
  position: fixed;
1915
1985
  top: calc(16px + env(safe-area-inset-top));
@@ -1917,11 +1987,10 @@ var OVERLAY_CSS = (
1917
1987
  transform: translateX(-50%);
1918
1988
  z-index: var(--lens-z);
1919
1989
  display: inline-flex;
1920
- flex-wrap: wrap;
1990
+ flex-wrap: nowrap;
1921
1991
  align-items: center;
1922
- justify-content: center;
1923
1992
  gap: 6px;
1924
- max-width: calc(100vw - 24px);
1993
+ max-width: calc(100vw - 16px);
1925
1994
  padding: 6px 8px;
1926
1995
  border-radius: 14px;
1927
1996
  background: rgba(20, 20, 23, 0.92);
@@ -1931,12 +2000,50 @@ var OVERLAY_CSS = (
1931
2000
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
1932
2001
  color: #f4f4f5;
1933
2002
  }
2003
+ /* Tools lay out as ONE horizontal row that scrolls if it can't fit, so the bar
2004
+ is always a single slim height \u2014 never a wrapping (or, worse, min-content
2005
+ collapsed vertical) block. The grip sits outside this track. */
2006
+ .lens-tool-track {
2007
+ display: flex;
2008
+ align-items: center;
2009
+ flex-wrap: nowrap;
2010
+ justify-content: flex-start;
2011
+ gap: 6px;
2012
+ flex: 1 1 auto;
2013
+ min-width: 0;
2014
+ overflow-x: auto;
2015
+ overflow-y: hidden;
2016
+ scrollbar-width: none;
2017
+ -webkit-overflow-scrolling: touch;
2018
+ }
2019
+ .lens-tool-track::-webkit-scrollbar { display: none; }
1934
2020
  .lens-tool-group {
1935
2021
  display: inline-flex;
1936
2022
  align-items: center;
1937
2023
  gap: 4px;
1938
- flex-wrap: wrap;
2024
+ flex-wrap: nowrap;
2025
+ flex: none;
1939
2026
  }
2027
+ /* Single close (=Done, keeps marks) pinned at the right, always reachable
2028
+ while the tools scroll. Reads as a light control on the dark bar. */
2029
+ .lens-tool-close {
2030
+ display: inline-flex;
2031
+ align-items: center;
2032
+ justify-content: center;
2033
+ flex: none;
2034
+ width: 32px;
2035
+ height: 30px;
2036
+ padding: 0;
2037
+ border: 0;
2038
+ border-radius: 8px;
2039
+ background: rgba(255, 255, 255, 0.14);
2040
+ color: #f4f4f5;
2041
+ cursor: pointer;
2042
+ transition: background 150ms ease;
2043
+ }
2044
+ .lens-tool-close:hover { background: rgba(255, 255, 255, 0.26); }
2045
+ .lens-tool-close:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.7); outline-offset: 1px; }
2046
+ .lens-tool-close svg { width: 18px; height: 18px; }
1940
2047
  .lens-tool-sep {
1941
2048
  width: 1px;
1942
2049
  align-self: stretch;
@@ -1944,6 +2051,51 @@ var OVERLAY_CSS = (
1944
2051
  background: rgba(255, 255, 255, 0.16);
1945
2052
  margin: 0 2px;
1946
2053
  }
2054
+
2055
+ /* ---------------- Draggable surfaces ---------------- */
2056
+ /* Every drag handle: no page pan/scroll gesture starts here, and the cursor
2057
+ signals grabbability. Applied to the pill, panel header, toolbar grip, chips. */
2058
+ .lens-drag-handle { touch-action: none; cursor: grab; }
2059
+ .lens-drag-handle:active { cursor: grabbing; }
2060
+ .is-dragging, .is-dragging * { cursor: grabbing !important; }
2061
+ .is-dragging { user-select: none; }
2062
+ /* While dragging, drop the entry animation/transition so it tracks 1:1. */
2063
+ .lens-pill.is-dragging, .lens-panel.is-dragging,
2064
+ .lens-annotate-toolbar.is-dragging, .lens-rec-chip.is-dragging {
2065
+ animation: none;
2066
+ transition: none;
2067
+ }
2068
+
2069
+ /* Toolbar drag grip \u2014 the only grab region, kept out of the scroll track. */
2070
+ .lens-tool-grip {
2071
+ display: inline-flex;
2072
+ align-items: center;
2073
+ justify-content: center;
2074
+ flex: none;
2075
+ width: 22px;
2076
+ height: 30px;
2077
+ margin-right: 2px;
2078
+ border-radius: 6px;
2079
+ color: #a1a1aa;
2080
+ }
2081
+ .lens-tool-grip:hover { color: #f4f4f5; background: rgba(255, 255, 255, 0.08); }
2082
+ .lens-tool-grip svg { width: 16px; height: 16px; }
2083
+
2084
+ /* Pill grip (the eye dot) + panel header grip. */
2085
+ .lens-pill__grip { cursor: grab; }
2086
+ .lens-panel__grip {
2087
+ display: inline-flex;
2088
+ align-items: center;
2089
+ justify-content: center;
2090
+ flex: none;
2091
+ width: 22px;
2092
+ height: 22px;
2093
+ margin-right: 6px;
2094
+ color: #a1a1aa;
2095
+ }
2096
+ .lens-panel__grip svg { width: 16px; height: 16px; }
2097
+ .lens-panel__head { cursor: grab; }
2098
+ .lens-panel__head:active { cursor: grabbing; }
1947
2099
  .lens-tool-btn {
1948
2100
  display: inline-flex;
1949
2101
  align-items: center;
@@ -1959,11 +2111,11 @@ var OVERLAY_CSS = (
1959
2111
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
1960
2112
  }
1961
2113
  .lens-tool-btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
1962
- .lens-tool-btn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
2114
+ .lens-tool-btn:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.7); outline-offset: 1px; }
1963
2115
  .lens-tool-btn.is-active {
1964
- background: var(--lens-accent);
1965
- border-color: var(--lens-accent);
1966
- color: #fff;
2116
+ background: #fff;
2117
+ border-color: #fff;
2118
+ color: #18181b;
1967
2119
  }
1968
2120
  .lens-tool-btn:disabled { opacity: 0.4; cursor: default; }
1969
2121
  .lens-tool-btn:disabled:hover { background: transparent; color: #d4d4d8; }
@@ -2003,17 +2155,18 @@ var OVERLAY_CSS = (
2003
2155
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
2004
2156
  }
2005
2157
  .lens-width-btn:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
2006
- .lens-width-btn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
2158
+ .lens-width-btn:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.7); outline-offset: 1px; }
2007
2159
  .lens-width-btn.is-active {
2008
- background: var(--lens-accent);
2009
- border-color: var(--lens-accent);
2010
- color: #fff;
2160
+ background: #fff;
2161
+ border-color: #fff;
2162
+ color: #18181b;
2011
2163
  }
2012
2164
 
2013
2165
  /* Coarse pointers: bump the tool targets toward 40px. */
2014
2166
  @media (pointer: coarse) {
2015
2167
  .lens-tool-btn, .lens-width-btn { width: 38px; height: 38px; }
2016
2168
  .lens-swatch { width: 30px; height: 30px; }
2169
+ .lens-tool-grip { width: 30px; height: 38px; }
2017
2170
  }
2018
2171
 
2019
2172
  /* Stacking inside the shadow layer: the panel, pill and floating chips always
@@ -2038,8 +2191,8 @@ var OVERLAY_CSS = (
2038
2191
  transition: background 150ms ease;
2039
2192
  }
2040
2193
  .lens-pick-chip__btn:hover { background: rgba(255, 255, 255, 0.2); }
2041
- .lens-pick-chip__btn--primary { background: var(--lens-accent); }
2042
- .lens-pick-chip__btn--primary:hover { background: var(--lens-accent-strong); }
2194
+ .lens-pick-chip__btn--primary { background: #fff; color: #18181b; }
2195
+ .lens-pick-chip__btn--primary:hover { background: #e4e4e7; }
2043
2196
 
2044
2197
  /* ---------------- Recording chip ---------------- */
2045
2198
  .lens-rec-chip {
@@ -2094,21 +2247,29 @@ var OVERLAY_CSS = (
2094
2247
  .lens-rec-chip__stop:hover { background: #dc2626; }
2095
2248
  .lens-chip--rec { gap: 6px; }
2096
2249
 
2250
+ /* Very narrow screens: collapse the pill's "Report issue" label to its icon so
2251
+ the pill stays a single slim row (never wraps into a 2-row blob). */
2252
+ @media (max-width: 400px) {
2253
+ .lens-btn__label { display: none; }
2254
+ .lens-btn--report { width: 44px; padding: 0; justify-content: center; }
2255
+ }
2256
+
2097
2257
  /* ---------------- Coarse pointers (touch): \u226544px targets ---------------- */
2098
2258
  @media (pointer: coarse) {
2099
2259
  .lens-btn { height: 44px; padding: 0 16px; }
2100
2260
  .lens-btn--icon { width: 44px; }
2261
+ .lens-tool-close { width: 44px; height: 38px; }
2101
2262
  .lens-pill__dot { width: 40px; height: 40px; }
2102
2263
  .lens-abtn { height: 44px; }
2103
- .lens-tgl { min-height: 40px; padding: 4px 14px; }
2104
2264
  .lens-panel__x { width: 44px; height: 44px; }
2105
2265
  .lens-chip__x { min-width: 32px; min-height: 32px; align-items: center; justify-content: center; }
2106
2266
  .lens-pick-chip__btn, .lens-rec-chip__stop { height: 40px; padding: 0 18px; }
2107
2267
  }
2108
2268
 
2109
- /* ---------------- Bottom sheet under 640px ---------------- */
2269
+ /* ---------------- Narrow screens (\u2264640px) ---------------- */
2110
2270
  @media (max-width: 640px) {
2111
- .lens-panel {
2271
+ /* Report panel as a bottom sheet (unless the user has dragged it loose). */
2272
+ .lens-panel:not(.is-dragging) {
2112
2273
  left: 0;
2113
2274
  right: 0;
2114
2275
  bottom: 0;
@@ -2125,6 +2286,14 @@ var OVERLAY_CSS = (
2125
2286
  to { opacity: 1; transform: none; }
2126
2287
  }
2127
2288
  .lens-textarea { font-size: 16px; }
2289
+
2290
+ /* On narrow screens the single-row tool track usually overflows: fade its
2291
+ trailing edge so it reads as "more tools this way" (the grip sits outside
2292
+ the track and stays fully opaque). */
2293
+ .lens-tool-track {
2294
+ -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent 100%);
2295
+ mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent 100%);
2296
+ }
2128
2297
  }
2129
2298
  `
2130
2299
  );
@@ -2142,8 +2311,14 @@ var Overlay = class {
2142
2311
  this.shadow.append(this.layer);
2143
2312
  this.picker = new ElementPicker(this.layer, this.host);
2144
2313
  this.annotator = new AnnotationEditor(this.layer);
2145
- this.layer.append(this.buildPill());
2314
+ const pill = this.buildPill();
2315
+ this.layer.append(pill);
2146
2316
  document.documentElement.append(this.host);
2317
+ this.pillDrag = makeDraggable(pill, {
2318
+ handle: pill,
2319
+ key: "lens-pill",
2320
+ ignore: isInteractiveTarget
2321
+ });
2147
2322
  window.addEventListener("keydown", this.onGlobalKey, true);
2148
2323
  }
2149
2324
  deps;
@@ -2156,7 +2331,9 @@ var Overlay = class {
2156
2331
  panel = null;
2157
2332
  target = null;
2158
2333
  destroyed = false;
2334
+ pillDrag = null;
2159
2335
  recordingChip = null;
2336
+ recordingDrag = null;
2160
2337
  recordingTimerEl = null;
2161
2338
  recordingInterval = null;
2162
2339
  recordingStartTs = 0;
@@ -2171,10 +2348,16 @@ var Overlay = class {
2171
2348
  on: { click: () => this.togglePicker() }
2172
2349
  });
2173
2350
  return el("div", { class: "lens-pill", attrs: { role: "toolbar", "aria-label": "Lens" } }, [
2174
- el("span", { class: "lens-pill__dot", html: iconEye }),
2351
+ el("span", {
2352
+ class: "lens-pill__dot lens-pill__grip",
2353
+ html: iconEye,
2354
+ title: "Drag to move",
2355
+ ariaLabel: "Drag to move Lens"
2356
+ }),
2175
2357
  el("button", {
2176
- class: "lens-btn",
2177
- text: "Report issue",
2358
+ class: "lens-btn lens-btn--report",
2359
+ html: iconReport + '<span class="lens-btn__label">Report issue</span>',
2360
+ ariaLabel: "Report issue",
2178
2361
  on: { click: () => this.open() }
2179
2362
  }),
2180
2363
  this.pickBtn,
@@ -2228,6 +2411,7 @@ var Overlay = class {
2228
2411
  this.stopPicker();
2229
2412
  return;
2230
2413
  }
2414
+ if (this.annotator.isActive) this.annotator.exit();
2231
2415
  this.close();
2232
2416
  this.pickBtn.classList.add("is-active");
2233
2417
  this.picker.start(
@@ -2289,6 +2473,11 @@ var Overlay = class {
2289
2473
  })
2290
2474
  ]);
2291
2475
  this.layer.append(this.recordingChip);
2476
+ this.recordingDrag = makeDraggable(this.recordingChip, {
2477
+ handle: this.recordingChip,
2478
+ key: "lens-rec-chip",
2479
+ ignore: isInteractiveTarget
2480
+ });
2292
2481
  this.recordingInterval = setInterval(() => this.tickRecording(), 250);
2293
2482
  }
2294
2483
  tickRecording() {
@@ -2301,6 +2490,10 @@ var Overlay = class {
2301
2490
  clearInterval(this.recordingInterval);
2302
2491
  this.recordingInterval = null;
2303
2492
  }
2493
+ if (this.recordingDrag) {
2494
+ this.recordingDrag.destroy();
2495
+ this.recordingDrag = null;
2496
+ }
2304
2497
  if (this.recordingChip) {
2305
2498
  this.recordingChip.remove();
2306
2499
  this.recordingChip = null;
@@ -2341,6 +2534,8 @@ var Overlay = class {
2341
2534
  if (this.destroyed) return;
2342
2535
  this.destroyed = true;
2343
2536
  window.removeEventListener("keydown", this.onGlobalKey, true);
2537
+ this.pillDrag?.destroy();
2538
+ this.pillDrag = null;
2344
2539
  this.stopPicker();
2345
2540
  if (this.deps.client.isRecording()) this.deps.client.stopRecording();
2346
2541
  this.hideRecordingChip();
@@ -2529,4 +2724,4 @@ export {
2529
2724
  boot,
2530
2725
  autoBoot
2531
2726
  };
2532
- //# sourceMappingURL=chunk-PNY6BL4C.js.map
2727
+ //# sourceMappingURL=chunk-M3QMIY4Y.js.map