@triagly/sdk 0.1.1-beta.3 → 0.1.1-beta.5

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
@@ -13,6 +13,24 @@ class FeedbackWidget {
13
13
  init() {
14
14
  this.createButton();
15
15
  this.injectStyles();
16
+ // Load Turnstile script if configured
17
+ if (this.config.turnstileSiteKey) {
18
+ this.loadTurnstileScript();
19
+ }
20
+ }
21
+ /**
22
+ * Load Cloudflare Turnstile script dynamically
23
+ */
24
+ loadTurnstileScript() {
25
+ // Check if already loaded
26
+ if (window.turnstile || document.querySelector('script[src*="turnstile"]')) {
27
+ return;
28
+ }
29
+ const script = document.createElement('script');
30
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
31
+ script.async = true;
32
+ script.defer = true;
33
+ document.head.appendChild(script);
16
34
  }
17
35
  /**
18
36
  * Create the feedback button
@@ -27,26 +45,23 @@ class FeedbackWidget {
27
45
  // Button orientation
28
46
  const orientation = this.config.orientation || 'horizontal';
29
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>`;
30
51
  // Handle button text based on shape
31
- const fullText = this.config.buttonText || '🐛 Feedback';
52
+ const fullText = this.config.buttonText || 'Feedback';
32
53
  if (shape === 'circular') {
33
- button.innerHTML = '🐛';
54
+ button.innerHTML = largeIcon;
34
55
  button.setAttribute('aria-label', fullText);
35
56
  }
36
57
  else if (shape === 'expandable') {
37
- // Expandable starts with emoji, expands to full text on hover
38
- 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>`;
39
60
  button.setAttribute('aria-label', fullText);
40
- // Store custom text if provided
41
- if (this.config.buttonText) {
42
- const textSpan = button.querySelector('.triagly-btn-text');
43
- if (textSpan) {
44
- textSpan.textContent = ' ' + this.config.buttonText.replace('🐛', '').trim();
45
- }
46
- }
47
61
  }
48
62
  else {
49
- button.innerHTML = fullText;
63
+ // Default: icon + text
64
+ button.innerHTML = `${speechBubbleIcon}<span class="triagly-btn-label">${fullText}</span>`;
50
65
  }
51
66
  button.onclick = () => this.toggle();
52
67
  // Position button
@@ -112,9 +127,9 @@ class FeedbackWidget {
112
127
  this.setupKeyboardEvents();
113
128
  // Set up focus trap
114
129
  this.setupFocusTrap();
115
- // Focus on title field
116
- const titleInput = this.container?.querySelector('input[type="text"]');
117
- titleInput?.focus();
130
+ // Focus on description field
131
+ const descInput = this.container?.querySelector('#triagly-description');
132
+ descInput?.focus();
118
133
  }, 0);
119
134
  }
120
135
  /**
@@ -159,7 +174,7 @@ class FeedbackWidget {
159
174
  overlay.className = 'triagly-overlay';
160
175
  overlay.setAttribute('role', 'dialog');
161
176
  overlay.setAttribute('aria-modal', 'true');
162
- overlay.setAttribute('aria-labelledby', 'triagly-modal-title');
177
+ overlay.setAttribute('aria-label', 'Send feedback');
163
178
  overlay.onclick = (e) => {
164
179
  if (e.target === overlay)
165
180
  this.close('overlay');
@@ -170,7 +185,6 @@ class FeedbackWidget {
170
185
  const header = document.createElement('div');
171
186
  header.className = 'triagly-header';
172
187
  header.innerHTML = `
173
- <h3 id="triagly-modal-title">Send Feedback</h3>
174
188
  <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>
175
189
  `;
176
190
  const closeBtn = header.querySelector('.triagly-close');
@@ -179,16 +193,7 @@ class FeedbackWidget {
179
193
  form.className = 'triagly-form';
180
194
  form.innerHTML = `
181
195
  <div class="triagly-field">
182
- <label for="triagly-title">Title (optional)</label>
183
- <input
184
- type="text"
185
- id="triagly-title"
186
- placeholder="Brief summary of your feedback"
187
- />
188
- </div>
189
-
190
- <div class="triagly-field">
191
- <label for="triagly-description">Description *</label>
196
+ <label for="triagly-description">What's on your mind?</label>
192
197
  <textarea
193
198
  id="triagly-description"
194
199
  required
@@ -197,6 +202,15 @@ class FeedbackWidget {
197
202
  ></textarea>
198
203
  </div>
199
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
+
200
214
  <div class="triagly-field">
201
215
  <label for="triagly-email">Email (optional)</label>
202
216
  <input
@@ -206,13 +220,6 @@ class FeedbackWidget {
206
220
  />
207
221
  </div>
208
222
 
209
- <div class="triagly-field triagly-checkbox">
210
- <label>
211
- <input type="checkbox" id="triagly-screenshot" checked />
212
- <span>Include screenshot</span>
213
- </label>
214
- </div>
215
-
216
223
  ${this.config.turnstileSiteKey ? `
217
224
  <div class="triagly-field triagly-turnstile">
218
225
  <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>
@@ -236,8 +243,16 @@ class FeedbackWidget {
236
243
  e.preventDefault();
237
244
  this.handleSubmit(form);
238
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
+ `;
239
253
  modal.appendChild(header);
240
254
  modal.appendChild(form);
255
+ modal.appendChild(footer);
241
256
  overlay.appendChild(modal);
242
257
  // Render Turnstile widget if available
243
258
  if (this.config.turnstileSiteKey) {
@@ -286,10 +301,9 @@ class FeedbackWidget {
286
301
  * Handle form submission
287
302
  */
288
303
  async handleSubmit(form) {
289
- const titleInput = form.querySelector('#triagly-title');
290
304
  const descInput = form.querySelector('#triagly-description');
305
+ const nameInput = form.querySelector('#triagly-name');
291
306
  const emailInput = form.querySelector('#triagly-email');
292
- const screenshotCheckbox = form.querySelector('#triagly-screenshot');
293
307
  const statusDiv = form.querySelector('#triagly-status');
294
308
  const submitBtn = form.querySelector('button[type="submit"]');
295
309
  const turnstileContainer = form.querySelector('.cf-turnstile');
@@ -303,10 +317,9 @@ class FeedbackWidget {
303
317
  turnstileToken = turnstileContainer.getAttribute('data-turnstile-response') || undefined;
304
318
  }
305
319
  const data = {
306
- title: titleInput.value.trim() || undefined,
307
320
  description: descInput.value.trim(),
321
+ reporterName: nameInput.value.trim() || undefined,
308
322
  reporterEmail: emailInput.value.trim() || undefined,
309
- includeScreenshot: screenshotCheckbox.checked,
310
323
  turnstileToken,
311
324
  };
312
325
  // Create a promise that waits for actual submission result
@@ -370,21 +383,32 @@ class FeedbackWidget {
370
383
  position: fixed;
371
384
  z-index: 999999;
372
385
  padding: 12px 20px;
373
- background: var(--triagly-button-bg, #6366f1);
386
+ background: var(--triagly-button-bg, #18181b);
374
387
  color: var(--triagly-button-text, #ffffff);
375
388
  border: none;
376
389
  border-radius: var(--triagly-button-radius, 8px);
377
390
  font-size: 14px;
378
391
  font-weight: 500;
379
392
  cursor: pointer;
380
- 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));
381
394
  transition: all 0.2s;
395
+ display: inline-flex;
396
+ align-items: center;
397
+ gap: 8px;
382
398
  }
383
399
 
384
400
  .triagly-button:hover {
385
- background: var(--triagly-button-bg-hover, #4f46e5);
401
+ background: var(--triagly-button-bg-hover, #27272a);
386
402
  transform: translateY(-2px);
387
- 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;
388
412
  }
389
413
 
390
414
  /* Prevent expandable buttons from shifting on hover */
@@ -478,8 +502,8 @@ class FeedbackWidget {
478
502
  min-width: auto;
479
503
  padding: 12px 20px;
480
504
  border-radius: 30px;
481
- background: var(--triagly-button-bg-hover, #4f46e5);
482
- 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));
483
507
  }
484
508
  .triagly-shape-expandable:hover .triagly-btn-text {
485
509
  width: auto;
@@ -538,18 +562,10 @@ class FeedbackWidget {
538
562
 
539
563
  .triagly-header {
540
564
  display: flex;
541
- justify-content: space-between;
565
+ justify-content: flex-end;
542
566
  align-items: center;
543
- padding: 20px 24px;
567
+ padding: 8px 12px 0;
544
568
  background: var(--triagly-header-bg, #ffffff);
545
- border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);
546
- }
547
-
548
- .triagly-header h3 {
549
- margin: 0;
550
- font-size: 18px;
551
- font-weight: 600;
552
- color: var(--triagly-header-text, #111827);
553
569
  }
554
570
 
555
571
  .triagly-close {
@@ -607,46 +623,21 @@ class FeedbackWidget {
607
623
  .triagly-field input:focus,
608
624
  .triagly-field textarea:focus {
609
625
  outline: none;
610
- border-color: var(--triagly-input-border-focus, #6366f1);
611
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
612
- }
613
-
614
- .triagly-checkbox label {
615
- display: flex;
616
- align-items: center;
617
- gap: 8px;
618
- cursor: pointer;
619
- font-weight: 400;
620
- }
621
-
622
- .triagly-checkbox label span {
623
- user-select: none;
624
- }
625
-
626
- .triagly-checkbox input {
627
- width: 16px;
628
- height: 16px;
629
- margin: 0;
630
- cursor: pointer;
626
+ border-color: var(--triagly-input-border-focus, #a1a1aa);
627
+ box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);
631
628
  }
632
629
 
633
630
  /* Focus visible styles for accessibility */
634
631
  .triagly-button:focus-visible,
635
632
  .triagly-field input:focus-visible,
636
633
  .triagly-field textarea:focus-visible,
637
- .triagly-checkbox input:focus-visible,
638
634
  .triagly-btn-primary:focus-visible,
639
635
  .triagly-btn-secondary:focus-visible,
640
636
  .triagly-close:focus-visible {
641
- outline: 2px solid #6366f1;
637
+ outline: 2px solid #a1a1aa;
642
638
  outline-offset: 2px;
643
639
  }
644
640
 
645
- /* Checkbox label gets visual indicator when checkbox is focused */
646
- .triagly-checkbox input:focus-visible + span {
647
- text-decoration: underline;
648
- }
649
-
650
641
  .triagly-turnstile {
651
642
  display: flex;
652
643
  justify-content: center;
@@ -672,12 +663,12 @@ class FeedbackWidget {
672
663
  }
673
664
 
674
665
  .triagly-btn-primary {
675
- background: var(--triagly-btn-primary-bg, #6366f1);
666
+ background: var(--triagly-btn-primary-bg, #18181b);
676
667
  color: var(--triagly-btn-primary-text, #ffffff);
677
668
  }
678
669
 
679
670
  .triagly-btn-primary:hover:not(:disabled) {
680
- background: var(--triagly-btn-primary-bg-hover, #4f46e5);
671
+ background: var(--triagly-btn-primary-bg-hover, #27272a);
681
672
  }
682
673
 
683
674
  .triagly-btn-primary:disabled {
@@ -713,6 +704,29 @@ class FeedbackWidget {
713
704
  background: var(--triagly-error-bg, #fee2e2);
714
705
  color: var(--triagly-error-text, #991b1b);
715
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
+ }
716
730
  `;
717
731
  document.head.appendChild(style);
718
732
  }
@@ -878,7 +892,6 @@ class TriaglyAPI {
878
892
  consoleLogs: data.consoleLogs,
879
893
  },
880
894
  tags: data.tags,
881
- screenshot: data.screenshot,
882
895
  reporterEmail: data.reporterEmail,
883
896
  reporterName: data.reporterName,
884
897
  turnstileToken,
@@ -965,34 +978,6 @@ function detectBrowser() {
965
978
  }
966
979
  return browser;
967
980
  }
968
- /**
969
- * Capture screenshot of current page
970
- */
971
- async function captureScreenshot() {
972
- try {
973
- // Use html2canvas library if available
974
- if (typeof window.html2canvas !== 'undefined') {
975
- const canvas = await window.html2canvas(document.body, {
976
- logging: false,
977
- useCORS: true,
978
- allowTaint: true,
979
- });
980
- return canvas.toDataURL('image/png');
981
- }
982
- // Fallback to native screenshot API if supported (limited browser support)
983
- if ('mediaDevices' in navigator && 'getDisplayMedia' in navigator.mediaDevices) {
984
- // This requires user interaction and shows a permission dialog
985
- // Not ideal for automatic screenshots
986
- console.warn('Screenshot capture requires html2canvas library');
987
- return null;
988
- }
989
- return null;
990
- }
991
- catch (error) {
992
- console.error('Screenshot capture failed:', error);
993
- return null;
994
- }
995
- }
996
981
  /**
997
982
  * Simple rate limiter using localStorage
998
983
  */
@@ -1213,7 +1198,7 @@ class Triagly {
1213
1198
  theme: 'auto',
1214
1199
  position: 'bottom-right',
1215
1200
  buttonShape: 'rounded',
1216
- buttonText: '🐛 Feedback',
1201
+ buttonText: 'Feedback',
1217
1202
  placeholderText: 'Describe what happened...',
1218
1203
  successMessage: 'Feedback sent successfully!',
1219
1204
  errorMessage: 'Failed to send feedback. Please try again.',
@@ -1268,17 +1253,11 @@ class Triagly {
1268
1253
  }
1269
1254
  // Collect metadata
1270
1255
  const metadata = collectMetadata(this.config.metadata);
1271
- // Capture screenshot if requested
1272
- let screenshot = null;
1273
- if (data.includeScreenshot) {
1274
- screenshot = await captureScreenshot();
1275
- }
1276
1256
  // Prepare feedback data
1277
1257
  const feedbackData = {
1278
1258
  title: data.title,
1279
1259
  description: data.description,
1280
1260
  reporterEmail: data.reporterEmail,
1281
- screenshot: screenshot || undefined,
1282
1261
  consoleLogs: this.consoleLogger?.getLogs(),
1283
1262
  };
1284
1263
  // Submit to API with Turnstile token if provided