@reticlehq/browser 2.8.0 → 2.9.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 (79) hide show
  1. package/NOTICE +7 -0
  2. package/dist/actions/actions-dom.js +23 -15
  3. package/dist/actions/actions.d.ts +17 -0
  4. package/dist/actions/actions.js +132 -29
  5. package/dist/actions/synthetic-input.d.ts +9 -0
  6. package/dist/actions/synthetic-input.js +40 -0
  7. package/dist/commands/commands.js +11 -1
  8. package/dist/connect-options.d.ts +9 -9
  9. package/dist/dom/a11y.d.ts +9 -0
  10. package/dist/dom/a11y.js +19 -0
  11. package/dist/dom/dom-ignore.d.ts +19 -6
  12. package/dist/dom/dom-ignore.js +20 -13
  13. package/dist/dom/query.d.ts +2 -2
  14. package/dist/dom/query.js +85 -45
  15. package/dist/dom/snapshot.d.ts +5 -0
  16. package/dist/dom/snapshot.js +34 -0
  17. package/dist/dom/source.d.ts +14 -0
  18. package/dist/dom/source.js +16 -0
  19. package/dist/observers/install-all.js +4 -1
  20. package/dist/observers/ipc.d.ts +16 -0
  21. package/dist/observers/ipc.js +19 -1
  22. package/dist/observers/network.d.ts +8 -0
  23. package/dist/observers/network.js +24 -3
  24. package/dist/observers/state.js +21 -2
  25. package/dist/presenter/presenter-brand.d.ts +24 -7
  26. package/dist/presenter/presenter-brand.js +23 -6
  27. package/dist/presenter/presenter-config.d.ts +102 -9
  28. package/dist/presenter/presenter-config.js +94 -8
  29. package/dist/presenter/presenter-controls.d.ts +10 -10
  30. package/dist/presenter/presenter-controls.js +175 -85
  31. package/dist/presenter/presenter-dock-layout.d.ts +6 -0
  32. package/dist/presenter/presenter-dock-layout.js +170 -0
  33. package/dist/presenter/presenter-drag.d.ts +48 -0
  34. package/dist/presenter/presenter-drag.js +198 -0
  35. package/dist/presenter/presenter-effects.d.ts +1 -1
  36. package/dist/presenter/presenter-effects.js +1 -1
  37. package/dist/presenter/presenter-glow.d.ts +5 -5
  38. package/dist/presenter/presenter-glow.js +7 -7
  39. package/dist/presenter/presenter-heroicons-data.d.ts +41 -0
  40. package/dist/presenter/presenter-heroicons-data.js +38 -0
  41. package/dist/presenter/presenter-hud-chrome.d.ts +17 -0
  42. package/dist/presenter/presenter-hud-chrome.js +41 -0
  43. package/dist/presenter/presenter-icons.d.ts +44 -0
  44. package/dist/presenter/presenter-icons.js +100 -0
  45. package/dist/presenter/presenter-log.d.ts +27 -5
  46. package/dist/presenter/presenter-log.js +114 -28
  47. package/dist/presenter/presenter-report-copy.d.ts +83 -0
  48. package/dist/presenter/presenter-report-copy.js +127 -0
  49. package/dist/presenter/presenter-report-styles.d.ts +7 -0
  50. package/dist/presenter/presenter-report-styles.js +102 -0
  51. package/dist/presenter/presenter-report.d.ts +23 -0
  52. package/dist/presenter/presenter-report.js +217 -0
  53. package/dist/presenter/presenter-run-state.d.ts +1 -1
  54. package/dist/presenter/presenter-settings-styles.d.ts +2 -0
  55. package/dist/presenter/presenter-settings-styles.js +153 -0
  56. package/dist/presenter/presenter-settings.d.ts +82 -0
  57. package/dist/presenter/presenter-settings.js +494 -0
  58. package/dist/presenter/presenter-shell-styles.d.ts +1 -0
  59. package/dist/presenter/presenter-shell-styles.js +375 -0
  60. package/dist/presenter/presenter-shell.d.ts +48 -0
  61. package/dist/presenter/presenter-shell.js +408 -0
  62. package/dist/presenter/presenter-styles.d.ts +9 -4
  63. package/dist/presenter/presenter-styles.js +61 -125
  64. package/dist/presenter/presenter-tally.d.ts +8 -11
  65. package/dist/presenter/presenter-tally.js +48 -23
  66. package/dist/presenter/presenter-test-helpers.d.ts +1 -1
  67. package/dist/presenter/presenter-test-helpers.js +1 -1
  68. package/dist/presenter/presenter-verbs.js +2 -2
  69. package/dist/presenter/presenter-workspace.d.ts +15 -0
  70. package/dist/presenter/presenter-workspace.js +180 -0
  71. package/dist/presenter/presenter.d.ts +15 -10
  72. package/dist/presenter/presenter.js +194 -46
  73. package/dist/reticle.d.ts +2 -2
  74. package/dist/reticle.js +23 -19
  75. package/dist/review/annotator-styles.d.ts +6 -0
  76. package/dist/review/annotator-styles.js +81 -0
  77. package/dist/review/annotator.d.ts +29 -17
  78. package/dist/review/annotator.js +477 -146
  79. package/package.json +7 -4
@@ -0,0 +1,217 @@
1
+ import { PresenterIcon, PRESENTER_ICON_SIZE, hiIconHtml } from './presenter-icons.js';
2
+ import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
3
+ import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
4
+ import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './presenter-report-copy.js';
5
+ /**
6
+ * The impact report: what Reticle has actually done for this user.
7
+ *
8
+ * Reads the local record only - the panel is a view over `.reticle/impact.json`, pushed by the
9
+ * daemon, never fetched from us. Two scopes (this project, this machine) behind one toggle.
10
+ */
11
+ const SCOPE_ATTR = 'data-reticle-report-scope';
12
+ const SHARE_X_ATTR = 'data-reticle-share-x';
13
+ const SHARE_IN_ATTR = 'data-reticle-share-in';
14
+ const SHARE_COPY_ATTR = 'data-reticle-share-copy';
15
+ const REFER_ATTR = 'data-reticle-refer';
16
+ const COPIED_FLASH_MS = 1600;
17
+ export function reportPanelHtml() {
18
+ const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
19
+ return `<div ${REPORT_PANEL_ATTR} class="reticle-report ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle impact" aria-hidden="true">
20
+ <div class="reticle-report-inner">
21
+ <div class="reticle-report-head">
22
+ <span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
23
+ <button type="button" ${SCOPE_ATTR} class="reticle-report-scope" aria-pressed="false">${REPORT_TEXT.PROJECT}</button>
24
+ <button type="button" ${REPORT_CLOSE_ATTR} class="reticle-report-close" title="Close" aria-label="Close impact">${close}</button>
25
+ </div>
26
+ <div class="reticle-report-body" data-reticle-report-body></div>
27
+ <div class="reticle-report-foot">
28
+ <button type="button" ${SHARE_X_ATTR} class="reticle-report-share">Post on X</button>
29
+ <button type="button" ${SHARE_IN_ATTR} class="reticle-report-share">LinkedIn</button>
30
+ <button type="button" ${SHARE_COPY_ATTR} class="reticle-report-share">${REPORT_TEXT.COPY}</button>
31
+ <button type="button" ${REFER_ATTR} class="reticle-report-share reticle-report-refer">${REPORT_TEXT.REFER}</button>
32
+ </div>
33
+ <div class="reticle-report-links">
34
+ <a href="${REPORT_LINKS.DOCS}" target="_blank" rel="noreferrer noopener">Docs</a>
35
+ <a href="${REPORT_LINKS.GITHUB}" target="_blank" rel="noreferrer noopener">GitHub</a>
36
+ <a href="${REPORT_LINKS.SITE}" target="_blank" rel="noreferrer noopener">reticle.sh</a>
37
+ <a href="${REPORT_LINKS.DISCORD}" target="_blank" rel="noreferrer noopener">Discord</a>
38
+ </div>
39
+ </div>
40
+ </div>`;
41
+ }
42
+ /** A stat card: the number, what it is, and - for an estimate - what it is measured against. */
43
+ function card(value, label, basis) {
44
+ const tag = basis === undefined
45
+ ? ''
46
+ : `<span class="reticle-report-basis" title="${basis}">${REPORT_TEXT.ESTIMATE_TAG}</span>`;
47
+ return `<div class="reticle-report-card"><span class="reticle-report-value">${value}</span><span class="reticle-report-label">${label}${tag}</span></div>`;
48
+ }
49
+ /**
50
+ * A 30-day bar chart of verdicts, as inline spans.
51
+ *
52
+ * Bars rather than a line: a day with no verdicts is a real fact about the week, and a line chart
53
+ * interpolates straight through it.
54
+ */
55
+ function chart(scope) {
56
+ const days = scope.days.slice(-30);
57
+ if (0 === days.length)
58
+ return '';
59
+ const peak = Math.max(1, ...days.map((d) => d.counts.verdicts));
60
+ const bars = days
61
+ .map((d) => {
62
+ const pct = Math.max(4, Math.round((d.counts.verdicts / peak) * 100));
63
+ const title = `${d.date}: ${String(d.counts.verdicts)} verdicts, ${String(d.counts.failed)} defects`;
64
+ const hot = d.counts.failed > 0 ? ' data-hot="1"' : '';
65
+ return `<span class="reticle-report-bar" style="height:${String(pct)}%" title="${title}"${hot}></span>`;
66
+ })
67
+ .join('');
68
+ return `<div class="reticle-report-chart-wrap"><span class="reticle-report-section">${REPORT_TEXT.CHART}</span><div class="reticle-report-chart">${bars}</div></div>`;
69
+ }
70
+ export function reportBodyHtml(scope) {
71
+ const c = scope.counts;
72
+ if (0 === c.calls)
73
+ return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
74
+ const streak = scope.records.streakDays > 0
75
+ ? `<span class="reticle-report-streak" title="Consecutive days with at least one verdict">🔥 ${String(scope.records.streakDays)} <span class="reticle-report-streak-label">${REPORT_TEXT.STREAK}</span></span>`
76
+ : '';
77
+ const hero = `<div class="reticle-report-hero"><span class="reticle-report-hero-value">${compactNumber(c.failed)}</span><span class="reticle-report-hero-label">${REPORT_TEXT.HERO_DEFECTS}</span></div>`;
78
+ const verdicts = `<div class="reticle-report-verdicts" title="${REPORT_TEXT.UNKNOWN_HELP}">
79
+ <span class="reticle-report-verdict" data-kind="pass">${compactNumber(c.passed)} ${REPORT_TEXT.PASSED}</span>
80
+ <span class="reticle-report-verdict" data-kind="fail">${compactNumber(c.failed)} ${REPORT_TEXT.FAILED}</span>
81
+ <span class="reticle-report-verdict" data-kind="unknown">${compactNumber(c.unknown)} ${REPORT_TEXT.UNKNOWN}</span>
82
+ </div>`;
83
+ const cards = [
84
+ card(compactNumber(c.verdicts), REPORT_TEXT.VERDICTS),
85
+ card(compactNumber(c.calls), REPORT_TEXT.CALLS),
86
+ card(compactDuration(c.drivingMs), REPORT_TEXT.DRIVING),
87
+ card(compactNumber(c.sessions), REPORT_TEXT.SESSIONS),
88
+ card(compactDuration(scope.records.longestRunMs), REPORT_TEXT.LONGEST),
89
+ card(compactNumber(c.tokensReturned), REPORT_TEXT.TOKENS),
90
+ card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
91
+ card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
92
+ ].join('');
93
+ return `${streak}${hero}${verdicts}<div class="reticle-report-grid">${cards}</div>${chart(scope)}`;
94
+ }
95
+ /** The report panel controller: scope toggle, live repaint, share + referral actions. */
96
+ export class PresenterReport {
97
+ #panel;
98
+ #body;
99
+ #root;
100
+ #snapshot;
101
+ #global = false;
102
+ #host;
103
+ constructor(host = {}) {
104
+ this.#host = host;
105
+ }
106
+ mount(root) {
107
+ this.#root = root;
108
+ this.#panel = root.querySelector(`[${REPORT_PANEL_ATTR}]`) ?? undefined;
109
+ this.#body = root.querySelector('[data-reticle-report-body]') ?? undefined;
110
+ root.querySelector(`[${REPORT_CLOSE_ATTR}]`)?.addEventListener('click', (e) => {
111
+ e.stopPropagation();
112
+ this.close();
113
+ });
114
+ const scopeBtn = root.querySelector(`[${SCOPE_ATTR}]`);
115
+ scopeBtn?.addEventListener('click', (e) => {
116
+ e.stopPropagation();
117
+ this.#global = !this.#global;
118
+ scopeBtn.setAttribute('aria-pressed', this.#global ? 'true' : 'false');
119
+ scopeBtn.textContent = this.#global ? REPORT_TEXT.GLOBAL : REPORT_TEXT.PROJECT;
120
+ this.#paint();
121
+ });
122
+ root.querySelector(`[${SHARE_X_ATTR}]`)?.addEventListener('click', (e) => {
123
+ e.stopPropagation();
124
+ this.#openShare(buildXShareUrl(this.shareText()));
125
+ });
126
+ root.querySelector(`[${SHARE_IN_ATTR}]`)?.addEventListener('click', (e) => {
127
+ e.stopPropagation();
128
+ // LinkedIn ignores every text parameter, so the caption goes to the clipboard first and the
129
+ // composer opens on the link - the only route that needs no posting permission.
130
+ void this.#copy(this.shareText());
131
+ this.#openShare(buildLinkedInShareUrl());
132
+ });
133
+ root.querySelector(`[${SHARE_COPY_ATTR}]`)?.addEventListener('click', (e) => {
134
+ e.stopPropagation();
135
+ void this.#copy(this.shareText(), e.currentTarget);
136
+ });
137
+ root.querySelector(`[${REFER_ATTR}]`)?.addEventListener('click', (e) => {
138
+ e.stopPropagation();
139
+ void this.#copy(buildReferralText(), e.currentTarget);
140
+ });
141
+ }
142
+ /** The record arrived from the daemon. Repaint only if the panel is on screen. */
143
+ setSnapshot(snapshot) {
144
+ this.#snapshot = snapshot;
145
+ if (this.isOpen())
146
+ this.#paint();
147
+ }
148
+ snapshot() {
149
+ return this.#snapshot;
150
+ }
151
+ isOpen() {
152
+ return '1' === this.#root?.getAttribute(REPORT_ATTR);
153
+ }
154
+ open() {
155
+ if (this.#root === undefined)
156
+ return;
157
+ this.#host.onBeforeOpen?.();
158
+ this.#paint();
159
+ this.#root.setAttribute(REPORT_ATTR, '1');
160
+ this.#panel?.setAttribute('aria-hidden', 'false');
161
+ }
162
+ close() {
163
+ this.#root?.setAttribute(REPORT_ATTR, '0');
164
+ this.#panel?.setAttribute('aria-hidden', 'true');
165
+ }
166
+ toggle() {
167
+ if (this.isOpen())
168
+ this.close();
169
+ else
170
+ this.open();
171
+ }
172
+ contains(node) {
173
+ return true === this.#panel?.contains(node);
174
+ }
175
+ /** The post text for whichever scope is showing. */
176
+ shareText() {
177
+ const snap = this.#snapshot;
178
+ if (snap === undefined)
179
+ return buildReferralText();
180
+ return this.#global
181
+ ? buildShareText(snap.global)
182
+ : buildShareText(snap.project, snap.projectName);
183
+ }
184
+ #scope() {
185
+ const snap = this.#snapshot;
186
+ if (snap === undefined)
187
+ return undefined;
188
+ return this.#global ? snap.global : snap.project;
189
+ }
190
+ #paint() {
191
+ const scope = this.#scope();
192
+ if (this.#body === undefined)
193
+ return;
194
+ this.#body.innerHTML =
195
+ scope === undefined
196
+ ? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
197
+ : reportBodyHtml(scope);
198
+ }
199
+ #openShare(url) {
200
+ window.open(url, '_blank', 'noopener,noreferrer');
201
+ }
202
+ async #copy(text, button) {
203
+ try {
204
+ await navigator.clipboard?.writeText(text);
205
+ }
206
+ catch {
207
+ // A blocked clipboard is not worth an error dialog over; the text is on screen either way.
208
+ }
209
+ if (button instanceof HTMLElement) {
210
+ const original = button.textContent ?? '';
211
+ button.textContent = REPORT_TEXT.COPIED;
212
+ window.setTimeout(() => {
213
+ button.textContent = original;
214
+ }, COPIED_FLASH_MS);
215
+ }
216
+ }
217
+ }
@@ -24,7 +24,7 @@ export interface PresenterRunState {
24
24
  result?: LogResult;
25
25
  }[];
26
26
  }
27
- /** Everything the controller hands the builder — the live fields a run state is computed from. */
27
+ /** Everything the controller hands the builder - the live fields a run state is computed from. */
28
28
  interface RunStateInput {
29
29
  sessionId: string;
30
30
  state: SessionState;
@@ -0,0 +1,2 @@
1
+ /** Compact settings card - dock-anchored above the HUD pill. */
2
+ export declare const SETTINGS_CSS: string;
@@ -0,0 +1,153 @@
1
+ import { SETTINGS_PANEL_ATTR, SETTINGS_ATTR, DOCK_ATTR, DOCK_ALIGN_ATTR, SETTINGS_PLACEMENT_ATTR, } from './presenter-config.js';
2
+ import { HUD_DROP_SHADOW, HUD_SURFACE_CLASS, HUD_SURFACE_PAINT } from './presenter-hud-chrome.js';
3
+ /** Compact settings card - dock-anchored above the HUD pill. */
4
+ export const SETTINGS_CSS = `
5
+ [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-chat-panel'}]{
6
+ display:none !important;visibility:hidden !important;pointer-events:none !important;}
7
+ [${SETTINGS_PANEL_ATTR}]{
8
+ position:absolute;right:0;left:auto;top:auto;bottom:calc(100% + 12px);z-index:30;
9
+ box-sizing:border-box;display:flex;flex-direction:column;width:272px;min-height:0;
10
+ max-width:min(272px,calc(100vw - 16px));
11
+ max-height:min(var(--reticle-settings-max-h,78vh),calc(100vh - 96px));
12
+ padding:0;border:none;border-radius:16px;overflow:hidden;
13
+ box-shadow:${HUD_DROP_SHADOW};
14
+ color:var(--reticle-fg);font-size:13px;line-height:1.35;
15
+ opacity:0;pointer-events:none;visibility:hidden;touch-action:manipulation;
16
+ transform:translate3d(0,6px,0) scale(.98);
17
+ transition:opacity .14s ease,transform .18s cubic-bezier(.22,1,.36,1),visibility .14s;
18
+ contain:none;isolation:isolate;${HUD_SURFACE_PAINT}}
19
+ [${DOCK_ATTR}][${SETTINGS_PLACEMENT_ATTR}="below"] [${SETTINGS_PANEL_ATTR}]{
20
+ bottom:auto;top:calc(100% + 12px);}
21
+ [${DOCK_ATTR}][${DOCK_ALIGN_ATTR}="start"] [${SETTINGS_PANEL_ATTR}]{
22
+ right:auto;left:0;}
23
+ [${DOCK_ATTR}][${SETTINGS_PLACEMENT_ATTR}="below"] [${SETTINGS_PANEL_ATTR}].${HUD_SURFACE_CLASS}::after{
24
+ top:auto;bottom:100%;margin-top:0;margin-bottom:-4px;box-shadow:-1px -1px 0 0 rgba(255,255,255,.08);}
25
+ [${DOCK_ATTR}][${DOCK_ALIGN_ATTR}="start"] [${SETTINGS_PANEL_ATTR}].${HUD_SURFACE_CLASS}::after{
26
+ right:auto;left:14px;}
27
+ [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${SETTINGS_PANEL_ATTR}]{
28
+ opacity:1;pointer-events:auto;visibility:visible;transform:translate3d(0,0,0) scale(1);}
29
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-inner{
30
+ position:relative;z-index:2;display:grid;grid-template-rows:auto minmax(0,1fr) auto;
31
+ flex:1 1 auto;min-height:0;max-height:100%;overflow:hidden;}
32
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-head{
33
+ display:flex;flex-shrink:0;align-items:center;justify-content:space-between;gap:8px;
34
+ padding:12px 14px 8px;border-bottom:1px solid rgba(255,255,255,.06);}
35
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-title{
36
+ font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:rgba(255,255,255,.45);}
37
+ [${SETTINGS_PANEL_ATTR}].${HUD_SURFACE_CLASS}::after{
38
+ content:"";position:absolute;top:100%;right:14px;width:8px;height:8px;margin-top:-4px;
39
+ background:#000;transform:rotate(45deg);box-shadow:1px 1px 0 0 rgba(255,255,255,.08);z-index:0;pointer-events:none;}
40
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-close{
41
+ position:relative;top:auto;right:auto;z-index:1;
42
+ display:inline-flex;align-items:center;justify-content:center;
43
+ width:24px;height:24px;padding:0;border:none;border-radius:999px;cursor:pointer;
44
+ background:rgba(255,255,255,.06);color:var(--reticle-muted);line-height:0;
45
+ transition:background .15s,color .15s,transform .1s;}
46
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-close:hover{background:rgba(255,255,255,.12);color:var(--reticle-fg);}
47
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-close:active{transform:scale(.94);}
48
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-close:focus-visible{outline:2px solid rgba(59,130,246,.65);outline-offset:1px;}
49
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-body{
50
+ min-height:0;padding:6px 0 4px;overflow-x:hidden;overflow-y:auto;
51
+ overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
52
+ scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;
53
+ background:linear-gradient(180deg,#0a0a0e 0%,#000 100%);}
54
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-body::-webkit-scrollbar{width:8px;}
55
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-body::-webkit-scrollbar-thumb{
56
+ background:rgba(255,255,255,.14);border-radius:8px;border:2px solid transparent;background-clip:content-box;}
57
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-row{
58
+ display:flex;align-items:center;justify-content:space-between;gap:12px;
59
+ padding:8px 14px;min-height:30px;}
60
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-label{
61
+ display:inline-flex;align-items:center;gap:6px;color:rgba(255,255,255,.62);font-size:13px;font-weight:500;}
62
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-help{
63
+ display:inline-flex;align-items:center;justify-content:center;
64
+ width:14px;height:14px;border:none;border-radius:999px;padding:0;cursor:help;
65
+ background:transparent;color:rgba(255,255,255,.35);line-height:0;flex:none;}
66
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-help:hover{color:rgba(255,255,255,.75);}
67
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-help:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:1px;}
68
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-cycle{
69
+ display:inline-flex;align-items:center;gap:8px;border:none;border-radius:8px;cursor:pointer;
70
+ background:rgba(255,255,255,.08);color:#fff;font:inherit;font-size:12px;font-weight:600;
71
+ padding:5px 8px 5px 10px;transition:background .15s,transform .1s;}
72
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-cycle:hover{background:rgba(255,255,255,.12);}
73
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-cycle:active{transform:scale(.97);}
74
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-cycle:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:1px;}
75
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-dots{display:inline-flex;flex-direction:column;gap:2px;}
76
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-dot{
77
+ width:3px;height:3px;border-radius:999px;background:rgba(255,255,255,.22);}
78
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-dot[data-on="1"]{background:var(--reticle-accent);}
79
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle{
80
+ position:relative;width:28px;height:18px;border:none;border-radius:999px;cursor:pointer;padding:0;flex:none;
81
+ background:rgba(255,255,255,.16);transition:background .15s ease,box-shadow .15s ease;}
82
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle[data-on="1"]{background:var(--reticle-accent);}
83
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle::after{
84
+ content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
85
+ background:#fafafa;box-shadow:0 1px 2px rgba(0,0,0,.35);transition:transform .15s ease;}
86
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle[data-on="1"]::after{transform:translateX(10px);}
87
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-toggle:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:2px;}
88
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-row[data-disabled="1"]{opacity:.45;pointer-events:none;}
89
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-section{
90
+ padding:10px 14px 6px;color:rgba(255,255,255,.38);font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;}
91
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatches{
92
+ display:flex;gap:8px;flex-wrap:wrap;padding:0 14px 10px;}
93
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch{
94
+ width:24px;height:24px;border-radius:999px;padding:0;cursor:pointer;
95
+ border:2px solid transparent;background-clip:padding-box;
96
+ box-shadow:inset 0 0 0 1px rgba(0,0,0,.25);transition:transform .12s,border-color .12s;}
97
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch:hover{transform:scale(1.06);}
98
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch[data-on="1"]{
99
+ border-color:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.35);}
100
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-swatch:focus-visible{outline:2px solid rgba(59,130,246,.65);outline-offset:2px;}
101
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-checkrow{
102
+ display:flex;align-items:center;gap:10px;padding:7px 14px;color:rgba(255,255,255,.8);font-size:13px;
103
+ cursor:pointer;user-select:none;margin:0;transition:background .12s;}
104
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-checkrow:hover{background:rgba(255,255,255,.03);}
105
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-check{
106
+ width:16px;height:16px;border-radius:5px;border:1px solid rgba(255,255,255,.22);
107
+ background:rgba(255,255,255,.04);display:inline-flex;align-items:center;justify-content:center;
108
+ color:transparent;font-size:10px;line-height:1;flex:none;transition:background .12s,border-color .12s,color .12s;}
109
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-check[data-on="1"]{
110
+ background:#fafafa;border-color:#fafafa;color:#111;}
111
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-check:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:2px;}
112
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-foot{
113
+ flex-shrink:0;margin-top:2px;border-top:1px solid rgba(255,255,255,.08);
114
+ display:flex;flex-direction:column;gap:0;background:#000;}
115
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-reset{
116
+ width:100%;display:flex;align-items:center;justify-content:center;gap:6px;
117
+ border:none;background:rgba(255,255,255,.04);color:rgba(255,255,255,.78);font:inherit;font-size:12px;font-weight:600;
118
+ padding:10px 14px;cursor:pointer;text-align:center;transition:background .12s,color .12s;}
119
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-reset:hover{background:rgba(255,255,255,.08);color:#fff;}
120
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-reset:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:-2px;}
121
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-link{
122
+ width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;
123
+ border:none;background:transparent;color:rgba(255,255,255,.82);font:inherit;font-size:13px;
124
+ padding:11px 14px;cursor:pointer;text-align:left;transition:background .12s,color .12s;}
125
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-link-caret{display:inline-flex;align-items:center;flex:none;opacity:.55;}
126
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:hover{background:rgba(255,255,255,.04);color:#fff;}
127
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-link:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:-2px;}
128
+ [${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-hud'}] .reticle-tb-tip{
129
+ opacity:0;visibility:hidden;transition-delay:0s;}
130
+ /* The dock's accent IS the theme's active colour, published as --reticle-accent on the overlay by
131
+ applyPresenterSettings. It used to be a seventh independent picker with its own attribute. */
132
+ [${'data-reticle-dock'}]{--reticle-accent-soft:color-mix(in srgb,var(--reticle-accent) 18%,transparent);}
133
+ /* One chip per theme: three bands (active, idle, ended) over the theme's name. */
134
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-themes{
135
+ display:grid;grid-template-columns:repeat(auto-fit,minmax(72px,1fr));gap:8px;padding:4px 14px 12px;}
136
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme{
137
+ display:flex;flex-direction:column;align-items:stretch;gap:0;padding:0;overflow:hidden;
138
+ border:1px solid rgba(255,255,255,.12);border-radius:10px;background:rgba(255,255,255,.03);
139
+ cursor:pointer;transition:border-color .15s ease,background .15s ease;}
140
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme:hover{background:rgba(255,255,255,.07);}
141
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme[data-on="1"]{
142
+ border-color:#fff;background:rgba(255,255,255,.1);}
143
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme-band{display:block;height:6px;}
144
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme-band:first-of-type{height:10px;}
145
+ [${SETTINGS_PANEL_ATTR}] .reticle-settings-theme-name{
146
+ display:block;padding:5px 6px 6px;color:rgba(255,255,255,.78);font-size:11px;text-align:center;}
147
+ [${'data-reticle-blocker'}]{
148
+ position:fixed;inset:0;z-index:1;background:rgba(0,0,0,.16);pointer-events:auto;}
149
+ [${'data-reticle-overlay'}][data-reticle-block="0"] [${'data-reticle-blocker'}]{
150
+ display:none;pointer-events:none;}
151
+ [${'data-reticle-overlay'}][data-reticle-hidden="1"] [${DOCK_ATTR}],
152
+ [${'data-reticle-overlay'}][data-reticle-hidden="1"] [${SETTINGS_PANEL_ATTR}]{display:none !important;}
153
+ `;
@@ -0,0 +1,82 @@
1
+ import { SETTINGS_CSS } from './presenter-settings-styles.js';
2
+ export { SETTINGS_CSS };
3
+ /** How much detail lands in copied/exported run state. */
4
+ export declare const OutputDetail: {
5
+ readonly MINIMAL: "minimal";
6
+ readonly STANDARD: "standard";
7
+ readonly VERBOSE: "verbose";
8
+ };
9
+ export type OutputDetail = (typeof OutputDetail)[keyof typeof OutputDetail];
10
+ /**
11
+ * Status themes: ONE choice, three colours.
12
+ *
13
+ * This was four separate pickers - a chrome accent plus a swatch row per state - which is 28
14
+ * decisions to land on a set that works together. A theme is a combination we have already checked
15
+ * reads as a progression (working -> waiting -> done) and against a dark app, so the choice is
16
+ * "which mood", not "which four hex values".
17
+ */
18
+ export declare const StatusThemeId: {
19
+ readonly SIGNAL: "signal";
20
+ readonly TRAFFIC: "traffic";
21
+ readonly MONO: "mono";
22
+ readonly NEON: "neon";
23
+ readonly EMBER: "ember";
24
+ };
25
+ export type StatusThemeId = (typeof StatusThemeId)[keyof typeof StatusThemeId];
26
+ export interface StatusTheme {
27
+ id: StatusThemeId;
28
+ label: string;
29
+ active: string;
30
+ idle: string;
31
+ ended: string;
32
+ }
33
+ export declare const STATUS_THEMES: readonly StatusTheme[];
34
+ export declare function statusTheme(id: StatusThemeId): StatusTheme;
35
+ export interface PresenterSettings {
36
+ outputDetail: OutputDetail;
37
+ /**
38
+ * The colours the HUD signals session state with - the dot, the panel wash, the page glow, the
39
+ * FAB halo and the annotation marks all read from whichever one is current. One choice, because
40
+ * the three have to work as a set and against the user's own app.
41
+ */
42
+ statusThemeId: StatusThemeId;
43
+ /** The page-edge glow. Off leaves every other signal (dot, panel, FAB) in place. */
44
+ ambientGlow: boolean;
45
+ reactComponents: boolean;
46
+ hideUntilRestart: boolean;
47
+ clearOnCopy: boolean;
48
+ blockPageInteractions: boolean;
49
+ showTally: boolean;
50
+ showTimestamps: boolean;
51
+ autoOpenChat: boolean;
52
+ reduceMotion: boolean;
53
+ }
54
+ export declare function getPresenterSettings(): PresenterSettings;
55
+ export declare function loadPresenterSettings(): PresenterSettings;
56
+ /** Settings panel markup - anchored above the gear in the toolbar. */
57
+ export declare function settingsPanelHtml(): string;
58
+ export interface SettingsHost {
59
+ onHideUntilRestart?: () => void;
60
+ onSettingsChange?: (settings: PresenterSettings) => void;
61
+ onBeforeOpen?: () => void;
62
+ }
63
+ /** Apply persisted settings onto the overlay + dock. */
64
+ export declare function applyPresenterSettings(root: HTMLElement, settings: PresenterSettings): void;
65
+ /**
66
+ * Toggle the full-page blocker. Only active while annotate mode is live AND the user opted in -
67
+ * never on a collapsed FAB, so real hover/click on the host app still works.
68
+ */
69
+ export declare function syncPageBlocker(root: HTMLElement, settings: PresenterSettings, annotateLive: boolean): void;
70
+ /** Blocker node - sits under Reticle UI, above the host page. */
71
+ export declare function blockerHtml(): string;
72
+ export declare class PresenterSettingsPanel {
73
+ #private;
74
+ constructor(host?: SettingsHost);
75
+ contains(node: Node): boolean;
76
+ mount(root: HTMLElement): void;
77
+ teardown(): void;
78
+ isOpen(): boolean;
79
+ open(): void;
80
+ close(): void;
81
+ toggle(): void;
82
+ }