@reticlehq/browser 2.13.1 → 3.1.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 (244) hide show
  1. package/dist/actions/actions-dom.d.ts +8 -20
  2. package/dist/actions/actions-dom.js +70 -69
  3. package/dist/actions/actions-press.d.ts +14 -0
  4. package/dist/actions/actions-press.js +123 -0
  5. package/dist/actions/actions.d.ts +1 -89
  6. package/dist/actions/actions.js +64 -365
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +6 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +8 -10
  12. package/dist/actions/scroll.js +28 -17
  13. package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
  14. package/dist/actions/synthetic/synthetic-input.js +13 -0
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +10 -70
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +3 -90
  22. package/dist/dom/addressing/auto-anchor.d.ts +25 -0
  23. package/dist/dom/addressing/auto-anchor.js +41 -0
  24. package/dist/dom/addressing/refs.d.ts +14 -0
  25. package/dist/dom/addressing/refs.js +114 -0
  26. package/dist/dom/addressing/source.d.ts +9 -0
  27. package/dist/dom/addressing/source.js +29 -0
  28. package/dist/dom/chart.d.ts +0 -61
  29. package/dist/dom/chart.js +0 -60
  30. package/dist/dom/desktop-capture.d.ts +0 -30
  31. package/dist/dom/desktop-capture.js +1 -73
  32. package/dist/dom/dom-ignore.d.ts +0 -30
  33. package/dist/dom/dom-ignore.js +1 -48
  34. package/dist/dom/occlusion.d.ts +0 -7
  35. package/dist/dom/occlusion.js +31 -12
  36. package/dist/dom/paint-context.d.ts +6 -0
  37. package/dist/dom/paint-context.js +38 -0
  38. package/dist/dom/query.d.ts +0 -14
  39. package/dist/dom/query.js +50 -216
  40. package/dist/dom/realm.d.ts +0 -20
  41. package/dist/dom/realm.js +0 -28
  42. package/dist/dom/shadow-registry.d.ts +0 -33
  43. package/dist/dom/shadow-registry.js +2 -72
  44. package/dist/dom/snapshot.d.ts +2 -43
  45. package/dist/dom/snapshot.js +27 -152
  46. package/dist/dom/theme.d.ts +0 -37
  47. package/dist/dom/theme.js +2 -52
  48. package/dist/dom/virtualized.d.ts +0 -17
  49. package/dist/dom/virtualized.js +0 -27
  50. package/dist/edit-epoch.d.ts +1 -23
  51. package/dist/edit-epoch.js +1 -53
  52. package/dist/index.d.ts +4 -5
  53. package/dist/index.js +4 -28
  54. package/dist/observers/animation.d.ts +0 -1
  55. package/dist/observers/animation.js +1 -4
  56. package/dist/observers/blind-spots.d.ts +0 -26
  57. package/dist/observers/blind-spots.js +3 -62
  58. package/dist/observers/console.d.ts +1 -2
  59. package/dist/observers/console.js +41 -30
  60. package/dist/observers/context-open.d.ts +1 -11
  61. package/dist/observers/context-open.js +10 -14
  62. package/dist/observers/departure.d.ts +3 -0
  63. package/dist/observers/departure.js +52 -0
  64. package/dist/observers/dialogs.d.ts +2 -0
  65. package/dist/observers/dialogs.js +39 -0
  66. package/dist/observers/dom.js +28 -52
  67. package/dist/observers/download.d.ts +1 -2
  68. package/dist/observers/download.js +39 -56
  69. package/dist/observers/focus.d.ts +0 -6
  70. package/dist/observers/focus.js +0 -7
  71. package/dist/observers/frames.d.ts +0 -9
  72. package/dist/observers/frames.js +1 -28
  73. package/dist/observers/health.d.ts +0 -5
  74. package/dist/observers/health.js +2 -34
  75. package/dist/observers/install-all.d.ts +2 -0
  76. package/dist/observers/install-all.js +26 -27
  77. package/dist/observers/ipc.d.ts +0 -25
  78. package/dist/observers/ipc.js +0 -65
  79. package/dist/observers/navigation.d.ts +2 -0
  80. package/dist/observers/navigation.js +33 -0
  81. package/dist/observers/net-detail/network-body.d.ts +12 -0
  82. package/dist/observers/net-detail/network-body.js +71 -0
  83. package/dist/observers/net-detail/network-redact.d.ts +1 -0
  84. package/dist/observers/net-detail/network-redact.js +1 -0
  85. package/dist/observers/network-stream.d.ts +0 -21
  86. package/dist/observers/network-stream.js +3 -46
  87. package/dist/observers/network.d.ts +3 -27
  88. package/dist/observers/network.js +178 -295
  89. package/dist/observers/perf.d.ts +0 -10
  90. package/dist/observers/perf.js +0 -16
  91. package/dist/observers/route.d.ts +1 -2
  92. package/dist/observers/route.js +13 -19
  93. package/dist/observers/scroll.d.ts +0 -1
  94. package/dist/observers/scroll.js +2 -11
  95. package/dist/observers/sdk-failure.d.ts +1 -9
  96. package/dist/observers/sdk-failure.js +1 -30
  97. package/dist/observers/state.d.ts +1 -11
  98. package/dist/observers/state.js +13 -41
  99. package/dist/observers/storage.d.ts +0 -14
  100. package/dist/observers/storage.js +10 -34
  101. package/dist/observers/types.d.ts +0 -15
  102. package/dist/observers/types.js +0 -14
  103. package/dist/patching/capture-method.d.ts +0 -24
  104. package/dist/patching/capture-method.js +0 -26
  105. package/dist/presenter/chrome/layers.d.ts +2 -0
  106. package/dist/presenter/chrome/layers.js +2 -0
  107. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  108. package/dist/presenter/chrome/log-kinds.js +7 -0
  109. package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
  110. package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
  111. package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
  112. package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
  113. package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
  114. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
  115. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
  116. package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
  117. package/dist/presenter/chrome/presenter-log.d.ts +17 -0
  118. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
  119. package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
  120. package/dist/presenter/chrome/presenter-report-copy.js +95 -0
  121. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  122. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  123. package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
  124. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
  125. package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
  126. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
  127. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
  128. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
  129. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
  130. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  131. package/dist/presenter/presenter-account-styles.js +99 -0
  132. package/dist/presenter/presenter-account.d.ts +40 -0
  133. package/dist/presenter/presenter-account.js +178 -0
  134. package/dist/presenter/presenter-config.d.ts +10 -107
  135. package/dist/presenter/presenter-config.js +5 -92
  136. package/dist/presenter/presenter-controls.d.ts +1 -35
  137. package/dist/presenter/presenter-controls.js +10 -133
  138. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  139. package/dist/presenter/presenter-dock-layout.js +1 -5
  140. package/dist/presenter/presenter-drag.d.ts +2 -42
  141. package/dist/presenter/presenter-drag.js +3 -76
  142. package/dist/presenter/presenter-glow.d.ts +0 -21
  143. package/dist/presenter/presenter-glow.js +2 -28
  144. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  145. package/dist/presenter/presenter-hud-position.js +50 -0
  146. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  147. package/dist/presenter/presenter-report-styles.js +22 -7
  148. package/dist/presenter/presenter-report.d.ts +5 -7
  149. package/dist/presenter/presenter-report.js +44 -95
  150. package/dist/presenter/presenter-run-state.d.ts +1 -8
  151. package/dist/presenter/presenter-run-state.js +1 -6
  152. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  153. package/dist/presenter/presenter-settings-styles.js +2 -2
  154. package/dist/presenter/presenter-settings.d.ts +5 -23
  155. package/dist/presenter/presenter-settings.js +21 -33
  156. package/dist/presenter/presenter-shell-styles.js +23 -20
  157. package/dist/presenter/presenter-shell.d.ts +6 -18
  158. package/dist/presenter/presenter-shell.js +26 -77
  159. package/dist/presenter/presenter-styles.d.ts +2 -10
  160. package/dist/presenter/presenter-styles.js +6 -12
  161. package/dist/presenter/presenter-workspace.d.ts +4 -4
  162. package/dist/presenter/presenter-workspace.js +30 -10
  163. package/dist/presenter/presenter.d.ts +3 -63
  164. package/dist/presenter/presenter.js +29 -132
  165. package/dist/presenter/tour/tour-view.d.ts +33 -0
  166. package/dist/presenter/tour/tour-view.js +188 -0
  167. package/dist/presenter/tour/tour.d.ts +25 -0
  168. package/dist/presenter/tour/tour.js +245 -0
  169. package/dist/recorder/recorder-styles.d.ts +0 -6
  170. package/dist/recorder/recorder-styles.js +0 -6
  171. package/dist/recorder/recorder.d.ts +0 -34
  172. package/dist/recorder/recorder.js +6 -49
  173. package/dist/registry/auto-testids.d.ts +0 -19
  174. package/dist/registry/auto-testids.js +1 -21
  175. package/dist/registry/capabilities.d.ts +0 -31
  176. package/dist/registry/capabilities.js +1 -46
  177. package/dist/registry/commit-and-signal.d.ts +0 -6
  178. package/dist/registry/commit-and-signal.js +1 -7
  179. package/dist/registry/declared-channels.d.ts +2 -0
  180. package/dist/registry/declared-channels.js +14 -0
  181. package/dist/registry/declared-platform.d.ts +4 -0
  182. package/dist/registry/declared-platform.js +34 -0
  183. package/dist/registry/domains.d.ts +0 -8
  184. package/dist/registry/domains.js +0 -7
  185. package/dist/registry/emitter.d.ts +0 -14
  186. package/dist/registry/emitter.js +1 -11
  187. package/dist/registry/store-adapters.d.ts +0 -186
  188. package/dist/registry/store-adapters.js +1 -164
  189. package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
  190. package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
  191. package/dist/registry/stores.d.ts +0 -33
  192. package/dist/registry/stores.js +1 -60
  193. package/dist/reticle-presenter-helpers.d.ts +0 -13
  194. package/dist/reticle-presenter-helpers.js +10 -20
  195. package/dist/reticle-singleton.d.ts +2 -0
  196. package/dist/reticle-singleton.js +3 -0
  197. package/dist/reticle.d.ts +1 -52
  198. package/dist/reticle.js +115 -180
  199. package/dist/review/annotator-styles.js +1 -8
  200. package/dist/review/annotator.d.ts +2 -13
  201. package/dist/review/annotator.js +2 -42
  202. package/dist/review/mark-anchor.d.ts +0 -11
  203. package/dist/review/mark-anchor.js +3 -11
  204. package/dist/security/serialization.d.ts +0 -20
  205. package/dist/security/serialization.js +1 -66
  206. package/dist/session-continuity.d.ts +0 -16
  207. package/dist/session-continuity.js +1 -20
  208. package/dist/timers/clock.d.ts +1 -14
  209. package/dist/timers/clock.js +59 -60
  210. package/dist/timers/console/native-console.js +5 -0
  211. package/dist/timers/native/native-timers.d.ts +11 -0
  212. package/dist/timers/native/native-timers.js +48 -0
  213. package/dist/transport/transport.d.ts +0 -51
  214. package/dist/transport/transport.js +5 -131
  215. package/dist/transport/unreachable-message.d.ts +0 -17
  216. package/dist/transport/unreachable-message.js +0 -17
  217. package/dist/util/captured-method.d.ts +0 -35
  218. package/dist/util/captured-method.js +0 -35
  219. package/package.json +5 -4
  220. package/dist/actions/synthetic-input.d.ts +0 -9
  221. package/dist/actions/synthetic-input.js +0 -40
  222. package/dist/dom/auto-anchor.d.ts +0 -65
  223. package/dist/dom/auto-anchor.js +0 -74
  224. package/dist/dom/refs.d.ts +0 -90
  225. package/dist/dom/refs.js +0 -245
  226. package/dist/dom/source.d.ts +0 -53
  227. package/dist/dom/source.js +0 -74
  228. package/dist/observers/network-body.d.ts +0 -35
  229. package/dist/observers/network-body.js +0 -168
  230. package/dist/observers/network-redact.d.ts +0 -20
  231. package/dist/observers/network-redact.js +0 -80
  232. package/dist/presenter/presenter-brand.d.ts +0 -22
  233. package/dist/presenter/presenter-effects.d.ts +0 -13
  234. package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
  235. package/dist/presenter/presenter-log.d.ts +0 -64
  236. package/dist/presenter/presenter-report-copy.d.ts +0 -112
  237. package/dist/presenter/presenter-report-copy.js +0 -156
  238. package/dist/presenter/presenter-test-helpers.d.ts +0 -16
  239. package/dist/presenter/presenter-test-helpers.js +0 -41
  240. package/dist/timers/native-console.js +0 -9
  241. package/dist/timers/native-timers.d.ts +0 -36
  242. package/dist/timers/native-timers.js +0 -78
  243. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  244. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
@@ -1,28 +1,23 @@
1
- import { ActionType, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
2
- import { refs } from '../dom/refs.js';
1
+ import { ActionType, HumanControlKind, ReticleCommand, PresenterMode, SessionState, isPresenterTone, isSessionState, } from '@reticlehq/core';
2
+ import { refs } from '../dom/addressing/refs.js';
3
3
  import { unreachableStripText } from '../transport/unreachable-message.js';
4
- import { actionVerb } from './presenter-verbs.js';
5
- import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native-timers.js';
6
- import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './presenter-log.js';
4
+ import { actionVerb } from './chrome/presenter-verbs.js';
5
+ import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native/native-timers.js';
6
+ import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './chrome/presenter-log.js';
7
7
  import { PRESENTER_CSS } from './presenter-styles.js';
8
8
  import { HudShell } from './presenter-shell.js';
9
- import { parseImpactSnapshot } from './presenter-report-copy.js';
9
+ import { parseImpactSnapshot } from './chrome/presenter-report-copy.js';
10
10
  import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
11
11
  import { buildRunState } from './presenter-run-state.js';
12
- import { moveCursor, ringAround, spawnRipple, pace } from './presenter-effects.js';
12
+ import { moveCursor, ringAround, spawnRipple, pace } from './chrome/presenter-effects.js';
13
13
  import { GlowController } from './presenter-glow.js';
14
- import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './presenter-icons.js';
15
- import { renderTally } from './presenter-tally.js';
14
+ import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './icons/presenter-icons.js';
15
+ import { renderTally } from './chrome/presenter-tally.js';
16
16
  import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
17
17
  import { statusTheme, blockerHtml, getPresenterSettings, OutputDetail, syncPageBlocker, } from './presenter-settings.js';
18
18
  import { Annotator } from '../review/annotator.js';
19
- // Re-export the config surface so the public import path (`./presenter.js`) is unchanged.
20
19
  export { GlowPhase } from './presenter-config.js';
21
- export { LOG_KIND, LOG_RESULT } from './presenter-log.js';
22
- // Presenter / transparency layer: a human watches the agent work. Glowing border while
23
- // active, a synthetic cursor that flies to targets, click/hover/type effects, and a HUD that
24
- // shows the current action + the agent's narrated intent. All nodes carry data-reticle-* attrs
25
- // so they're excluded from snapshots/observers (see dom-ignore.ts).
20
+ export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
26
21
  export class Presenter {
27
22
  #paceMs;
28
23
  #root;
@@ -32,12 +27,9 @@ export class Presenter {
32
27
  #hud;
33
28
  #actLine;
34
29
  #actStrip;
35
- /** Label inside the minimised-chat capsule; mirrors the act strip. */
36
30
  #chatPillText;
37
- /** Age of the last action, shown in the capsule's own slot. */
38
31
  #chatPillTime;
39
32
  #chip;
40
- /** Live verdict tally (✓N ✗M) in the header - the running testing score the human watches. */
41
33
  #tally;
42
34
  #tallied = { passes: 0, fails: 0 };
43
35
  #mode = PresenterMode.IDLE;
@@ -45,25 +37,18 @@ export class Presenter {
45
37
  #heartbeatMs;
46
38
  #idleNoticeMs;
47
39
  #borderMode;
48
- /** The glow / activity state machine (border shimmer + cursor visibility from activity timing). */
49
40
  #glowCtl;
50
- /** Liveness: the most recent action text + a 1s ticker that ages it into an "idle · {dur}" clock. */
51
41
  #lastActionText = '';
52
42
  #heartbeatTimer;
53
- /** Session lifecycle: idle-end window (tweakable), session id, start/end cursors, structured run log. */
54
43
  #idleEndMs;
55
44
  #sessionId;
56
45
  #startMs;
57
46
  #endMs;
58
47
  #runLog = [];
59
- /** Tracks sessionStart/sessionEnd so both are idempotent (no strobe / no spurious off-write). */
60
48
  #sessionActive = false;
61
- // v2: narration + action status accumulate in a persistent, timestamped, scrollable log.
62
49
  #logMax;
63
50
  #log;
64
- /** now of the first row, the baseline for the +elapsed timestamps. */
65
51
  #logBaseMs;
66
- // Live-control panel: the two-way control surface (Pause/Resume + End + message Send).
67
52
  #onControl;
68
53
  #panel;
69
54
  #shell;
@@ -100,6 +85,7 @@ export class Presenter {
100
85
  onExpand: () => this.#syncAnnotator(),
101
86
  onAnnotateToggle: () => this.#syncAnnotator(),
102
87
  onCollapse: () => this.#syncAnnotator(),
88
+ onSyncNow: () => this.#onControl?.({ kind: HumanControlKind.SYNC }),
103
89
  settings: {
104
90
  onBeforeOpen: () => {
105
91
  if (this.#shell.isCollapsed())
@@ -110,26 +96,18 @@ export class Presenter {
110
96
  },
111
97
  });
112
98
  }
113
- /** Setter so reticle.ts can wire the control callback after construction. */
114
99
  setControlHandler(handler) {
115
100
  this.#onControl = handler;
116
101
  }
117
- /** Current live-control session state mirrored onto the panel (data-reticle-state). */
118
102
  get state() {
119
103
  return this.#panel.state;
120
104
  }
121
- /** Whether a run is currently being presented (false before the agent's first activity / after end). */
122
105
  get sessionActive() {
123
106
  return this.#sessionActive;
124
107
  }
125
- /** Drive the panel's live-control visual state (server-push / agent path; never emits). */
126
108
  setState(state, text, tone) {
127
109
  this.#panel.setState(state, text, tone);
128
110
  }
129
- /** Apply a bridge→browser presenter push: PRESENTER (state echo) or FLOWS (replay list, the human's
130
- * no-agent replay surface). Owns the wire parsing so the SDK dispatcher stays a thin router;
131
- * setState-only so an echo can't re-emit. */
132
- /** Re-scope the replay-flow chips to the current page (called by the SDK on route change). */
133
111
  refilterFlows() {
134
112
  this.#panel.refilterFlows();
135
113
  }
@@ -139,8 +117,15 @@ export class Presenter {
139
117
  return void this.#panel.setFlows(a['flows']);
140
118
  if (command.name === ReticleCommand.IMPACT) {
141
119
  const snapshot = parseImpactSnapshot(a['snapshot']);
142
- if (snapshot !== undefined)
120
+ if (snapshot !== undefined) {
143
121
  this.#shell.report.setSnapshot(snapshot);
122
+ this.#shell.paintAccount(snapshot.account, snapshot.dashboardUrl, {
123
+ projectName: snapshot.projectName,
124
+ dashboardUrl: snapshot.dashboardUrl,
125
+ verdicts: snapshot.project.counts.verdicts,
126
+ defects: snapshot.project.counts.failed,
127
+ });
128
+ }
144
129
  return;
145
130
  }
146
131
  const state = a['state'];
@@ -149,7 +134,6 @@ export class Presenter {
149
134
  if (isSessionState(state))
150
135
  this.setState(state, text, isPresenterTone(tone) ? tone : undefined);
151
136
  }
152
- /** Current cap on accumulated log rows. */
153
137
  get logMax() {
154
138
  return this.#logMax;
155
139
  }
@@ -166,10 +150,6 @@ export class Presenter {
166
150
  document.head.appendChild(style);
167
151
  const root = document.createElement('div');
168
152
  root.setAttribute('data-reticle-overlay', '');
169
- // The mode rides ON the status row, next to the dot and the elapsed time it already reports.
170
- // It used to be a sibling BELOW the strip, hidden except while reading or acting — so every
171
- // single tool call popped a block into the panel and took it away again, which reads as a
172
- // second UI flashing in rather than as the one status line changing what it says.
173
153
  const actStrip = `<div class="reticle-act-strip" data-liveness="idle"><span class="reticle-act-dot" aria-hidden="true"></span><span class="reticle-act">${ACT_STRIP.READY}</span><span class="reticle-chip" data-reticle-chip></span></div>`;
174
154
  root.innerHTML = `
175
155
  ${blockerHtml()}
@@ -195,12 +175,10 @@ export class Presenter {
195
175
  this.#shell.mount(root);
196
176
  syncPageBlocker(root, getPresenterSettings(), false);
197
177
  this.#glowCtl.setElements(this.#glow, this.#cursor);
198
- // The panel queries its refs, binds listeners, and paints the initial active state.
199
178
  this.#panel.mount(root, this.#glow);
200
179
  this.setMode(this.#mode);
201
180
  this.#renderTally();
202
181
  }
203
- /** Wire annotation chrome; expanding the HUD enters annotate mode. */
204
182
  bindAnnotator(annotator) {
205
183
  this.#annotator = annotator;
206
184
  const root = this.#root;
@@ -220,12 +198,7 @@ export class Presenter {
220
198
  annotator.setAccent(statusTheme(getPresenterSettings().statusThemeId).active);
221
199
  this.#syncAnnotator();
222
200
  }
223
- /** Annotate whenever the HUD is open and the person asked for it - agent or no agent. */
224
201
  #syncAnnotator() {
225
- // Two things have to agree, and both are the user's: the HUD is open, and annotate is switched
226
- // on. Session state used to be a third, so the mode was refused the moment the agent
227
- // disconnected - which is precisely when someone opens the HUD to record what they just saw.
228
- // The button still lit up, so the refusal was invisible: no outline, no composer, no reason.
229
202
  const live = !this.#shell.isCollapsed() && this.#shell.isAnnotateOn();
230
203
  this.#annotator?.toggle(live);
231
204
  if (this.#root !== undefined) {
@@ -246,19 +219,13 @@ export class Presenter {
246
219
  document.querySelectorAll('style[data-reticle-overlay]').forEach((s) => s.remove());
247
220
  this.#root = undefined;
248
221
  }
249
- /**
250
- * Session start: in 'session' border mode this fades the base border IN and keeps it on until
251
- * sessionEnd. Idempotent, and a no-op when unmounted or in 'busy' border mode.
252
- */
253
222
  sessionStart() {
254
- // Returning agent activity after the session ended (idle or explicit) revives it as a fresh run.
255
223
  if (this.state === SessionState.ENDED) {
256
224
  this.#revive();
257
225
  return;
258
226
  }
259
227
  if (this.#sessionActive)
260
228
  return;
261
- // A bridge that answered supersedes whatever the failed connect left on screen.
262
229
  if (UNREACHABLE_STATE === this.#root?.getAttribute(STATE_ATTR))
263
230
  this.#root.removeAttribute(STATE_ATTR);
264
231
  this.#sessionActive = true;
@@ -271,43 +238,25 @@ export class Presenter {
271
238
  this.#shell.openChat();
272
239
  }
273
240
  }
274
- /**
275
- * The bridge never answered: show that, rather than showing nothing.
276
- *
277
- * An instrumented page with a dead bridge used to be indistinguishable from a page with no
278
- * Reticle in it — overlay mounted, dock off, nothing on screen. The user cannot tell "I forgot to
279
- * start the daemon" from "the install did not work", and the commonest cause is the cheapest to
280
- * say: the port. So the HUD appears, states the URL it tried, and marks itself unreachable so it
281
- * is never mistaken for a live session.
282
- *
283
- * Not an error dialog and not modal: the page is the user's, and a dev overlay that shouts is one
284
- * they turn off. It is the same capsule they would have had, saying the one thing it knows.
285
- */
286
241
  showUnreachable(url, attempts) {
287
242
  if (this.#sessionActive)
288
243
  return;
289
244
  this.#root?.setAttribute(STATE_ATTR, UNREACHABLE_STATE);
290
- // Dock and HUD, but NOT the border glow: the glow means the agent is working, and nothing is.
291
245
  const dock = this.#root?.querySelector('[data-reticle-dock]');
292
246
  dock?.setAttribute(DATA_ON, GLOW_ON);
293
247
  this.#hud?.setAttribute(DATA_ON, GLOW_ON);
294
248
  this.#lastActionText = unreachableStripText(url, attempts);
295
249
  this.#paintActStrip(this.#lastActionText, true);
296
- // The message IS the reason this state exists, and a collapsed capsule hides it. Same setting
297
- // a live session honours, so a user who wants the bare toolbar still gets one.
298
250
  if (getPresenterSettings().autoOpenChat)
299
251
  this.#shell.openChat();
300
252
  }
301
- /** Turn the base border (session mode) + the HUD/log on - the visible "session is live" state. */
302
253
  #showSession() {
303
254
  const dock = this.#root?.querySelector('[data-reticle-dock]');
304
255
  dock?.setAttribute(DATA_ON, GLOW_ON);
305
256
  this.#hud?.setAttribute(DATA_ON, GLOW_ON);
306
- // Base border persists in 'session' mode; 'busy' mode leaves it to the busy machine.
307
257
  if (this.#borderMode === BorderMode.SESSION)
308
258
  this.#glow?.setAttribute(DATA_ON, GLOW_ON);
309
259
  }
310
- /** Revive after an ended session (new agent activity): clear the ended state + glow back on. */
311
260
  #revive() {
312
261
  this.#panel.setState(SessionState.ACTIVE);
313
262
  this.#endMs = undefined;
@@ -315,10 +264,6 @@ export class Presenter {
315
264
  this.#glowCtl.resetActivity(this.#now());
316
265
  this.#startHeartbeat();
317
266
  }
318
- /**
319
- * Session end: hides the log/HUD and (in 'session' mode) clears the base border. Idempotent; a
320
- * no-op without a prior sessionStart or when unmounted.
321
- */
322
267
  sessionEnd() {
323
268
  if (!this.#sessionActive)
324
269
  return;
@@ -336,30 +281,18 @@ export class Presenter {
336
281
  this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
337
282
  }
338
283
  }
339
- /**
340
- * Record agent activity. Idempotent while busy - only the first activity from idle/fading flips
341
- * the glow on, so a burst never restarts the reticle-pulse animation (no strobe). Subsequent calls
342
- * just refresh the last-activity timestamp and re-arm the idle check.
343
- */
344
284
  markActivity() {
345
285
  this.#glowCtl.markActivity();
346
286
  }
347
- /** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
348
287
  scheduleIdle() {
349
288
  this.#glowCtl.scheduleIdle();
350
289
  }
351
- /** Test/diagnostic accessor for the current glow phase. */
352
290
  glowPhase() {
353
291
  return this.#glowCtl.phase();
354
292
  }
355
- /** Current intent (reading vs acting), exposed for tests + the watcher. */
356
293
  get mode() {
357
294
  return this.#mode;
358
295
  }
359
- /**
360
- * Set the presenter intent. READING shows a cyan scan + chip and hides the cursor; ACTING
361
- * keeps the warm cursor/ripple + chip; IDLE clears the chip. Drives color via data-reticle-mode.
362
- */
363
296
  setMode(mode) {
364
297
  this.#mode = mode;
365
298
  this.#root?.setAttribute('data-reticle-mode', mode);
@@ -381,7 +314,6 @@ export class Presenter {
381
314
  this.#chip.appendChild(labelEl);
382
315
  }
383
316
  }
384
- // READING has no real pointer to show (synthetic-hover pointer is native-only) - hide the cursor.
385
317
  if (mode === PresenterMode.READING)
386
318
  this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
387
319
  }
@@ -392,13 +324,6 @@ export class Presenter {
392
324
  this.#lastActionText = text;
393
325
  this.#paintActStrip(text, false);
394
326
  }
395
- /**
396
- * Sync act-strip text + the live/idle state.
397
- *
398
- * The liveness is mirrored onto the overlay root as well as the strip, because that is what the
399
- * status COLOUR resolves against: the page glow, the collapsed FAB's halo and the minimised
400
- * capsule all live outside the strip and still have to say whether the agent is working.
401
- */
402
327
  #paintActStrip(text, idle) {
403
328
  if (this.#actLine !== undefined)
404
329
  this.#actLine.textContent = text;
@@ -407,9 +332,6 @@ export class Presenter {
407
332
  this.#actStrip.setAttribute('data-liveness', liveness);
408
333
  this.#root?.setAttribute(LIVENESS_ATTR, liveness);
409
334
  if (this.#chatPillText !== undefined) {
410
- // The capsule reads "logo | what the agent did | how long ago | expand", so it takes the
411
- // ACTION, never the composed "idle · 12s since last action" line the strip shows: the age
412
- // lives in its own slot next to it.
413
335
  this.#chatPillText.textContent = idle
414
336
  ? this.#lastActionText !== ''
415
337
  ? this.#lastActionText
@@ -417,12 +339,6 @@ export class Presenter {
417
339
  : text;
418
340
  }
419
341
  }
420
- /**
421
- * Liveness heartbeat (native 1s timer - never rAF, so it ticks in a foreground tab regardless of
422
- * agent activity). Once the agent has been quiet for IDLE_NOTICE_MS, the act strip shows a LIVE,
423
- * growing "◌ idle · {duration} since last action" - the signal that was missing when a stopped
424
- * agent left the panel frozen and indistinguishable from one still thinking.
425
- */
426
342
  #startHeartbeat() {
427
343
  if (this.#heartbeatTimer !== undefined)
428
344
  nativeClearTimeout(this.#heartbeatTimer);
@@ -436,39 +352,35 @@ export class Presenter {
436
352
  if (!this.#sessionActive || this.#actLine === undefined)
437
353
  return;
438
354
  if (this.state === SessionState.ENDED)
439
- return; // already ended - leave the summary
355
+ return;
440
356
  const idleMs = this.#now() - this.#glowCtl.lastActivityMs();
441
357
  if (idleMs >= this.#idleEndMs) {
442
- this.#endIdle(idleMs); // crossed the idle-end window → auto-end (glow off, panel kept)
358
+ this.#endIdle(idleMs);
443
359
  return;
444
360
  }
445
361
  if (idleMs < this.#idleNoticeMs)
446
- return; // still active (or a brief think) - keep the action text
447
- const since = this.#lastActionText !== '' ? ACT_STRIP.SINCE_LAST : '';
448
- this.#paintActStrip(`${ACT_STRIP.IDLE_PREFIX}${humanDuration(idleMs)}${since}`, true);
362
+ return;
363
+ if ('' === this.#lastActionText)
364
+ return;
365
+ const since = ACT_STRIP.SINCE_LAST;
366
+ this.#paintActStrip(`${ACT_STRIP.PLANNING_PREFIX}${humanDuration(idleMs)}${since}`, true);
449
367
  if (this.#chatPillTime !== undefined)
450
368
  this.#chatPillTime.textContent = humanDuration(idleMs);
451
369
  }
452
- /** Auto-end after the idle window: stamp the end, drive the panel to ENDED, stop the heartbeat. */
453
370
  #endIdle(idleMs) {
454
371
  this.#endMs = this.#now();
455
- this.#panel.setState(SessionState.ENDED, `idle ${humanDuration(idleMs)}`);
372
+ this.#panel.setState(SessionState.ENDED, `${ACT_STRIP.STOPPED_PREFIX}${humanDuration(idleMs)} quiet`);
373
+ this.#paintActStrip(ACT_STRIP.STOPPED, false);
456
374
  if (this.#heartbeatTimer !== undefined) {
457
375
  nativeClearTimeout(this.#heartbeatTimer);
458
376
  this.#heartbeatTimer = undefined;
459
377
  }
460
378
  }
461
- /** Agent-tunable idle-end window (reticle_session). Floored so it can't be set uselessly small. */
462
379
  setIdleEndMs(ms) {
463
380
  if (!Number.isFinite(ms))
464
381
  return;
465
382
  this.#idleEndMs = Math.max(IDLE_END_MIN_MS, Math.floor(ms));
466
383
  }
467
- /**
468
- * The exported "run state" for the Copy/Export buttons - everything the page holds about this
469
- * run: session id, url, duration, capability surface, per-kind counts, and the full activity log.
470
- * (The full network/console ring-buffer lives server-side; this is the in-page run summary.)
471
- */
472
384
  runState() {
473
385
  const base = buildRunState({
474
386
  sessionId: this.#sessionId,
@@ -518,11 +430,6 @@ export class Presenter {
518
430
  syncPageBlocker(this.#root, settings, live);
519
431
  }
520
432
  }
521
- /**
522
- * Append an activity-log row. Accumulates (never overwrites): each call adds a timestamped row
523
- * with a mode chip + text. Returns a handle to stamp the row's outcome glyph (✓/✗) later, or
524
- * undefined when unmounted / when the text is empty after trimming.
525
- */
526
433
  log(kind, text, result) {
527
434
  const ms = this.#now();
528
435
  this.#glowCtl.markActivity(ms);
@@ -532,7 +439,6 @@ export class Presenter {
532
439
  if (0 === trimmed.length)
533
440
  return undefined;
534
441
  this.#logBaseMs ??= ms;
535
- // Structured run-log entry (mirrors the DOM row) for the exported run state, capped like the DOM.
536
442
  const entry = result !== undefined
537
443
  ? { at: ms - this.#logBaseMs, kind, text: trimmed, result }
538
444
  : { at: ms - this.#logBaseMs, kind, text: trimmed };
@@ -546,16 +452,14 @@ export class Presenter {
546
452
  if (this.#shell.isCollapsed())
547
453
  this.#shell.pulseFab(true);
548
454
  this.#renderTally();
549
- // Wrap the handle so a later outcome stamp updates BOTH the DOM glyph and the run-log entry.
550
455
  return {
551
456
  result: (r) => {
552
457
  handle.result(r);
553
458
  entry.result = r;
554
- this.#renderTally(); // a deferred ✓/✗ stamp bumps the header tally
459
+ this.#renderTally();
555
460
  },
556
461
  };
557
462
  }
558
- /** Repaint the header verdict tally from the run log; the side that grew gets a one-shot pop. */
559
463
  #renderTally() {
560
464
  if (!getPresenterSettings().showTally) {
561
465
  this.#tally?.setAttribute('hidden', '');
@@ -563,7 +467,6 @@ export class Presenter {
563
467
  }
564
468
  this.#tallied = renderTally(this.#tally, this.#runLog, this.#tallied);
565
469
  }
566
- /** Back-compat: narration appends to the live log (append-only, never overwrites). */
567
470
  narrate(text, level = 'info') {
568
471
  const line = 'info' === level ? text : `[${level}] ${text}`;
569
472
  return this.log(LOG_KIND.NARRATION, line);
@@ -575,11 +478,6 @@ export class Presenter {
575
478
  this.#log.firstElementChild?.remove();
576
479
  }
577
480
  }
578
- /**
579
- * Mirror the server's session.throttled state onto the HUD border. When throttled (tab
580
- * backgrounded or stale), the border turns amber so the developer knows actions are no-oping -
581
- * the same signal the agent already reads from result.session.throttled.
582
- */
583
481
  setThrottled(throttled) {
584
482
  this.#root?.setAttribute(THROTTLED_ATTR, throttled ? '1' : '0');
585
483
  if (throttled && this.#actLine !== undefined) {
@@ -587,7 +485,6 @@ export class Presenter {
587
485
  'Tab backgrounded - actions throttled. Bring tab to front or use `reticle drive`.';
588
486
  }
589
487
  }
590
- /** Fly the cursor to an element, play the action's effect, then pace for the human. */
591
488
  async beforeAct(refId, action, label) {
592
489
  const el = refs.resolve(refId);
593
490
  this.status(`${actionVerb(action)} ${label}`);
@@ -0,0 +1,33 @@
1
+ import { TourAnchor, type TourPrompt } from '@reticlehq/core/tour';
2
+ export declare const HANDOFF_INDEX: number;
3
+ export declare const SLIDE_COUNT: number;
4
+ export declare const TOUR_ATTR = "data-reticle-tour";
5
+ export declare const TOUR_TARGET_ATTR = "data-reticle-tour-target";
6
+ export declare const TOUR_PROMPT_ATTR = "data-reticle-tour-prompt";
7
+ export declare const COPY_LABEL = "Copy";
8
+ export declare const COPIED_LABEL = "Copied";
9
+ export declare const COPY_MANUAL_LABEL = "Selected \u2014 press Ctrl/Cmd+C";
10
+ export declare const COPY_FLASH_MS = 1600;
11
+ export interface TourSlide {
12
+ readonly index: number;
13
+ readonly title: string;
14
+ readonly body: string;
15
+ readonly anchor: TourAnchor;
16
+ readonly tryIt?: string;
17
+ readonly prompts?: readonly TourPrompt[];
18
+ }
19
+ export declare const isInteractive: (slide: TourSlide) => boolean;
20
+ export declare function tourSlides(): readonly TourSlide[];
21
+ export declare function escapeHtml(text: string): string;
22
+ export declare function withInlineCode(text: string): string;
23
+ export declare function nextIndex(current: number, delta: number): number;
24
+ export declare const isLastSlide: (index: number) => boolean;
25
+ export interface TourRect {
26
+ readonly left: number;
27
+ readonly top: number;
28
+ readonly width: number;
29
+ readonly height: number;
30
+ }
31
+ export declare function holeRects(box: TourRect, viewWidth: number, viewHeight: number): readonly TourRect[];
32
+ export declare function slideHtml(slide: TourSlide): string;
33
+ export declare const TOUR_CSS: string;
@@ -0,0 +1,188 @@
1
+ import { TOUR_STEPS, TOUR_HUD_STEPS, TOUR_HANDOFF_PROMPTS, TourAnchor, } from '@reticlehq/core/tour';
2
+ import { Z_TOUR } from '../chrome/layers.js';
3
+ const ORDERED_STEPS = [...TOUR_STEPS.slice(0, 1), ...TOUR_HUD_STEPS];
4
+ export const HANDOFF_INDEX = ORDERED_STEPS.length;
5
+ export const SLIDE_COUNT = ORDERED_STEPS.length + 1;
6
+ export const TOUR_ATTR = 'data-reticle-tour';
7
+ export const TOUR_TARGET_ATTR = 'data-reticle-tour-target';
8
+ export const TOUR_PROMPT_ATTR = 'data-reticle-tour-prompt';
9
+ export const COPY_LABEL = 'Copy';
10
+ export const COPIED_LABEL = 'Copied';
11
+ export const COPY_MANUAL_LABEL = 'Selected — press Ctrl/Cmd+C';
12
+ export const COPY_FLASH_MS = 1600;
13
+ const stepSlide = (step, index) => ({
14
+ index,
15
+ title: step.title,
16
+ body: step.say,
17
+ anchor: step.anchor ?? TourAnchor.NONE,
18
+ ...(undefined === step.tryIt ? {} : { tryIt: step.tryIt }),
19
+ });
20
+ export const isInteractive = (slide) => undefined !== slide.tryIt;
21
+ export function tourSlides() {
22
+ return [
23
+ ...ORDERED_STEPS.map(stepSlide),
24
+ {
25
+ index: HANDOFF_INDEX,
26
+ title: 'Get Started',
27
+ body: 'Paste one of these into Claude Code, Cursor, Codex, or whatever you use.',
28
+ anchor: TourAnchor.NONE,
29
+ prompts: TOUR_HANDOFF_PROMPTS,
30
+ },
31
+ ];
32
+ }
33
+ export function escapeHtml(text) {
34
+ return text
35
+ .split('&')
36
+ .join('&amp;')
37
+ .split('<')
38
+ .join('&lt;')
39
+ .split('>')
40
+ .join('&gt;')
41
+ .split('"')
42
+ .join('&quot;');
43
+ }
44
+ export function withInlineCode(text) {
45
+ return escapeHtml(text).replace(/`([^`]+)`/g, '<code class="reticle-tour-code">$1</code>');
46
+ }
47
+ export function nextIndex(current, delta) {
48
+ const wanted = current + delta;
49
+ if (wanted < 0)
50
+ return 0;
51
+ if (wanted > SLIDE_COUNT - 1)
52
+ return SLIDE_COUNT - 1;
53
+ return wanted;
54
+ }
55
+ export const isLastSlide = (index) => index === SLIDE_COUNT - 1;
56
+ export function holeRects(box, viewWidth, viewHeight) {
57
+ const left = Math.max(0, box.left);
58
+ const top = Math.max(0, box.top);
59
+ const right = Math.min(viewWidth, box.left + box.width);
60
+ const bottom = Math.min(viewHeight, box.top + box.height);
61
+ const candidates = [
62
+ { left: 0, top: 0, width: viewWidth, height: top },
63
+ { left: 0, top: bottom, width: viewWidth, height: viewHeight - bottom },
64
+ { left: 0, top, width: left, height: bottom - top },
65
+ { left: right, top, width: viewWidth - right, height: bottom - top },
66
+ ];
67
+ return candidates.filter((r) => r.width > 0 && r.height > 0);
68
+ }
69
+ export function slideHtml(slide) {
70
+ const dots = tourSlides()
71
+ .map((s) => `<span class="reticle-tour-dot${s.index === slide.index ? ' is-on' : ''}" aria-hidden="true"></span>`)
72
+ .join('');
73
+ const tryIt = undefined === slide.tryIt
74
+ ? ''
75
+ : `<p class="reticle-tour-try" ${TOUR_TARGET_ATTR}="try">${escapeHtml(slide.tryIt)}</p>`;
76
+ const prompt = undefined === slide.prompts
77
+ ? ''
78
+ : `<div class="reticle-tour-prompts">` +
79
+ slide.prompts
80
+ .map((p, i) => `<div class="reticle-tour-prompt" ${TOUR_PROMPT_ATTR}="${String(i)}">` +
81
+ `<span class="reticle-tour-prompt-label">${escapeHtml(p.label)}</span>` +
82
+ `<pre class="reticle-tour-prompt-text">${escapeHtml(p.text)}</pre>` +
83
+ `<button type="button" class="reticle-tour-copy" ${TOUR_TARGET_ATTR}="copy" ` +
84
+ `${TOUR_PROMPT_ATTR}="${String(i)}">${COPY_LABEL}</button></div>`)
85
+ .join('') +
86
+ `</div>`;
87
+ const back = 0 === slide.index
88
+ ? ''
89
+ : `<button type="button" class="reticle-tour-back" ${TOUR_TARGET_ATTR}="back">Back</button>`;
90
+ const forward = isLastSlide(slide.index)
91
+ ? `<button type="button" class="reticle-tour-next" ${TOUR_TARGET_ATTR}="done">Done</button>`
92
+ : `<button type="button" class="reticle-tour-next" ${TOUR_TARGET_ATTR}="next">Next</button>`;
93
+ return (`<div class="reticle-tour-card" role="dialog" aria-modal="false" aria-label="Reticle tour">` +
94
+ `<button type="button" class="reticle-tour-skip" ${TOUR_TARGET_ATTR}="skip" aria-label="Skip the tour">Skip</button>` +
95
+ `<p class="reticle-tour-step">Step ${String(slide.index + 1)} of ${String(SLIDE_COUNT)}</p>` +
96
+ `<h2 class="reticle-tour-title">${escapeHtml(slide.title)}</h2>` +
97
+ `<p class="reticle-tour-body">${withInlineCode(slide.body)}</p>` +
98
+ tryIt +
99
+ prompt +
100
+ `<div class="reticle-tour-foot"><div class="reticle-tour-dots">${dots}</div>` +
101
+ `<div class="reticle-tour-nav">${back}${forward}</div></div>` +
102
+ `</div>`);
103
+ }
104
+ export const TOUR_CSS = `
105
+ [${TOUR_ATTR}]{position:fixed;inset:0;z-index:${String(Z_TOUR)};pointer-events:none;
106
+ font:13px/1.55 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
107
+ [${TOUR_ATTR}] .reticle-tour-scrim{position:absolute;inset:0;background:rgba(6,8,14,.55);
108
+ backdrop-filter:blur(1.5px);pointer-events:auto;z-index:0;}
109
+ /* Blocks the click everywhere the hole is not. Same job as the scrim, minus the one rectangle the
110
+ slide is inviting somebody to press. */
111
+ [${TOUR_ATTR}] .reticle-tour-blocker{position:absolute;pointer-events:auto;z-index:0;}
112
+ /* Ringed slides dim through the ring's own 9999px shadow, which leaves a hole over the thing being
113
+ pointed at. The scrim would cover that hole -- dimming the HUD the slide is ABOUT, and washing
114
+ the page twice (~80% rather than 55%). Cleared, not removed: it is also what swallows clicks
115
+ meant for the app underneath, and a tour that lets you click through is not a tour. */
116
+ [${TOUR_ATTR}] .reticle-tour-scrim.is-clear{background:transparent;backdrop-filter:none;}
117
+ /* Above the ring, and the z-index is the whole point.
118
+ The ring is appended after the card, and both are positioned with an auto z-index, so the ring
119
+ painted on top of it -- and the ring's dimming IS a 9999px shadow, which therefore fell across the
120
+ card as well. On every ringed slide the card was washed at 55% and read as sitting BEHIND the page
121
+ it was drawn over. The ring must stay above the scrim and below the card; saying so explicitly is
122
+ the fix, because DOM order alone cannot express "between these two". */
123
+ [${TOUR_ATTR}] .reticle-tour-card{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
124
+ width:min(430px,calc(100vw - 32px));box-sizing:border-box;pointer-events:auto;z-index:3;
125
+ background:#14161d;color:#e8eaf0;border:1px solid #2b303d;border-radius:14px;padding:20px 20px 16px;
126
+ box-shadow:0 24px 60px rgba(0,0,0,.5);}
127
+ [${TOUR_ATTR}] .reticle-tour-skip{position:absolute;top:10px;right:12px;background:none;border:0;
128
+ color:#8a93a6;font:inherit;font-size:12px;cursor:pointer;padding:4px 6px;border-radius:6px;}
129
+ [${TOUR_ATTR}] .reticle-tour-skip:hover{color:#e8eaf0;background:rgba(255,255,255,.06);}
130
+ [${TOUR_ATTR}] .reticle-tour-step{margin:0 0 6px;font-size:11px;letter-spacing:.06em;
131
+ text-transform:uppercase;color:#7f8798;}
132
+ [${TOUR_ATTR}] .reticle-tour-title{margin:0 0 8px;font-size:18px;line-height:1.25;font-weight:650;
133
+ color:#f2f4f8;}
134
+ [${TOUR_ATTR}] .reticle-tour-body{margin:0 0 10px;color:#c9cedb;}
135
+ /* A call named inside a sentence. Sets its own colour for the reason every other text class does:
136
+ an inherited one loses to any direct element selector in the host app. */
137
+ [${TOUR_ATTR}] .reticle-tour-code{padding:1px 5px;border-radius:5px;background:#0c0e14;
138
+ border:1px solid #232834;color:#9fb6ff;
139
+ font:11.5px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;overflow-wrap:anywhere;}
140
+ [${TOUR_ATTR}] .reticle-tour-try{margin:0 0 12px;padding:8px 10px;border-radius:8px;
141
+ background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.32);color:#c7cbff;
142
+ font-size:12.5px;font-weight:550;}
143
+ [${TOUR_ATTR}] .reticle-tour-prompts{display:flex;flex-direction:column;gap:8px;margin:0 0 12px;}
144
+ [${TOUR_ATTR}] .reticle-tour-prompt{display:grid;gap:6px;padding:9px 10px;border-radius:8px;
145
+ background:#0c0e14;border:1px solid #232834;
146
+ grid-template-columns:1fr auto;grid-template-areas:"label copy" "text text";align-items:center;}
147
+ [${TOUR_ATTR}] .reticle-tour-prompt-label{grid-area:label;color:#8a93a6;font-size:11px;
148
+ letter-spacing:.05em;text-transform:uppercase;}
149
+ [${TOUR_ATTR}] .reticle-tour-prompt-text{grid-area:text;width:100%;box-sizing:border-box;margin:0;
150
+ white-space:pre-wrap;overflow-wrap:anywhere;color:#c9cedb;
151
+ font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;}
152
+ [${TOUR_ATTR}] .reticle-tour-copy{grid-area:copy;padding:5px 10px;border-radius:6px;
153
+ border:1px solid #2b303d;background:transparent;color:#c9cedb;font:inherit;font-size:11.5px;
154
+ font-weight:600;cursor:pointer;white-space:nowrap;}
155
+ [${TOUR_ATTR}] .reticle-tour-copy:hover{background:rgba(255,255,255,.07);color:#fff;}
156
+ /* The flash that tells somebody the click did something. Colour AND text: colour alone is not an
157
+ answer for the people who cannot see the difference. */
158
+ [${TOUR_ATTR}] .reticle-tour-copy.is-done{background:#16a34a;border-color:#16a34a;color:#fff;}
159
+ [${TOUR_ATTR}] .reticle-tour-copy.is-manual{background:#b45309;border-color:#b45309;color:#fff;}
160
+ [${TOUR_ATTR}] .reticle-tour-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;}
161
+ [${TOUR_ATTR}] .reticle-tour-dots{display:flex;gap:6px;}
162
+ [${TOUR_ATTR}] .reticle-tour-dot{width:6px;height:6px;border-radius:50%;background:#333a49;}
163
+ [${TOUR_ATTR}] .reticle-tour-dot.is-on{background:#6366f1;}
164
+ [${TOUR_ATTR}] .reticle-tour-nav{display:flex;gap:8px;}
165
+ [${TOUR_ATTR}] .reticle-tour-back,[${TOUR_ATTR}] .reticle-tour-next{padding:8px 14px;border-radius:8px;
166
+ font:inherit;font-weight:600;cursor:pointer;border:1px solid #2b303d;background:transparent;color:#c9cedb;}
167
+ [${TOUR_ATTR}] .reticle-tour-next{background:#6366f1;border-color:#6366f1;color:#fff;}
168
+ [${TOUR_ATTR}] .reticle-tour-next:hover{filter:brightness(1.08);}
169
+ [${TOUR_ATTR}] .reticle-tour-back:hover{background:rgba(255,255,255,.06);color:#e8eaf0;}
170
+ [${TOUR_ATTR}] .reticle-tour-ring{position:absolute;border-radius:14px;pointer-events:none;z-index:1;
171
+ box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);transition:all .25s ease;}
172
+ /* A ring somebody is being asked to press, rather than to look at: it pulses, so the invitation in
173
+ the prose has something on the page agreeing with it. */
174
+ [${TOUR_ATTR}] .reticle-tour-ring.is-live{animation:reticle-tour-pulse 1.8s ease-in-out infinite;}
175
+ @keyframes reticle-tour-pulse{
176
+ 0%,100%{box-shadow:0 0 0 3px #6366f1,0 0 0 9999px rgba(6,8,14,.55);}
177
+ 50%{box-shadow:0 0 0 6px rgba(99,102,241,.55),0 0 0 9999px rgba(6,8,14,.55);}
178
+ }
179
+ /* A REGION, not a target. The spotlight above cuts a hole in the dimming, which is right for
180
+ something small and wrong for the app: the hole becomes the whole viewport, the dimming
181
+ disappears, and the card is left competing with a fully lit page while the ring edges sit off at
182
+ the margins pointing at nothing. An outline says "all of this" without turning the lights on. */
183
+ [${TOUR_ATTR}] .reticle-tour-ring.is-region{box-shadow:none;border:2px dashed #7c83f5;
184
+ background:rgba(99,102,241,.06);}
185
+ @media (prefers-reduced-motion:reduce){
186
+ [${TOUR_ATTR}] .reticle-tour-ring{transition:none;}
187
+ }
188
+ `;