@jamwidgets/astro 0.3.4 → 0.4.2

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
@@ -239,18 +239,29 @@ await addReaction({
239
239
 
240
240
  ## Styling
241
241
 
242
- Components use CSS custom properties for theming. Override them to match your site:
242
+ Components inherit their font and text color from the surrounding page by default, while interactive controls use the browser's accent colors. Use a preset when the widget sits outside your site's normal color context:
243
+
244
+ ```astro
245
+ <Comments theme="auto" ... />
246
+ ```
247
+
248
+ Set semantic CSS custom properties on a wrapper or the widget itself to match your site. The same tokens work across every styled component:
243
249
 
244
250
  ```css
245
- .jamwidgets-comments {
246
- --jamwidgets-border-color: #e5e7eb;
247
- --jamwidgets-bg-color: #f9fafb;
248
- --jamwidgets-text-color: inherit;
249
- --jamwidgets-button-bg: #3b82f6;
250
- /* ... see component source for all variables */
251
+ .article-widgets {
252
+ --jamwidgets-color-text: var(--color-ink);
253
+ --jamwidgets-color-muted: var(--color-ink-muted);
254
+ --jamwidgets-color-surface: var(--color-paper);
255
+ --jamwidgets-color-border: var(--color-rule);
256
+ --jamwidgets-color-accent: var(--color-link);
257
+ --jamwidgets-color-accent-hover: var(--color-link-hover);
258
+ --jamwidgets-color-on-accent: white;
259
+ --jamwidgets-focus-ring-color: color-mix(in srgb, var(--color-link) 28%, transparent);
251
260
  }
252
261
  ```
253
262
 
263
+ Status colors can be customized with `--jamwidgets-color-success`, `--jamwidgets-color-error`, and `--jamwidgets-color-notice`. Components adapt to their container width, so subscribe, waitlist, comments, and feedback controls stack when embedded in a narrow column.
264
+
254
265
  ## License
255
266
 
256
267
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.3.4",
3
+ "version": "0.4.2",
4
4
  "description": "Astro components and content loader for Jamwidgets (forms, comments, reactions, posts)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,7 +42,7 @@
42
42
  "scripts": {
43
43
  "build": "tsc",
44
44
  "dev": "tsc --watch",
45
- "test": "vitest run test/loader.test.ts"
45
+ "test": "vitest run"
46
46
  },
47
47
  "keywords": [
48
48
  "astro",
@@ -57,7 +57,7 @@
57
57
  ],
58
58
  "license": "MIT",
59
59
  "dependencies": {
60
- "@jamwidgets/core": "^0.3.3"
60
+ "@jamwidgets/core": "^0.3.4"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "astro": "^5.0.0 || ^6.0.0 || ^7.0.0"
@@ -23,8 +23,10 @@ const { siteKey = "", endpoint = "" } = Astro.props;
23
23
  const el = document.querySelector("[data-jamwidgets-analytics]") as
24
24
  | HTMLElement
25
25
  | null;
26
- initAnalytics({
27
- siteKey: el?.dataset.siteKey || undefined,
28
- endpoint: el?.dataset.endpoint || undefined,
29
- });
26
+ if (el) {
27
+ initAnalytics({
28
+ siteKey: el.dataset.siteKey || undefined,
29
+ endpoint: el.dataset.endpoint || undefined,
30
+ });
31
+ }
30
32
  </script>
@@ -14,6 +14,8 @@ interface Props {
14
14
  siteKey: string;
15
15
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
16
16
  endpoint?: string;
17
+ /** Theme preset. Defaults to inheriting the host page. */
18
+ theme?: "inherit" | "light" | "dark" | "auto";
17
19
  /** CSS class to add to the container */
18
20
  class?: string;
19
21
  }
@@ -24,15 +26,19 @@ const API_PATH = "/api/v1";
24
26
  const {
25
27
  siteKey,
26
28
  endpoint = DEFAULT_ENDPOINT,
29
+ theme = "inherit",
27
30
  class: className = "",
28
31
  } = Astro.props;
29
32
 
30
33
  // Build the API URL
31
34
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
35
+
36
+ import Theme from "./Theme.astro";
32
37
  ---
33
38
 
39
+ <Theme />
34
40
  <div
35
- class:list={["jamwidgets-announcements", className]}
41
+ class:list={["jamwidgets-announcements", `jamwidgets-theme-${theme}`, className]}
36
42
  data-jamwidgets-announcements
37
43
  data-endpoint={baseUrl}
38
44
  data-site-key={siteKey}
@@ -155,23 +161,23 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
155
161
  }
156
162
 
157
163
  .jamwidgets-announcement-info {
158
- background: #dbeafe;
159
- color: #1e40af;
164
+ background: var(--jamwidgets-color-info-surface, color-mix(in srgb, #2563eb 10%, transparent));
165
+ color: var(--jamwidgets-color-info, currentColor);
160
166
  }
161
167
 
162
168
  .jamwidgets-announcement-warning {
163
- background: #fef3c7;
164
- color: #92400e;
169
+ background: var(--jamwidgets-color-notice-surface, color-mix(in srgb, #ca8a04 12%, transparent));
170
+ color: var(--jamwidgets-color-notice, currentColor);
165
171
  }
166
172
 
167
173
  .jamwidgets-announcement-success {
168
- background: #dcfce7;
169
- color: #166534;
174
+ background: var(--jamwidgets-success-bg);
175
+ color: var(--jamwidgets-success-text);
170
176
  }
171
177
 
172
178
  .jamwidgets-announcement-error {
173
- background: #fef2f2;
174
- color: #991b1b;
179
+ background: var(--jamwidgets-error-bg);
180
+ color: var(--jamwidgets-error-text);
175
181
  }
176
182
 
177
183
  .jamwidgets-announcement-content {
@@ -207,27 +213,5 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
207
213
  height: 1rem;
208
214
  }
209
215
 
210
- /* Dark mode adjustments */
211
- @media (prefers-color-scheme: dark) {
212
- .jamwidgets-announcement-info {
213
- background: rgba(59, 130, 246, 0.2);
214
- color: #93c5fd;
215
- }
216
-
217
- .jamwidgets-announcement-warning {
218
- background: rgba(245, 158, 11, 0.2);
219
- color: #fcd34d;
220
- }
221
-
222
- .jamwidgets-announcement-success {
223
- background: rgba(34, 197, 94, 0.2);
224
- color: #86efac;
225
- }
226
-
227
- .jamwidgets-announcement-error {
228
- background: rgba(239, 68, 68, 0.2);
229
- color: #fca5a5;
230
- }
231
- }
232
216
  }
233
217
  </style>
@@ -16,8 +16,8 @@ interface Props {
16
16
  pageId: string;
17
17
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
18
18
  endpoint?: string;
19
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
20
- theme?: "light" | "dark" | "auto";
19
+ /** Theme preset. Defaults to inheriting the host page. */
20
+ theme?: "inherit" | "light" | "dark" | "auto";
21
21
  /** CSS class to add to the container */
22
22
  class?: string;
23
23
  }
@@ -29,7 +29,7 @@ const {
29
29
  siteKey,
30
30
  pageId,
31
31
  endpoint = DEFAULT_ENDPOINT,
32
- theme = "light",
32
+ theme = "inherit",
33
33
  class: className = "",
34
34
  } = Astro.props;
35
35
 
@@ -37,8 +37,10 @@ const {
37
37
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
38
38
 
39
39
  import PoweredBy from "./PoweredBy.astro";
40
+ import Theme from "./Theme.astro";
40
41
  ---
41
42
 
43
+ <Theme />
42
44
  <div
43
45
  class:list={["jamwidgets-comments", `jamwidgets-theme-${theme}`, className]}
44
46
  data-jamwidgets-comments
@@ -227,7 +229,7 @@ import PoweredBy from "./PoweredBy.astro";
227
229
  content: formData.get("content"),
228
230
  parentId: parentId ? parseInt(parentId, 10) : undefined,
229
231
  _gotcha: honeypot || undefined,
230
- _jamwidgets_ts: loadTimestamp,
232
+ _seriph_ts: loadTimestamp,
231
233
  }),
232
234
  },
233
235
  );
@@ -260,57 +262,11 @@ import PoweredBy from "./PoweredBy.astro";
260
262
  </script>
261
263
 
262
264
  <style is:global>
265
+ @layer jamwidgets {
263
266
  .jamwidgets-comments {
264
- --jamwidgets-border-color: #e5e7eb;
265
- --jamwidgets-bg-color: #f9fafb;
266
- --jamwidgets-text-color: inherit;
267
- --jamwidgets-text-muted: #6b7280;
268
- --jamwidgets-input-bg: white;
269
- --jamwidgets-input-border: #9ca3af;
270
- --jamwidgets-focus-color: #3b82f6;
271
- --jamwidgets-focus-ring: rgba(59, 130, 246, 0.2);
272
- --jamwidgets-button-bg: #3b82f6;
273
- --jamwidgets-button-hover: #2563eb;
274
- --jamwidgets-button-text: white;
275
- --jamwidgets-notice-bg: #fef3c7;
276
- --jamwidgets-notice-text: #92400e;
277
267
  color: var(--jamwidgets-text-color);
278
268
  }
279
269
 
280
- .jamwidgets-comments.jamwidgets-theme-dark {
281
- --jamwidgets-border-color: #4b5563;
282
- --jamwidgets-bg-color: transparent;
283
- --jamwidgets-text-color: #f3f4f6;
284
- --jamwidgets-text-muted: #9ca3af;
285
- --jamwidgets-input-bg: #374151;
286
- --jamwidgets-input-border: #9ca3af;
287
- --jamwidgets-focus-color: #60a5fa;
288
- --jamwidgets-focus-ring: rgba(96, 165, 250, 0.3);
289
- --jamwidgets-button-bg: #3b82f6;
290
- --jamwidgets-button-hover: #60a5fa;
291
- --jamwidgets-button-text: white;
292
- --jamwidgets-notice-bg: #422006;
293
- --jamwidgets-notice-text: #fcd34d;
294
- }
295
-
296
- @media (prefers-color-scheme: dark) {
297
- .jamwidgets-comments.jamwidgets-theme-auto {
298
- --jamwidgets-border-color: #4b5563;
299
- --jamwidgets-bg-color: transparent;
300
- --jamwidgets-text-color: #f3f4f6;
301
- --jamwidgets-text-muted: #9ca3af;
302
- --jamwidgets-input-bg: #374151;
303
- --jamwidgets-input-border: #9ca3af;
304
- --jamwidgets-focus-color: #60a5fa;
305
- --jamwidgets-focus-ring: rgba(96, 165, 250, 0.3);
306
- --jamwidgets-button-bg: #3b82f6;
307
- --jamwidgets-button-hover: #60a5fa;
308
- --jamwidgets-button-text: white;
309
- --jamwidgets-notice-bg: #422006;
310
- --jamwidgets-notice-text: #fcd34d;
311
- }
312
- }
313
-
314
270
  .jamwidgets-comments-form {
315
271
  margin-top: 1rem;
316
272
  padding-top: 1rem;
@@ -320,7 +276,7 @@ import PoweredBy from "./PoweredBy.astro";
320
276
  gap: 0.5rem;
321
277
  }
322
278
 
323
- @media (min-width: 480px) {
279
+ @container (min-width: 30rem) {
324
280
  .jamwidgets-comments-form {
325
281
  grid-template-columns: 1fr 1fr;
326
282
  }
@@ -384,9 +340,7 @@ import PoweredBy from "./PoweredBy.astro";
384
340
 
385
341
  .jamwidgets-comments .jamwidgets-form-group input:focus,
386
342
  .jamwidgets-comments .jamwidgets-form-group textarea:focus {
387
- outline: none;
388
343
  border-color: var(--jamwidgets-focus-color);
389
- box-shadow: 0 0 0 2px var(--jamwidgets-focus-ring);
390
344
  }
391
345
 
392
346
  .jamwidgets-comments .jamwidgets-comments-form button[type="submit"] {
@@ -474,4 +428,5 @@ import PoweredBy from "./PoweredBy.astro";
474
428
  border-radius: 0.25rem;
475
429
  font-size: 0.8125rem;
476
430
  }
431
+ }
477
432
  </style>
package/src/Embed.astro CHANGED
@@ -16,8 +16,8 @@ interface Props {
16
16
  siteKey: string;
17
17
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
18
18
  endpoint?: string;
19
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
20
- theme?: "light" | "dark" | "auto";
19
+ /** Theme preset. Defaults to inheriting the host page. */
20
+ theme?: "inherit" | "light" | "dark" | "auto";
21
21
  }
22
22
 
23
23
  const DEFAULT_ENDPOINT = "https://jamwidgets.com";
@@ -26,12 +26,15 @@ const API_PATH = "/api/v1";
26
26
  const {
27
27
  siteKey,
28
28
  endpoint = DEFAULT_ENDPOINT,
29
- theme = "light",
29
+ theme = "inherit",
30
30
  } = Astro.props;
31
31
 
32
32
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
33
+
34
+ import Theme from "./Theme.astro";
33
35
  ---
34
36
 
37
+ <Theme />
35
38
  <jamwidgets-embed
36
39
  data-site-key={siteKey}
37
40
  data-endpoint={baseUrl}
@@ -316,50 +319,9 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
316
319
  <style is:global>
317
320
  @layer jamwidgets {
318
321
  .jamwidgets-poll {
319
- --jamwidgets-border-color: #e5e7eb;
320
- --jamwidgets-bg-color: white;
321
- --jamwidgets-text-color: inherit;
322
- --jamwidgets-text-muted: #6b7280;
323
- --jamwidgets-hover-bg: #f3f4f6;
324
- --jamwidgets-selected-bg: #dbeafe;
325
- --jamwidgets-selected-border: #3b82f6;
326
- --jamwidgets-bar-bg: #dbeafe;
327
- --jamwidgets-voted-bar-bg: #3b82f6;
328
- --jamwidgets-button-bg: #3b82f6;
329
- --jamwidgets-button-text: white;
330
- --jamwidgets-button-hover: #2563eb;
331
-
332
322
  font-family: inherit;
333
323
  }
334
324
 
335
- /* Dark theme */
336
- .jamwidgets-poll.jamwidgets-theme-dark {
337
- --jamwidgets-border-color: #374151;
338
- --jamwidgets-bg-color: transparent;
339
- --jamwidgets-text-color: #f3f4f6;
340
- --jamwidgets-text-muted: #9ca3af;
341
- --jamwidgets-hover-bg: #374151;
342
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.2);
343
- --jamwidgets-selected-border: #3b82f6;
344
- --jamwidgets-bar-bg: rgba(59, 130, 246, 0.2);
345
- --jamwidgets-voted-bar-bg: #3b82f6;
346
- }
347
-
348
- /* Auto theme */
349
- @media (prefers-color-scheme: dark) {
350
- .jamwidgets-poll.jamwidgets-theme-auto {
351
- --jamwidgets-border-color: #374151;
352
- --jamwidgets-bg-color: transparent;
353
- --jamwidgets-text-color: #f3f4f6;
354
- --jamwidgets-text-muted: #9ca3af;
355
- --jamwidgets-hover-bg: #374151;
356
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.2);
357
- --jamwidgets-selected-border: #3b82f6;
358
- --jamwidgets-bar-bg: rgba(59, 130, 246, 0.2);
359
- --jamwidgets-voted-bar-bg: #3b82f6;
360
- }
361
- }
362
-
363
325
  .jamwidgets-poll-loading {
364
326
  display: flex;
365
327
  justify-content: center;
@@ -491,23 +453,11 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
491
453
 
492
454
  .jamwidgets-poll-error {
493
455
  padding: 0.75rem;
494
- background: #fef2f2;
495
- color: #991b1b;
456
+ background: var(--jamwidgets-error-bg);
457
+ color: var(--jamwidgets-error-text);
496
458
  border-radius: 0.5rem;
497
459
  font-size: 0.875rem;
498
460
  }
499
461
 
500
- .jamwidgets-theme-dark .jamwidgets-poll-error,
501
- .jamwidgets-theme-auto .jamwidgets-poll-error {
502
- background: rgba(239, 68, 68, 0.15);
503
- color: #fca5a5;
504
- }
505
-
506
- @media (prefers-color-scheme: dark) {
507
- .jamwidgets-theme-auto .jamwidgets-poll-error {
508
- background: rgba(239, 68, 68, 0.15);
509
- color: #fca5a5;
510
- }
511
- }
512
462
  }
513
463
  </style>
@@ -14,8 +14,8 @@ interface Props {
14
14
  siteKey: string;
15
15
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
16
16
  endpoint?: string;
17
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
18
- theme?: "light" | "dark" | "auto";
17
+ /** Theme preset. Defaults to inheriting the host page. */
18
+ theme?: "inherit" | "light" | "dark" | "auto";
19
19
  /** Placeholder text for the feedback input (default: 'Share your feedback...') */
20
20
  placeholder?: string;
21
21
  /** Show feedback type selector (default: true) */
@@ -30,7 +30,7 @@ const API_PATH = "/api/v1";
30
30
  const {
31
31
  siteKey,
32
32
  endpoint = DEFAULT_ENDPOINT,
33
- theme = "light",
33
+ theme = "inherit",
34
34
  placeholder = "Share your feedback...",
35
35
  showTypeSelector = true,
36
36
  class: className = "",
@@ -40,8 +40,10 @@ const {
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
41
 
42
42
  import PoweredBy from "./PoweredBy.astro";
43
+ import Theme from "./Theme.astro";
43
44
  ---
44
45
 
46
+ <Theme />
45
47
  <div
46
48
  class:list={["jamwidgets-feedback", `jamwidgets-theme-${theme}`, className]}
47
49
  data-jamwidgets-feedback
@@ -170,48 +172,11 @@ import PoweredBy from "./PoweredBy.astro";
170
172
  </script>
171
173
 
172
174
  <style is:global>
175
+ @layer jamwidgets {
173
176
  .jamwidgets-feedback {
174
- --jamwidgets-border-color: #9ca3af;
175
- --jamwidgets-bg-color: white;
176
- --jamwidgets-text-color: inherit;
177
- --jamwidgets-text-muted: #6b7280;
178
- --jamwidgets-hover-bg: #f3f4f6;
179
- --jamwidgets-selected-bg: #dbeafe;
180
- --jamwidgets-selected-border: #3b82f6;
181
- --jamwidgets-button-bg: #3b82f6;
182
- --jamwidgets-button-text: white;
183
- --jamwidgets-button-hover: #2563eb;
184
- --jamwidgets-success-color: #16a34a;
185
- --jamwidgets-error-color: #dc2626;
186
177
  font-family: inherit;
187
178
  }
188
179
 
189
- .jamwidgets-feedback.jamwidgets-theme-dark {
190
- --jamwidgets-border-color: #9ca3af;
191
- --jamwidgets-bg-color: #374151;
192
- --jamwidgets-text-color: #f3f4f6;
193
- --jamwidgets-text-muted: #9ca3af;
194
- --jamwidgets-hover-bg: #4b5563;
195
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.3);
196
- --jamwidgets-selected-border: #60a5fa;
197
- --jamwidgets-success-color: #22c55e;
198
- --jamwidgets-error-color: #ef4444;
199
- }
200
-
201
- @media (prefers-color-scheme: dark) {
202
- .jamwidgets-feedback.jamwidgets-theme-auto {
203
- --jamwidgets-border-color: #9ca3af;
204
- --jamwidgets-bg-color: #374151;
205
- --jamwidgets-text-color: #f3f4f6;
206
- --jamwidgets-text-muted: #9ca3af;
207
- --jamwidgets-hover-bg: #4b5563;
208
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.3);
209
- --jamwidgets-selected-border: #60a5fa;
210
- --jamwidgets-success-color: #22c55e;
211
- --jamwidgets-error-color: #ef4444;
212
- }
213
- }
214
-
215
180
  .jamwidgets-feedback-form {
216
181
  display: flex;
217
182
  flex-direction: column;
@@ -278,9 +243,7 @@ import PoweredBy from "./PoweredBy.astro";
278
243
  }
279
244
 
280
245
  .jamwidgets-feedback .jamwidgets-feedback-textarea:focus {
281
- outline: none;
282
246
  border-color: var(--jamwidgets-selected-border);
283
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
284
247
  }
285
248
 
286
249
  .jamwidgets-feedback .jamwidgets-feedback-textarea::placeholder {
@@ -328,4 +291,16 @@ import PoweredBy from "./PoweredBy.astro";
328
291
  opacity: 0.5;
329
292
  cursor: not-allowed;
330
293
  }
294
+
295
+ @container (max-width: 25rem) {
296
+ .jamwidgets-feedback-footer {
297
+ align-items: stretch;
298
+ flex-direction: column;
299
+ }
300
+
301
+ .jamwidgets-feedback .jamwidgets-feedback-submit {
302
+ width: 100%;
303
+ }
304
+ }
305
+ }
331
306
  </style>
package/src/Form.astro CHANGED
@@ -21,8 +21,8 @@ interface Props {
21
21
  formSlug: string;
22
22
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
23
23
  endpoint?: string;
24
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
25
- theme?: "light" | "dark" | "auto";
24
+ /** Theme preset. Defaults to inheriting the host page. */
25
+ theme?: "inherit" | "light" | "dark" | "auto";
26
26
  /** CSS class to add to the form */
27
27
  class?: string;
28
28
  }
@@ -34,7 +34,7 @@ const {
34
34
  siteKey,
35
35
  formSlug,
36
36
  endpoint = DEFAULT_ENDPOINT,
37
- theme = "light",
37
+ theme = "inherit",
38
38
  class: className = "",
39
39
  } = Astro.props;
40
40
 
@@ -42,8 +42,10 @@ const {
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
43
 
44
44
  import PoweredBy from "./PoweredBy.astro";
45
+ import Theme from "./Theme.astro";
45
46
  ---
46
47
 
48
+ <Theme />
47
49
  <form
48
50
  class:list={["jamwidgets-form", `jamwidgets-theme-${theme}`, className]}
49
51
  data-jamwidgets-form
@@ -65,7 +67,7 @@ import PoweredBy from "./PoweredBy.astro";
65
67
  </form>
66
68
 
67
69
  <script>
68
- import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
70
+ import { generateUUID, getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
69
71
 
70
72
  interface FormSubmitResponse {
71
73
  success: boolean;
@@ -74,10 +76,13 @@ import PoweredBy from "./PoweredBy.astro";
74
76
 
75
77
  // Store form load timestamps for spam detection
76
78
  const formTimestamps = new WeakMap<Element, number>();
79
+ const requestIds = new WeakMap<Element, string>();
80
+ const requestBodies = new WeakMap<Element, string>();
77
81
 
78
82
  document.querySelectorAll("[data-jamwidgets-form]").forEach((form) => {
79
83
  // Record when form was loaded (for time-based spam detection)
80
84
  formTimestamps.set(form, Math.floor(Date.now() / 1000));
85
+ requestIds.set(form, generateUUID());
81
86
 
82
87
  form.addEventListener("submit", async (e) => {
83
88
  e.preventDefault();
@@ -110,8 +115,14 @@ import PoweredBy from "./PoweredBy.astro";
110
115
  // Add timestamp for time-based spam detection
111
116
  const loadTimestamp = formTimestamps.get(formEl);
112
117
  if (loadTimestamp) {
113
- data._jamwidgets_ts = loadTimestamp;
118
+ data._seriph_ts = loadTimestamp;
114
119
  }
120
+ const comparableBody = JSON.stringify(data);
121
+ if (requestBodies.get(formEl) !== comparableBody) {
122
+ requestIds.set(formEl, generateUUID());
123
+ requestBodies.set(formEl, comparableBody);
124
+ }
125
+ data._seriph_id = requestIds.get(formEl) || generateUUID();
115
126
 
116
127
  // Dispatch loading event
117
128
  formEl.dispatchEvent(new CustomEvent("jamwidgets:loading"));
@@ -139,6 +150,8 @@ import PoweredBy from "./PoweredBy.astro";
139
150
  }
140
151
 
141
152
  const result: FormSubmitResponse = await response.json();
153
+ requestIds.set(formEl, generateUUID());
154
+ requestBodies.delete(formEl);
142
155
 
143
156
  // Show success message
144
157
  if (messageEl) {
@@ -181,37 +194,6 @@ import PoweredBy from "./PoweredBy.astro";
181
194
 
182
195
  <style is:global>
183
196
  @layer jamwidgets {
184
- .jamwidgets-form {
185
- --jamwidgets-success-bg: #dcfce7;
186
- --jamwidgets-success-text: #166534;
187
- --jamwidgets-success-border: #bbf7d0;
188
- --jamwidgets-error-bg: #fef2f2;
189
- --jamwidgets-error-text: #991b1b;
190
- --jamwidgets-error-border: #fecaca;
191
- }
192
-
193
- /* Dark theme */
194
- .jamwidgets-form.jamwidgets-theme-dark {
195
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
196
- --jamwidgets-success-text: #86efac;
197
- --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
198
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
199
- --jamwidgets-error-text: #fca5a5;
200
- --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
201
- }
202
-
203
- /* Auto theme - follows prefers-color-scheme */
204
- @media (prefers-color-scheme: dark) {
205
- .jamwidgets-form.jamwidgets-theme-auto {
206
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
207
- --jamwidgets-success-text: #86efac;
208
- --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
209
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
210
- --jamwidgets-error-text: #fca5a5;
211
- --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
212
- }
213
- }
214
-
215
197
  .jamwidgets-form-message {
216
198
  margin-top: 1rem;
217
199
  padding: 0.75rem 1rem;
package/src/Poll.astro CHANGED
@@ -19,8 +19,8 @@ interface Props {
19
19
  slug: string;
20
20
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
21
21
  endpoint?: string;
22
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
23
- theme?: "light" | "dark" | "auto";
22
+ /** Theme preset. Defaults to inheriting the host page. */
23
+ theme?: "inherit" | "light" | "dark" | "auto";
24
24
  /** CSS class to add to the container */
25
25
  class?: string;
26
26
  }
@@ -32,7 +32,7 @@ const {
32
32
  siteKey,
33
33
  slug,
34
34
  endpoint = DEFAULT_ENDPOINT,
35
- theme = "light",
35
+ theme = "inherit",
36
36
  class: className = "",
37
37
  } = Astro.props;
38
38
 
@@ -40,8 +40,10 @@ const {
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
41
 
42
42
  import PoweredBy from "./PoweredBy.astro";
43
+ import Theme from "./Theme.astro";
43
44
  ---
44
45
 
46
+ <Theme />
45
47
  <div
46
48
  class:list={["jamwidgets-poll", `jamwidgets-theme-${theme}`, className]}
47
49
  data-jamwidgets-poll
@@ -297,51 +299,11 @@ import PoweredBy from "./PoweredBy.astro";
297
299
  </script>
298
300
 
299
301
  <style is:global>
302
+ @layer jamwidgets {
300
303
  .jamwidgets-poll {
301
- --jamwidgets-border-color: #e5e7eb;
302
- --jamwidgets-bg-color: white;
303
- --jamwidgets-text-color: inherit;
304
- --jamwidgets-text-muted: #6b7280;
305
- --jamwidgets-hover-bg: #f3f4f6;
306
- --jamwidgets-selected-bg: #dbeafe;
307
- --jamwidgets-selected-border: #3b82f6;
308
- --jamwidgets-bar-bg: #dbeafe;
309
- --jamwidgets-voted-bar-bg: #3b82f6;
310
- --jamwidgets-button-bg: #3b82f6;
311
- --jamwidgets-button-text: white;
312
- --jamwidgets-button-hover: #2563eb;
313
-
314
304
  font-family: inherit;
315
305
  }
316
306
 
317
- /* Dark theme */
318
- .jamwidgets-poll.jamwidgets-theme-dark {
319
- --jamwidgets-border-color: #374151;
320
- --jamwidgets-bg-color: transparent;
321
- --jamwidgets-text-color: #f3f4f6;
322
- --jamwidgets-text-muted: #9ca3af;
323
- --jamwidgets-hover-bg: #374151;
324
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.2);
325
- --jamwidgets-selected-border: #3b82f6;
326
- --jamwidgets-bar-bg: rgba(59, 130, 246, 0.2);
327
- --jamwidgets-voted-bar-bg: #3b82f6;
328
- }
329
-
330
- /* Auto theme */
331
- @media (prefers-color-scheme: dark) {
332
- .jamwidgets-poll.jamwidgets-theme-auto {
333
- --jamwidgets-border-color: #374151;
334
- --jamwidgets-bg-color: transparent;
335
- --jamwidgets-text-color: #f3f4f6;
336
- --jamwidgets-text-muted: #9ca3af;
337
- --jamwidgets-hover-bg: #374151;
338
- --jamwidgets-selected-bg: rgba(59, 130, 246, 0.2);
339
- --jamwidgets-selected-border: #3b82f6;
340
- --jamwidgets-bar-bg: rgba(59, 130, 246, 0.2);
341
- --jamwidgets-voted-bar-bg: #3b82f6;
342
- }
343
- }
344
-
345
307
  .jamwidgets-poll-loading {
346
308
  display: flex;
347
309
  justify-content: center;
@@ -473,22 +435,11 @@ import PoweredBy from "./PoweredBy.astro";
473
435
 
474
436
  .jamwidgets-poll-error {
475
437
  padding: 0.75rem;
476
- background: #fef2f2;
477
- color: #991b1b;
438
+ background: var(--jamwidgets-error-bg);
439
+ color: var(--jamwidgets-error-text);
478
440
  border-radius: 0.5rem;
479
441
  font-size: 0.875rem;
480
442
  }
481
443
 
482
- .jamwidgets-theme-dark .jamwidgets-poll-error,
483
- .jamwidgets-theme-auto .jamwidgets-poll-error {
484
- background: rgba(239, 68, 68, 0.15);
485
- color: #fca5a5;
486
- }
487
-
488
- @media (prefers-color-scheme: dark) {
489
- .jamwidgets-theme-auto .jamwidgets-poll-error {
490
- background: rgba(239, 68, 68, 0.15);
491
- color: #fca5a5;
492
- }
493
444
  }
494
445
  </style>
@@ -74,7 +74,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
74
74
  line-height: 1;
75
75
  }
76
76
  .jamwidgets-powered-by a {
77
- color: var(--jamwidgets-muted-color, #9ca3af);
77
+ color: var(--jamwidgets-color-muted, color-mix(in srgb, currentColor 55%, transparent));
78
78
  text-decoration: none;
79
79
  }
80
80
  .jamwidgets-powered-by a:hover {
@@ -24,8 +24,8 @@ interface Props {
24
24
  reactions?: string[];
25
25
  /** Custom icons for reaction types (emoji or text) */
26
26
  icons?: Record<string, string>;
27
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
28
- theme?: "light" | "dark" | "auto";
27
+ /** Theme preset. Defaults to inheriting the host page. */
28
+ theme?: "inherit" | "light" | "dark" | "auto";
29
29
  /** CSS class to add to the container */
30
30
  class?: string;
31
31
  }
@@ -49,7 +49,7 @@ const {
49
49
  endpoint = DEFAULT_ENDPOINT,
50
50
  reactions = ["like"],
51
51
  icons = {},
52
- theme = "light",
52
+ theme = "inherit",
53
53
  class: className = "",
54
54
  } = Astro.props;
55
55
 
@@ -62,8 +62,11 @@ function getIcon(type: string): string {
62
62
 
63
63
  // Build the API URL
64
64
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
65
+
66
+ import Theme from "./Theme.astro";
65
67
  ---
66
68
 
69
+ <Theme />
67
70
  <div
68
71
  class:list={["jamwidgets-reactions", `jamwidgets-theme-${theme}`, className]}
69
72
  data-jamwidgets-reactions
@@ -215,50 +218,13 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
215
218
  </script>
216
219
 
217
220
  <style is:global>
221
+ @layer jamwidgets {
218
222
  .jamwidgets-reactions {
219
- --jamwidgets-border-color: #e5e7eb;
220
- --jamwidgets-button-bg: white;
221
- --jamwidgets-hover-bg: #f3f4f6;
222
- --jamwidgets-hover-border: #d1d5db;
223
- --jamwidgets-active-bg: #dbeafe;
224
- --jamwidgets-active-border: #93c5fd;
225
- --jamwidgets-active-hover-bg: #bfdbfe;
226
- --jamwidgets-count-color: #6b7280;
227
- --jamwidgets-active-text: #1d4ed8;
228
-
229
223
  display: flex;
230
224
  gap: 0.5rem;
231
225
  flex-wrap: wrap;
232
226
  }
233
227
 
234
- /* Dark theme */
235
- .jamwidgets-reactions.jamwidgets-theme-dark {
236
- --jamwidgets-border-color: #374151;
237
- --jamwidgets-button-bg: transparent;
238
- --jamwidgets-hover-bg: #374151;
239
- --jamwidgets-hover-border: #4b5563;
240
- --jamwidgets-active-bg: rgba(59, 130, 246, 0.2);
241
- --jamwidgets-active-border: #3b82f6;
242
- --jamwidgets-active-hover-bg: rgba(59, 130, 246, 0.3);
243
- --jamwidgets-count-color: #9ca3af;
244
- --jamwidgets-active-text: #60a5fa;
245
- }
246
-
247
- /* Auto theme - follows prefers-color-scheme */
248
- @media (prefers-color-scheme: dark) {
249
- .jamwidgets-reactions.jamwidgets-theme-auto {
250
- --jamwidgets-border-color: #374151;
251
- --jamwidgets-button-bg: transparent;
252
- --jamwidgets-hover-bg: #374151;
253
- --jamwidgets-hover-border: #4b5563;
254
- --jamwidgets-active-bg: rgba(59, 130, 246, 0.2);
255
- --jamwidgets-active-border: #3b82f6;
256
- --jamwidgets-active-hover-bg: rgba(59, 130, 246, 0.3);
257
- --jamwidgets-count-color: #9ca3af;
258
- --jamwidgets-active-text: #60a5fa;
259
- }
260
- }
261
-
262
228
  .jamwidgets-reaction-btn {
263
229
  display: inline-flex;
264
230
  align-items: center;
@@ -306,4 +272,5 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
306
272
  .jamwidgets-reacted .jamwidgets-reaction-count {
307
273
  color: var(--jamwidgets-active-text);
308
274
  }
275
+ }
309
276
  </style>
@@ -28,8 +28,8 @@ interface Props {
28
28
  placeholder?: string;
29
29
  /** Success message override (default: uses server response) */
30
30
  successMessage?: string;
31
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
32
- theme?: "light" | "dark" | "auto";
31
+ /** Theme preset. Defaults to inheriting the host page. */
32
+ theme?: "inherit" | "light" | "dark" | "auto";
33
33
  /** CSS class to add to the form */
34
34
  class?: string;
35
35
  }
@@ -43,7 +43,7 @@ const {
43
43
  buttonText = "Subscribe",
44
44
  placeholder = "your@email.com",
45
45
  successMessage,
46
- theme = "light",
46
+ theme = "inherit",
47
47
  class: className = "",
48
48
  } = Astro.props;
49
49
 
@@ -51,8 +51,10 @@ const {
51
51
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
52
52
 
53
53
  import PoweredBy from "./PoweredBy.astro";
54
+ import Theme from "./Theme.astro";
54
55
  ---
55
56
 
57
+ <Theme />
56
58
  <form
57
59
  class:list={["jamwidgets-subscribe", `jamwidgets-theme-${theme}`, className]}
58
60
  data-jamwidgets-subscribe
@@ -197,55 +199,11 @@ import PoweredBy from "./PoweredBy.astro";
197
199
  </script>
198
200
 
199
201
  <style is:global>
202
+ @layer jamwidgets {
200
203
  .jamwidgets-subscribe {
201
- --jamwidgets-border-color: #d1d5db;
202
- --jamwidgets-input-bg: #ffffff;
203
- --jamwidgets-input-text: #111827;
204
- --jamwidgets-button-bg: #4f46e5;
205
- --jamwidgets-button-text: #ffffff;
206
- --jamwidgets-button-hover: #4338ca;
207
- --jamwidgets-success-bg: #dcfce7;
208
- --jamwidgets-success-text: #166534;
209
- --jamwidgets-success-border: #bbf7d0;
210
- --jamwidgets-error-bg: #fef2f2;
211
- --jamwidgets-error-text: #991b1b;
212
- --jamwidgets-error-border: #fecaca;
213
- --jamwidgets-focus-ring: rgba(79, 70, 229, 0.5);
214
204
  position: relative;
215
205
  }
216
206
 
217
- /* Dark theme */
218
- .jamwidgets-subscribe.jamwidgets-theme-dark {
219
- --jamwidgets-border-color: #4b5563;
220
- --jamwidgets-input-bg: #1f2937;
221
- --jamwidgets-input-text: #f9fafb;
222
- --jamwidgets-button-bg: #6366f1;
223
- --jamwidgets-button-hover: #818cf8;
224
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
225
- --jamwidgets-success-text: #86efac;
226
- --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
227
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
228
- --jamwidgets-error-text: #fca5a5;
229
- --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
230
- }
231
-
232
- /* Auto theme - follows prefers-color-scheme */
233
- @media (prefers-color-scheme: dark) {
234
- .jamwidgets-subscribe.jamwidgets-theme-auto {
235
- --jamwidgets-border-color: #4b5563;
236
- --jamwidgets-input-bg: #1f2937;
237
- --jamwidgets-input-text: #f9fafb;
238
- --jamwidgets-button-bg: #6366f1;
239
- --jamwidgets-button-hover: #818cf8;
240
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
241
- --jamwidgets-success-text: #86efac;
242
- --jamwidgets-success-border: rgba(34, 197, 94, 0.3);
243
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
244
- --jamwidgets-error-text: #fca5a5;
245
- --jamwidgets-error-border: rgba(239, 68, 68, 0.3);
246
- }
247
- }
248
-
249
207
  .jamwidgets-subscribe-input-group {
250
208
  display: flex;
251
209
  gap: 0.5rem;
@@ -263,13 +221,11 @@ import PoweredBy from "./PoweredBy.astro";
263
221
  }
264
222
 
265
223
  .jamwidgets-subscribe-input:focus {
266
- outline: none;
267
224
  border-color: var(--jamwidgets-button-bg);
268
- box-shadow: 0 0 0 3px var(--jamwidgets-focus-ring);
269
225
  }
270
226
 
271
227
  .jamwidgets-subscribe-input::placeholder {
272
- color: #9ca3af;
228
+ color: var(--jamwidgets-text-muted);
273
229
  }
274
230
 
275
231
  .jamwidgets-subscribe-button {
@@ -312,4 +268,15 @@ import PoweredBy from "./PoweredBy.astro";
312
268
  color: var(--jamwidgets-error-text);
313
269
  border: 1px solid var(--jamwidgets-error-border);
314
270
  }
271
+
272
+ @container (max-width: 25rem) {
273
+ .jamwidgets-subscribe-input-group {
274
+ flex-direction: column;
275
+ }
276
+
277
+ .jamwidgets-subscribe-button {
278
+ width: 100%;
279
+ }
280
+ }
281
+ }
315
282
  </style>
@@ -0,0 +1,129 @@
1
+ <style is:global>
2
+ @layer jamwidgets {
3
+ :where(
4
+ .jamwidgets-announcements,
5
+ .jamwidgets-comments,
6
+ .jamwidgets-feedback,
7
+ .jamwidgets-form,
8
+ .jamwidgets-poll,
9
+ .jamwidgets-reactions,
10
+ .jamwidgets-subscribe,
11
+ .jamwidgets-views,
12
+ .jamwidgets-waitlist
13
+ ) {
14
+ --jamwidgets-border-color: var(--jamwidgets-color-border, color-mix(in srgb, currentColor 22%, transparent));
15
+ --jamwidgets-bg-color: var(--jamwidgets-color-surface, transparent);
16
+ --jamwidgets-input-bg: var(--jamwidgets-color-surface, transparent);
17
+ --jamwidgets-input-text: var(--jamwidgets-color-text, currentColor);
18
+ --jamwidgets-input-border: var(--jamwidgets-color-border, color-mix(in srgb, currentColor 35%, transparent));
19
+ --jamwidgets-text-color: var(--jamwidgets-color-text, currentColor);
20
+ --jamwidgets-text-muted: var(--jamwidgets-color-muted, color-mix(in srgb, currentColor 62%, transparent));
21
+ --jamwidgets-icon-color: var(--jamwidgets-color-muted, color-mix(in srgb, currentColor 55%, transparent));
22
+ --jamwidgets-count-color: var(--jamwidgets-color-muted, color-mix(in srgb, currentColor 62%, transparent));
23
+ --jamwidgets-button-bg: var(--jamwidgets-color-accent, ButtonFace);
24
+ --jamwidgets-button-hover: var(--jamwidgets-color-accent-hover, color-mix(in srgb, var(--jamwidgets-color-accent, ButtonFace) 82%, black));
25
+ --jamwidgets-button-text: var(--jamwidgets-color-on-accent, ButtonText);
26
+ --jamwidgets-focus-color: var(--jamwidgets-color-accent, currentColor);
27
+ --jamwidgets-hover-bg: var(--jamwidgets-color-hover, color-mix(in srgb, currentColor 7%, transparent));
28
+ --jamwidgets-hover-border: var(--jamwidgets-color-border, color-mix(in srgb, currentColor 32%, transparent));
29
+ --jamwidgets-selected-bg: var(--jamwidgets-color-selected, color-mix(in srgb, var(--jamwidgets-color-accent, currentColor) 12%, transparent));
30
+ --jamwidgets-selected-border: var(--jamwidgets-color-accent, currentColor);
31
+ --jamwidgets-active-bg: var(--jamwidgets-color-selected, color-mix(in srgb, var(--jamwidgets-color-accent, currentColor) 12%, transparent));
32
+ --jamwidgets-active-border: var(--jamwidgets-color-accent, currentColor);
33
+ --jamwidgets-active-hover-bg: var(--jamwidgets-color-selected-hover, color-mix(in srgb, var(--jamwidgets-color-accent, currentColor) 18%, transparent));
34
+ --jamwidgets-active-text: var(--jamwidgets-color-accent, currentColor);
35
+ --jamwidgets-bar-bg: var(--jamwidgets-color-selected, color-mix(in srgb, var(--jamwidgets-color-accent, currentColor) 12%, transparent));
36
+ --jamwidgets-voted-bar-bg: var(--jamwidgets-color-accent, currentColor);
37
+ --jamwidgets-success-bg: var(--jamwidgets-color-success-surface, color-mix(in srgb, #16a34a 12%, transparent));
38
+ --jamwidgets-success-text: var(--jamwidgets-color-success, currentColor);
39
+ --jamwidgets-success-border: var(--jamwidgets-color-success-border, color-mix(in srgb, #16a34a 30%, transparent));
40
+ --jamwidgets-success-color: var(--jamwidgets-color-success, currentColor);
41
+ --jamwidgets-error-bg: var(--jamwidgets-color-error-surface, color-mix(in srgb, #dc2626 10%, transparent));
42
+ --jamwidgets-error-text: var(--jamwidgets-color-error, currentColor);
43
+ --jamwidgets-error-border: var(--jamwidgets-color-error-border, color-mix(in srgb, #dc2626 28%, transparent));
44
+ --jamwidgets-error-color: var(--jamwidgets-color-error, currentColor);
45
+ --jamwidgets-notice-bg: var(--jamwidgets-color-notice-surface, color-mix(in srgb, #ca8a04 12%, transparent));
46
+ --jamwidgets-notice-text: var(--jamwidgets-color-notice, currentColor);
47
+ color: var(--jamwidgets-text-color);
48
+ font-family: inherit;
49
+ font-size: var(--jamwidgets-font-size, inherit);
50
+ line-height: var(--jamwidgets-line-height, inherit);
51
+ }
52
+
53
+ :where(.jamwidgets-comments, .jamwidgets-feedback, .jamwidgets-subscribe, .jamwidgets-waitlist) {
54
+ container-type: inline-size;
55
+ }
56
+
57
+ :where(
58
+ .jamwidgets-announcements,
59
+ .jamwidgets-comments,
60
+ .jamwidgets-feedback,
61
+ .jamwidgets-form,
62
+ .jamwidgets-poll,
63
+ .jamwidgets-reactions,
64
+ .jamwidgets-subscribe,
65
+ .jamwidgets-views,
66
+ .jamwidgets-waitlist
67
+ ),
68
+ :where(.jamwidgets-comments, .jamwidgets-feedback, .jamwidgets-form, .jamwidgets-poll, .jamwidgets-reactions, .jamwidgets-subscribe, .jamwidgets-waitlist) :where(button, input, textarea, select) {
69
+ box-sizing: border-box;
70
+ }
71
+
72
+ :where(.jamwidgets-comments, .jamwidgets-feedback, .jamwidgets-form, .jamwidgets-poll, .jamwidgets-reactions, .jamwidgets-subscribe, .jamwidgets-waitlist) :where(button, input, textarea, select) {
73
+ font-family: inherit;
74
+ }
75
+
76
+ :where(.jamwidgets-theme-light, .jamwidgets-theme-auto) {
77
+ color-scheme: light;
78
+ --jamwidgets-color-text: #1f2937;
79
+ --jamwidgets-color-muted: #6b7280;
80
+ --jamwidgets-color-surface: #ffffff;
81
+ --jamwidgets-color-border: #d1d5db;
82
+ --jamwidgets-color-accent: #374151;
83
+ --jamwidgets-color-accent-hover: #111827;
84
+ --jamwidgets-color-on-accent: #ffffff;
85
+ --jamwidgets-color-success: #15803d;
86
+ --jamwidgets-color-error: #b91c1c;
87
+ --jamwidgets-color-notice: #854d0e;
88
+ --jamwidgets-color-info: #1d4ed8;
89
+ }
90
+
91
+ :where(.jamwidgets-theme-dark) {
92
+ color-scheme: dark;
93
+ --jamwidgets-color-text: #f3f4f6;
94
+ --jamwidgets-color-muted: #9ca3af;
95
+ --jamwidgets-color-surface: #1f2937;
96
+ --jamwidgets-color-border: #4b5563;
97
+ --jamwidgets-color-accent: #d1d5db;
98
+ --jamwidgets-color-accent-hover: #f9fafb;
99
+ --jamwidgets-color-on-accent: #111827;
100
+ --jamwidgets-color-success: #86efac;
101
+ --jamwidgets-color-error: #fca5a5;
102
+ --jamwidgets-color-notice: #fde68a;
103
+ --jamwidgets-color-info: #93c5fd;
104
+ }
105
+
106
+ @media (prefers-color-scheme: dark) {
107
+ :where(.jamwidgets-theme-auto) {
108
+ color-scheme: dark;
109
+ --jamwidgets-color-text: #f3f4f6;
110
+ --jamwidgets-color-muted: #9ca3af;
111
+ --jamwidgets-color-surface: #1f2937;
112
+ --jamwidgets-color-border: #4b5563;
113
+ --jamwidgets-color-accent: #d1d5db;
114
+ --jamwidgets-color-accent-hover: #f9fafb;
115
+ --jamwidgets-color-on-accent: #111827;
116
+ --jamwidgets-color-success: #86efac;
117
+ --jamwidgets-color-error: #fca5a5;
118
+ --jamwidgets-color-notice: #fde68a;
119
+ --jamwidgets-color-info: #93c5fd;
120
+ }
121
+ }
122
+
123
+ :where(.jamwidgets-comments, .jamwidgets-feedback, .jamwidgets-form, .jamwidgets-poll, .jamwidgets-reactions, .jamwidgets-subscribe, .jamwidgets-waitlist) :where(button, input, textarea, select):focus-visible,
124
+ :where(.jamwidgets-announcement-dismiss):focus-visible {
125
+ outline: 2px solid var(--jamwidgets-focus-color);
126
+ outline-offset: 2px;
127
+ }
128
+ }
129
+ </style>
package/src/Views.astro CHANGED
@@ -21,8 +21,8 @@ interface Props {
21
21
  endpoint?: string;
22
22
  /** Show unique visitor count alongside total views (default: false) */
23
23
  showUnique?: boolean;
24
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
25
- theme?: "light" | "dark" | "auto";
24
+ /** Theme preset. Defaults to inheriting the host page. */
25
+ theme?: "inherit" | "light" | "dark" | "auto";
26
26
  /** CSS class to add to the container */
27
27
  class?: string;
28
28
  }
@@ -35,14 +35,17 @@ const {
35
35
  pageId,
36
36
  endpoint = DEFAULT_ENDPOINT,
37
37
  showUnique = false,
38
- theme = "light",
38
+ theme = "inherit",
39
39
  class: className = "",
40
40
  } = Astro.props;
41
41
 
42
42
  // Build the API URL
43
43
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
44
+
45
+ import Theme from "./Theme.astro";
44
46
  ---
45
47
 
48
+ <Theme />
46
49
  <div
47
50
  class:list={["jamwidgets-views", `jamwidgets-theme-${theme}`, className]}
48
51
  data-jamwidgets-views
@@ -157,9 +160,6 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
157
160
  <style is:global>
158
161
  @layer jamwidgets {
159
162
  .jamwidgets-views {
160
- --jamwidgets-text-color: #6b7280;
161
- --jamwidgets-icon-color: #9ca3af;
162
-
163
163
  display: inline-flex;
164
164
  align-items: center;
165
165
  gap: 0.375rem;
@@ -167,20 +167,6 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
167
167
  color: var(--jamwidgets-text-color);
168
168
  }
169
169
 
170
- /* Dark theme */
171
- .jamwidgets-views.jamwidgets-theme-dark {
172
- --jamwidgets-text-color: #9ca3af;
173
- --jamwidgets-icon-color: #6b7280;
174
- }
175
-
176
- /* Auto theme - follows prefers-color-scheme */
177
- @media (prefers-color-scheme: dark) {
178
- .jamwidgets-views.jamwidgets-theme-auto {
179
- --jamwidgets-text-color: #9ca3af;
180
- --jamwidgets-icon-color: #6b7280;
181
- }
182
- }
183
-
184
170
  .jamwidgets-views-icon {
185
171
  display: flex;
186
172
  color: var(--jamwidgets-icon-color);
@@ -192,6 +178,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
192
178
  }
193
179
 
194
180
  .jamwidgets-views-count {
181
+ color: var(--jamwidgets-count-color);
195
182
  font-variant-numeric: tabular-nums;
196
183
  }
197
184
 
@@ -13,8 +13,8 @@ interface Props {
13
13
  siteKey: string;
14
14
  /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
15
15
  endpoint?: string;
16
- /** Theme preset: 'light' (default), 'dark', or 'auto' (uses prefers-color-scheme) */
17
- theme?: "light" | "dark" | "auto";
16
+ /** Theme preset. Defaults to inheriting the host page. */
17
+ theme?: "inherit" | "light" | "dark" | "auto";
18
18
  /** Placeholder text for the email input (default: 'Enter your email') */
19
19
  placeholder?: string;
20
20
  /** Button text (default: 'Join Waitlist') */
@@ -31,7 +31,7 @@ const API_PATH = "/api/v1";
31
31
  const {
32
32
  siteKey,
33
33
  endpoint = DEFAULT_ENDPOINT,
34
- theme = "light",
34
+ theme = "inherit",
35
35
  placeholder = "Enter your email",
36
36
  buttonText = "Join Waitlist",
37
37
  successMessage = "You're on the list!",
@@ -42,8 +42,10 @@ const {
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
43
 
44
44
  import PoweredBy from "./PoweredBy.astro";
45
+ import Theme from "./Theme.astro";
45
46
  ---
46
47
 
48
+ <Theme />
47
49
  <div
48
50
  class:list={["jamwidgets-waitlist", `jamwidgets-theme-${theme}`, className]}
49
51
  data-jamwidgets-waitlist
@@ -150,45 +152,11 @@ import PoweredBy from "./PoweredBy.astro";
150
152
  </script>
151
153
 
152
154
  <style is:global>
155
+ @layer jamwidgets {
153
156
  .jamwidgets-waitlist {
154
- --jamwidgets-border-color: #9ca3af;
155
- --jamwidgets-bg-color: white;
156
- --jamwidgets-text-color: inherit;
157
- --jamwidgets-text-muted: #6b7280;
158
- --jamwidgets-button-bg: #3b82f6;
159
- --jamwidgets-button-text: white;
160
- --jamwidgets-button-hover: #2563eb;
161
- --jamwidgets-success-color: #16a34a;
162
- --jamwidgets-success-bg: #dcfce7;
163
- --jamwidgets-error-color: #dc2626;
164
- --jamwidgets-error-bg: #fef2f2;
165
157
  font-family: inherit;
166
158
  }
167
159
 
168
- .jamwidgets-waitlist.jamwidgets-theme-dark {
169
- --jamwidgets-border-color: #9ca3af;
170
- --jamwidgets-bg-color: #374151;
171
- --jamwidgets-text-color: #f3f4f6;
172
- --jamwidgets-text-muted: #9ca3af;
173
- --jamwidgets-success-color: #22c55e;
174
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
175
- --jamwidgets-error-color: #ef4444;
176
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
177
- }
178
-
179
- @media (prefers-color-scheme: dark) {
180
- .jamwidgets-waitlist.jamwidgets-theme-auto {
181
- --jamwidgets-border-color: #9ca3af;
182
- --jamwidgets-bg-color: #374151;
183
- --jamwidgets-text-color: #f3f4f6;
184
- --jamwidgets-text-muted: #9ca3af;
185
- --jamwidgets-success-color: #22c55e;
186
- --jamwidgets-success-bg: rgba(34, 197, 94, 0.15);
187
- --jamwidgets-error-color: #ef4444;
188
- --jamwidgets-error-bg: rgba(239, 68, 68, 0.15);
189
- }
190
- }
191
-
192
160
  .jamwidgets-waitlist-form {
193
161
  display: flex;
194
162
  gap: 0.5rem;
@@ -207,9 +175,7 @@ import PoweredBy from "./PoweredBy.astro";
207
175
  }
208
176
 
209
177
  .jamwidgets-waitlist .jamwidgets-waitlist-input:focus {
210
- outline: none;
211
178
  border-color: var(--jamwidgets-button-bg);
212
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
213
179
  }
214
180
 
215
181
  .jamwidgets-waitlist .jamwidgets-waitlist-input::placeholder {
@@ -255,7 +221,7 @@ import PoweredBy from "./PoweredBy.astro";
255
221
  color: var(--jamwidgets-error-color);
256
222
  }
257
223
 
258
- @media (max-width: 400px) {
224
+ @container (max-width: 25rem) {
259
225
  .jamwidgets-waitlist-form {
260
226
  flex-direction: column;
261
227
  }
@@ -263,4 +229,5 @@ import PoweredBy from "./PoweredBy.astro";
263
229
  width: 100%;
264
230
  }
265
231
  }
232
+ }
266
233
  </style>