@triagly/sdk 0.1.1-beta.4 → 0.1.1-beta.6

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.
package/README.md CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  > JavaScript SDK for Triagly - Turn user feedback into GitHub issues instantly.
4
4
 
5
- Lightweight (<10KB gzipped) browser widget that captures user feedback with screenshots and automatically creates GitHub issues.
5
+ Lightweight (<10KB gzipped) browser widget that captures user feedback and automatically creates issues in your tracker.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - 🎨 **Beautiful UI Widget** - Clean, customizable feedback form
10
- - 📸 **Screenshot Capture** - Automatic page screenshots (with html2canvas)
11
10
  - 🐛 **Console Log Capture** - Automatically captures browser console errors and warnings for debugging
12
11
  - 🤖 **Bot Protection** - Cloudflare Turnstile integration to prevent spam
13
12
  - 🔒 **Secure Authentication** - Uses publishable keys with optional hardened mode
@@ -34,9 +33,6 @@ yarn add @triagly/sdk
34
33
  <!-- Include Cloudflare Turnstile for bot protection -->
35
34
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
36
35
 
37
- <!-- Include html2canvas for screenshot support (optional) -->
38
- <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
39
-
40
36
  <!-- Include Triagly SDK -->
41
37
  <script src="https://unpkg.com/@triagly/sdk/dist/index.min.js"></script>
42
38
  ```
@@ -204,6 +200,135 @@ Remove the widget and clean up event listeners.
204
200
  triagly.destroy();
205
201
  ```
206
202
 
203
+ #### `submitNPS(submission)`
204
+ Submit an NPS (Net Promoter Score) metric. Score must be between 0-10.
205
+
206
+ ```typescript
207
+ const response = await triagly.submitNPS({
208
+ score: 9,
209
+ question: 'How likely are you to recommend us?',
210
+ userIdentifier: 'user@example.com',
211
+ metadata: { source: 'email-campaign' }
212
+ });
213
+ ```
214
+
215
+ #### `submitCSAT(submission)`
216
+ Submit a CSAT (Customer Satisfaction) metric. Score must be between 1-5.
217
+
218
+ ```typescript
219
+ const response = await triagly.submitCSAT({
220
+ score: 4,
221
+ question: 'How satisfied are you with our service?',
222
+ userIdentifier: 'user@example.com'
223
+ });
224
+ ```
225
+
226
+ #### `submitCES(submission)`
227
+ Submit a CES (Customer Effort Score) metric. Score must be between 1-7.
228
+
229
+ ```typescript
230
+ const response = await triagly.submitCES({
231
+ score: 2,
232
+ question: 'How easy was it to resolve your issue?',
233
+ metadata: { support_ticket_id: 'TKT-123' }
234
+ });
235
+ ```
236
+
237
+ #### `getMetricStats(params)`
238
+ Retrieve statistics for a specific metric type with optional date range.
239
+
240
+ ```typescript
241
+ const stats = await triagly.getMetricStats({
242
+ metricType: 'NPS',
243
+ startDate: '2025-01-01',
244
+ endDate: '2025-01-31'
245
+ });
246
+ ```
247
+
248
+ ## Metrics Support
249
+
250
+ The SDK now supports submitting standard metrics like NPS (Net Promoter Score), CSAT (Customer Satisfaction), and CES (Customer Effort Score).
251
+
252
+ ### Submit NPS (Net Promoter Score)
253
+
254
+ ```typescript
255
+ import Triagly from '@triagly/sdk';
256
+
257
+ const triagly = new Triagly({
258
+ publishableKey: 'pub_live_abc123'
259
+ });
260
+
261
+ // Submit NPS (score: 0-10)
262
+ const npsResponse = await triagly.submitNPS({
263
+ score: 9,
264
+ question: 'How likely are you to recommend us?',
265
+ userIdentifier: 'user@example.com',
266
+ metadata: { source: 'email-campaign' }
267
+ });
268
+
269
+ console.log('NPS submitted:', npsResponse.id);
270
+ ```
271
+
272
+ ### Submit CSAT (Customer Satisfaction)
273
+
274
+ ```typescript
275
+ // Submit CSAT (score: 1-5)
276
+ const csatResponse = await triagly.submitCSAT({
277
+ score: 4,
278
+ question: 'How satisfied are you with our service?',
279
+ userIdentifier: 'user@example.com'
280
+ });
281
+
282
+ console.log('CSAT submitted:', csatResponse.id);
283
+ ```
284
+
285
+ ### Submit CES (Customer Effort Score)
286
+
287
+ ```typescript
288
+ // Submit CES (score: 1-7, lower is better)
289
+ const cesResponse = await triagly.submitCES({
290
+ score: 2,
291
+ question: 'How easy was it to resolve your issue?',
292
+ userIdentifier: 'user@example.com',
293
+ metadata: { support_ticket_id: 'TKT-123' }
294
+ });
295
+
296
+ console.log('CES submitted:', cesResponse.id);
297
+ ```
298
+
299
+ ### Get Metric Statistics
300
+
301
+ ```typescript
302
+ // Get NPS statistics for a date range
303
+ const stats = await triagly.getMetricStats({
304
+ metricType: 'NPS',
305
+ startDate: '2025-01-01',
306
+ endDate: '2025-01-31'
307
+ });
308
+
309
+ // Type-safe access based on metric type
310
+ if (stats.metric_type === 'NPS') {
311
+ console.log(`NPS Score: ${stats.score}`);
312
+ console.log(`Promoters: ${stats.promoters_percent}%`);
313
+ console.log(`Passives: ${stats.passives_percent}%`);
314
+ console.log(`Detractors: ${stats.detractors_percent}%`);
315
+ } else if (stats.metric_type === 'CSAT') {
316
+ console.log(`Average Score: ${stats.average_score}`);
317
+ console.log(`Satisfied: ${stats.percent_satisfied}%`);
318
+ } else if (stats.metric_type === 'CES') {
319
+ console.log(`Average Effort Score: ${stats.average_effort_score}`);
320
+ }
321
+ console.log(`Total Responses: ${stats.total_responses}`);
322
+ ```
323
+
324
+ ### Metric Score Ranges
325
+
326
+ - **NPS**: 0-10 (0 = Not at all likely, 10 = Extremely likely)
327
+ - **CSAT**: 1-5 (1 = Very dissatisfied, 5 = Very satisfied)
328
+ - **CES**: 1-7 (1 = Very easy, 7 = Very difficult)
329
+
330
+ The SDK automatically validates score ranges and throws descriptive errors for invalid inputs.
331
+
207
332
  ## Examples
208
333
 
209
334
  ### Custom Button Trigger
@@ -290,25 +415,6 @@ try {
290
415
 
291
416
  See [Styling Guide](./docs/guides/STYLING.md) for all available variables and more examples.
292
417
 
293
- ## Screenshot Support
294
-
295
- To enable screenshot capture, include html2canvas:
296
-
297
- ```html
298
- <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
299
- ```
300
-
301
- Or install via NPM and import in your bundle:
302
-
303
- ```bash
304
- npm install html2canvas
305
- ```
306
-
307
- ```typescript
308
- import html2canvas from 'html2canvas';
309
- (window as any).html2canvas = html2canvas;
310
- ```
311
-
312
418
  ## Console Log Capture
313
419
 
314
420
  The SDK automatically captures browser console messages (errors and warnings by default) to help developers debug issues.
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;CA2E7B"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;CA0E7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAKtD,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,EAAE,aAAa;IAwDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAgE1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxE;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB;AAGD,cAAc,SAAS,CAAC;AAexB,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAKjB,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,EAAE,aAAa;IAwDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAyD1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxE;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB;AAGD,cAAc,SAAS,CAAC;AAexB,eAAe,OAAO,CAAC"}
package/dist/index.esm.js CHANGED
@@ -45,26 +45,23 @@ class FeedbackWidget {
45
45
  // Button orientation
46
46
  const orientation = this.config.orientation || 'horizontal';
47
47
  button.classList.add(`triagly-orientation-${orientation}`);
48
+ // Speech bubble SVG icon
49
+ const speechBubbleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
50
+ const largeIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
48
51
  // Handle button text based on shape
49
- const fullText = this.config.buttonText || '🐛 Feedback';
52
+ const fullText = this.config.buttonText || 'Feedback';
50
53
  if (shape === 'circular') {
51
- button.innerHTML = '🐛';
54
+ button.innerHTML = largeIcon;
52
55
  button.setAttribute('aria-label', fullText);
53
56
  }
54
57
  else if (shape === 'expandable') {
55
- // Expandable starts with emoji, expands to full text on hover
56
- button.innerHTML = '<span class="triagly-btn-icon">🐛</span><span class="triagly-btn-text"> Feedback</span>';
58
+ // Expandable starts with icon, expands to full text on hover
59
+ button.innerHTML = `<span class="triagly-btn-icon">${largeIcon}</span><span class="triagly-btn-text"> ${this.config.buttonText || 'Feedback'}</span>`;
57
60
  button.setAttribute('aria-label', fullText);
58
- // Store custom text if provided
59
- if (this.config.buttonText) {
60
- const textSpan = button.querySelector('.triagly-btn-text');
61
- if (textSpan) {
62
- textSpan.textContent = ' ' + this.config.buttonText.replace('🐛', '').trim();
63
- }
64
- }
65
61
  }
66
62
  else {
67
- button.innerHTML = fullText;
63
+ // Default: icon + text
64
+ button.innerHTML = `${speechBubbleIcon}<span class="triagly-btn-label">${fullText}</span>`;
68
65
  }
69
66
  button.onclick = () => this.toggle();
70
67
  // Position button
@@ -130,9 +127,9 @@ class FeedbackWidget {
130
127
  this.setupKeyboardEvents();
131
128
  // Set up focus trap
132
129
  this.setupFocusTrap();
133
- // Focus on title field
134
- const titleInput = this.container?.querySelector('input[type="text"]');
135
- titleInput?.focus();
130
+ // Focus on description field
131
+ const descInput = this.container?.querySelector('#triagly-description');
132
+ descInput?.focus();
136
133
  }, 0);
137
134
  }
138
135
  /**
@@ -177,7 +174,7 @@ class FeedbackWidget {
177
174
  overlay.className = 'triagly-overlay';
178
175
  overlay.setAttribute('role', 'dialog');
179
176
  overlay.setAttribute('aria-modal', 'true');
180
- overlay.setAttribute('aria-labelledby', 'triagly-modal-title');
177
+ overlay.setAttribute('aria-label', 'Send feedback');
181
178
  overlay.onclick = (e) => {
182
179
  if (e.target === overlay)
183
180
  this.close('overlay');
@@ -188,7 +185,6 @@ class FeedbackWidget {
188
185
  const header = document.createElement('div');
189
186
  header.className = 'triagly-header';
190
187
  header.innerHTML = `
191
- <h3 id="triagly-modal-title">Send Feedback</h3>
192
188
  <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>
193
189
  `;
194
190
  const closeBtn = header.querySelector('.triagly-close');
@@ -197,16 +193,7 @@ class FeedbackWidget {
197
193
  form.className = 'triagly-form';
198
194
  form.innerHTML = `
199
195
  <div class="triagly-field">
200
- <label for="triagly-title">Title (optional)</label>
201
- <input
202
- type="text"
203
- id="triagly-title"
204
- placeholder="Brief summary of your feedback"
205
- />
206
- </div>
207
-
208
- <div class="triagly-field">
209
- <label for="triagly-description">Description *</label>
196
+ <label for="triagly-description">What's on your mind?</label>
210
197
  <textarea
211
198
  id="triagly-description"
212
199
  required
@@ -215,6 +202,15 @@ class FeedbackWidget {
215
202
  ></textarea>
216
203
  </div>
217
204
 
205
+ <div class="triagly-field">
206
+ <label for="triagly-name">Name (optional)</label>
207
+ <input
208
+ type="text"
209
+ id="triagly-name"
210
+ placeholder="Your name"
211
+ />
212
+ </div>
213
+
218
214
  <div class="triagly-field">
219
215
  <label for="triagly-email">Email (optional)</label>
220
216
  <input
@@ -224,13 +220,6 @@ class FeedbackWidget {
224
220
  />
225
221
  </div>
226
222
 
227
- <div class="triagly-field triagly-checkbox">
228
- <label>
229
- <input type="checkbox" id="triagly-screenshot" checked />
230
- <span>Include screenshot</span>
231
- </label>
232
- </div>
233
-
234
223
  ${this.config.turnstileSiteKey ? `
235
224
  <div class="triagly-field triagly-turnstile">
236
225
  <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>
@@ -254,8 +243,16 @@ class FeedbackWidget {
254
243
  e.preventDefault();
255
244
  this.handleSubmit(form);
256
245
  };
246
+ const footer = document.createElement('div');
247
+ footer.className = 'triagly-footer';
248
+ footer.innerHTML = `
249
+ <a href="https://triagly.com" target="_blank" rel="noopener noreferrer" class="triagly-branding">
250
+ Powered by <strong>Triagly</strong>
251
+ </a>
252
+ `;
257
253
  modal.appendChild(header);
258
254
  modal.appendChild(form);
255
+ modal.appendChild(footer);
259
256
  overlay.appendChild(modal);
260
257
  // Render Turnstile widget if available
261
258
  if (this.config.turnstileSiteKey) {
@@ -304,10 +301,9 @@ class FeedbackWidget {
304
301
  * Handle form submission
305
302
  */
306
303
  async handleSubmit(form) {
307
- const titleInput = form.querySelector('#triagly-title');
308
304
  const descInput = form.querySelector('#triagly-description');
305
+ const nameInput = form.querySelector('#triagly-name');
309
306
  const emailInput = form.querySelector('#triagly-email');
310
- const screenshotCheckbox = form.querySelector('#triagly-screenshot');
311
307
  const statusDiv = form.querySelector('#triagly-status');
312
308
  const submitBtn = form.querySelector('button[type="submit"]');
313
309
  const turnstileContainer = form.querySelector('.cf-turnstile');
@@ -321,10 +317,9 @@ class FeedbackWidget {
321
317
  turnstileToken = turnstileContainer.getAttribute('data-turnstile-response') || undefined;
322
318
  }
323
319
  const data = {
324
- title: titleInput.value.trim() || undefined,
325
320
  description: descInput.value.trim(),
321
+ reporterName: nameInput.value.trim() || undefined,
326
322
  reporterEmail: emailInput.value.trim() || undefined,
327
- includeScreenshot: screenshotCheckbox.checked,
328
323
  turnstileToken,
329
324
  };
330
325
  // Create a promise that waits for actual submission result
@@ -388,21 +383,32 @@ class FeedbackWidget {
388
383
  position: fixed;
389
384
  z-index: 999999;
390
385
  padding: 12px 20px;
391
- background: var(--triagly-button-bg, #6366f1);
386
+ background: var(--triagly-button-bg, #18181b);
392
387
  color: var(--triagly-button-text, #ffffff);
393
388
  border: none;
394
389
  border-radius: var(--triagly-button-radius, 8px);
395
390
  font-size: 14px;
396
391
  font-weight: 500;
397
392
  cursor: pointer;
398
- box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(99, 102, 241, 0.3));
393
+ box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));
399
394
  transition: all 0.2s;
395
+ display: inline-flex;
396
+ align-items: center;
397
+ gap: 8px;
400
398
  }
401
399
 
402
400
  .triagly-button:hover {
403
- background: var(--triagly-button-bg-hover, #4f46e5);
401
+ background: var(--triagly-button-bg-hover, #27272a);
404
402
  transform: translateY(-2px);
405
- box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));
403
+ box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
404
+ }
405
+
406
+ .triagly-button .triagly-icon {
407
+ flex-shrink: 0;
408
+ }
409
+
410
+ .triagly-button .triagly-btn-label {
411
+ line-height: 1;
406
412
  }
407
413
 
408
414
  /* Prevent expandable buttons from shifting on hover */
@@ -496,8 +502,8 @@ class FeedbackWidget {
496
502
  min-width: auto;
497
503
  padding: 12px 20px;
498
504
  border-radius: 30px;
499
- background: var(--triagly-button-bg-hover, #4f46e5);
500
- box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));
505
+ background: var(--triagly-button-bg-hover, #27272a);
506
+ box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
501
507
  }
502
508
  .triagly-shape-expandable:hover .triagly-btn-text {
503
509
  width: auto;
@@ -556,18 +562,10 @@ class FeedbackWidget {
556
562
 
557
563
  .triagly-header {
558
564
  display: flex;
559
- justify-content: space-between;
565
+ justify-content: flex-end;
560
566
  align-items: center;
561
- padding: 20px 24px;
567
+ padding: 8px 12px 0;
562
568
  background: var(--triagly-header-bg, #ffffff);
563
- border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);
564
- }
565
-
566
- .triagly-header h3 {
567
- margin: 0;
568
- font-size: 18px;
569
- font-weight: 600;
570
- color: var(--triagly-header-text, #111827);
571
569
  }
572
570
 
573
571
  .triagly-close {
@@ -625,46 +623,21 @@ class FeedbackWidget {
625
623
  .triagly-field input:focus,
626
624
  .triagly-field textarea:focus {
627
625
  outline: none;
628
- border-color: var(--triagly-input-border-focus, #6366f1);
629
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
630
- }
631
-
632
- .triagly-checkbox label {
633
- display: flex;
634
- align-items: center;
635
- gap: 8px;
636
- cursor: pointer;
637
- font-weight: 400;
638
- }
639
-
640
- .triagly-checkbox label span {
641
- user-select: none;
642
- }
643
-
644
- .triagly-checkbox input {
645
- width: 16px;
646
- height: 16px;
647
- margin: 0;
648
- cursor: pointer;
626
+ border-color: var(--triagly-input-border-focus, #a1a1aa);
627
+ box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);
649
628
  }
650
629
 
651
630
  /* Focus visible styles for accessibility */
652
631
  .triagly-button:focus-visible,
653
632
  .triagly-field input:focus-visible,
654
633
  .triagly-field textarea:focus-visible,
655
- .triagly-checkbox input:focus-visible,
656
634
  .triagly-btn-primary:focus-visible,
657
635
  .triagly-btn-secondary:focus-visible,
658
636
  .triagly-close:focus-visible {
659
- outline: 2px solid #6366f1;
637
+ outline: 2px solid #a1a1aa;
660
638
  outline-offset: 2px;
661
639
  }
662
640
 
663
- /* Checkbox label gets visual indicator when checkbox is focused */
664
- .triagly-checkbox input:focus-visible + span {
665
- text-decoration: underline;
666
- }
667
-
668
641
  .triagly-turnstile {
669
642
  display: flex;
670
643
  justify-content: center;
@@ -690,12 +663,12 @@ class FeedbackWidget {
690
663
  }
691
664
 
692
665
  .triagly-btn-primary {
693
- background: var(--triagly-btn-primary-bg, #6366f1);
666
+ background: var(--triagly-btn-primary-bg, #18181b);
694
667
  color: var(--triagly-btn-primary-text, #ffffff);
695
668
  }
696
669
 
697
670
  .triagly-btn-primary:hover:not(:disabled) {
698
- background: var(--triagly-btn-primary-bg-hover, #4f46e5);
671
+ background: var(--triagly-btn-primary-bg-hover, #27272a);
699
672
  }
700
673
 
701
674
  .triagly-btn-primary:disabled {
@@ -731,6 +704,29 @@ class FeedbackWidget {
731
704
  background: var(--triagly-error-bg, #fee2e2);
732
705
  color: var(--triagly-error-text, #991b1b);
733
706
  }
707
+
708
+ .triagly-footer {
709
+ padding: 12px 24px 16px;
710
+ text-align: right;
711
+ border-top: 1px solid var(--triagly-footer-border, #e5e7eb);
712
+ background: var(--triagly-footer-bg, #f9fafb);
713
+ border-radius: 0 0 var(--triagly-modal-radius, 12px) var(--triagly-modal-radius, 12px);
714
+ }
715
+
716
+ .triagly-branding {
717
+ font-size: 12px;
718
+ color: var(--triagly-footer-text, #6b7280);
719
+ text-decoration: none;
720
+ transition: color 0.2s;
721
+ }
722
+
723
+ .triagly-branding:hover {
724
+ color: var(--triagly-footer-text-hover, #18181b);
725
+ }
726
+
727
+ .triagly-branding strong {
728
+ font-weight: 600;
729
+ }
734
730
  `;
735
731
  document.head.appendChild(style);
736
732
  }
@@ -818,7 +814,7 @@ class FeedbackWidget {
818
814
  }
819
815
 
820
816
  // API Client
821
- const DEFAULT_API_URL = 'https://sypkjlwfyvyuqnvzkaxb.supabase.co/functions/v1';
817
+ const DEFAULT_API_URL = 'https://bssghvinezdawvupcyci.supabase.co/functions/v1';
822
818
  class TriaglyAPI {
823
819
  constructor(publishableKey, apiUrl, getToken, turnstileSiteKey) {
824
820
  this.apiUrl = (apiUrl || DEFAULT_API_URL).replace(/\/$/, ''); // Remove trailing slash
@@ -896,7 +892,6 @@ class TriaglyAPI {
896
892
  consoleLogs: data.consoleLogs,
897
893
  },
898
894
  tags: data.tags,
899
- screenshot: data.screenshot,
900
895
  reporterEmail: data.reporterEmail,
901
896
  reporterName: data.reporterName,
902
897
  turnstileToken,
@@ -983,34 +978,6 @@ function detectBrowser() {
983
978
  }
984
979
  return browser;
985
980
  }
986
- /**
987
- * Capture screenshot of current page
988
- */
989
- async function captureScreenshot() {
990
- try {
991
- // Use html2canvas library if available
992
- if (typeof window.html2canvas !== 'undefined') {
993
- const canvas = await window.html2canvas(document.body, {
994
- logging: false,
995
- useCORS: true,
996
- allowTaint: true,
997
- });
998
- return canvas.toDataURL('image/png');
999
- }
1000
- // Fallback to native screenshot API if supported (limited browser support)
1001
- if ('mediaDevices' in navigator && 'getDisplayMedia' in navigator.mediaDevices) {
1002
- // This requires user interaction and shows a permission dialog
1003
- // Not ideal for automatic screenshots
1004
- console.warn('Screenshot capture requires html2canvas library');
1005
- return null;
1006
- }
1007
- return null;
1008
- }
1009
- catch (error) {
1010
- console.error('Screenshot capture failed:', error);
1011
- return null;
1012
- }
1013
- }
1014
981
  /**
1015
982
  * Simple rate limiter using localStorage
1016
983
  */
@@ -1231,7 +1198,7 @@ class Triagly {
1231
1198
  theme: 'auto',
1232
1199
  position: 'bottom-right',
1233
1200
  buttonShape: 'rounded',
1234
- buttonText: '🐛 Feedback',
1201
+ buttonText: 'Feedback',
1235
1202
  placeholderText: 'Describe what happened...',
1236
1203
  successMessage: 'Feedback sent successfully!',
1237
1204
  errorMessage: 'Failed to send feedback. Please try again.',
@@ -1286,17 +1253,11 @@ class Triagly {
1286
1253
  }
1287
1254
  // Collect metadata
1288
1255
  const metadata = collectMetadata(this.config.metadata);
1289
- // Capture screenshot if requested
1290
- let screenshot = null;
1291
- if (data.includeScreenshot) {
1292
- screenshot = await captureScreenshot();
1293
- }
1294
1256
  // Prepare feedback data
1295
1257
  const feedbackData = {
1296
1258
  title: data.title,
1297
1259
  description: data.description,
1298
1260
  reporterEmail: data.reporterEmail,
1299
- screenshot: screenshot || undefined,
1300
1261
  consoleLogs: this.consoleLogger?.getLogs(),
1301
1262
  };
1302
1263
  // Submit to API with Turnstile token if provided