@usequeek/theme-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +37 -0
  2. package/api/collections.ts +93 -0
  3. package/api/orders.ts +23 -0
  4. package/apps/analytics-events.ts +32 -0
  5. package/apps/apps.css +613 -0
  6. package/apps/follow-card.tsx +192 -0
  7. package/apps/gtag.ts +19 -0
  8. package/apps/subscribe-form.tsx +124 -0
  9. package/client.ts +52 -0
  10. package/components/auth/auth-email-login-step.tsx +46 -0
  11. package/components/auth/auth-email-otp-step.tsx +54 -0
  12. package/components/auth/auth-email-otp-verify-step.tsx +82 -0
  13. package/components/auth/auth-email-register-step.tsx +59 -0
  14. package/components/auth/auth-flow.tsx +26 -0
  15. package/components/auth/auth-signup-step.tsx +53 -0
  16. package/components/blog/category-filter.tsx +92 -0
  17. package/components/blog/index.ts +12 -0
  18. package/components/blog/pagination.tsx +73 -0
  19. package/components/blog/post-categories.tsx +42 -0
  20. package/components/blog/post-meta.tsx +49 -0
  21. package/components/blog/related-posts.tsx +54 -0
  22. package/components/blog/share-buttons.tsx +89 -0
  23. package/components/cart/cart-panel-controller.tsx +67 -0
  24. package/components/cart/default-cart-shell.tsx +207 -0
  25. package/components/checkout/checkout-contact.tsx +84 -0
  26. package/components/checkout/default-checkout-shell.tsx +177 -0
  27. package/components/checkout/policy-links.tsx +98 -0
  28. package/components/image.tsx +149 -0
  29. package/components/markdown.tsx +54 -0
  30. package/components/product-media-frame.tsx +56 -0
  31. package/components/variant-picker.css +84 -0
  32. package/components/variant-picker.tsx +107 -0
  33. package/design-token-preview.tsx +81 -0
  34. package/edit-mode-active.tsx +324 -0
  35. package/edit-mode.tsx +80 -0
  36. package/hooks/use-address-autocomplete.ts +62 -0
  37. package/hooks/use-auth-flow.tsx +346 -0
  38. package/hooks/use-auth.ts +17 -0
  39. package/hooks/use-cart.ts +14 -0
  40. package/hooks/use-categories.ts +112 -0
  41. package/hooks/use-collection-params.ts +67 -0
  42. package/hooks/use-href.ts +15 -0
  43. package/hooks/use-posts.ts +75 -0
  44. package/hooks/use-product-detail.ts +69 -0
  45. package/hooks/use-product-questions.ts +77 -0
  46. package/hooks/use-product-sections.ts +165 -0
  47. package/hooks/use-products.ts +216 -0
  48. package/hooks/use-related-products.ts +56 -0
  49. package/hooks/use-resolve-coordinates.ts +48 -0
  50. package/hooks/use-reviews.ts +167 -0
  51. package/hooks/use-search.ts +55 -0
  52. package/hooks/use-shop-cart.ts +31 -0
  53. package/hooks/use-shop-params.ts +68 -0
  54. package/hooks/use-shop.ts +186 -0
  55. package/hooks/use-variant-selection.ts +98 -0
  56. package/index.ts +49 -0
  57. package/package.json +41 -0
  58. package/page-renderer.tsx +365 -0
  59. package/provider.tsx +91 -0
  60. package/sdk/client.ts +221 -0
  61. package/sdk/error-handler.ts +37 -0
  62. package/sdk/platform.ts +78 -0
  63. package/sdk/queek-client.ts +104 -0
  64. package/sdk/reauth-guard.ts +27 -0
  65. package/sdk/server-store-client.ts +165 -0
  66. package/shared-blocks/button.tsx +41 -0
  67. package/shared-blocks/callout.tsx +35 -0
  68. package/shared-blocks/content-default.tsx +23 -0
  69. package/shared-blocks/core-blocks.css +1792 -0
  70. package/shared-blocks/divider.tsx +17 -0
  71. package/shared-blocks/embed.tsx +40 -0
  72. package/shared-blocks/faq.tsx +44 -0
  73. package/shared-blocks/image.tsx +29 -0
  74. package/shared-blocks/index.ts +24 -0
  75. package/shared-blocks/manifest.ts +76 -0
  76. package/shared-blocks/product-qa.tsx +203 -0
  77. package/shared-blocks/quote.tsx +20 -0
  78. package/shared-blocks/reviews.tsx +436 -0
  79. package/shared-blocks/table.tsx +45 -0
  80. package/shared-blocks/video.tsx +60 -0
  81. package/stores/address-modal-store.ts +15 -0
  82. package/stores/auth-modal-store.ts +17 -0
  83. package/stores/cart-panel-store.ts +17 -0
  84. package/stores/cart-store.ts +172 -0
  85. package/stores/checkout-store.ts +167 -0
  86. package/stores/edit-preview-store.ts +61 -0
  87. package/stores/location-store.ts +41 -0
  88. package/stores/orders-sheet-store.ts +21 -0
  89. package/stores/policy-modal-store.ts +15 -0
  90. package/stores/product-modal-store.ts +18 -0
  91. package/stores/search-modal-store.ts +17 -0
  92. package/stores/toast-store.ts +46 -0
  93. package/stores/user-store.ts +88 -0
  94. package/theme-context.tsx +28 -0
  95. package/theme-variant-resolver.ts +44 -0
  96. package/types/block.ts +292 -0
  97. package/types/cart.ts +21 -0
  98. package/types/media.ts +55 -0
  99. package/types/menu.ts +27 -0
  100. package/types/order.ts +119 -0
  101. package/types/page.ts +86 -0
  102. package/types/product.ts +279 -0
  103. package/types/theme.ts +380 -0
  104. package/types/vendor.ts +243 -0
  105. package/utils/addon-selection.ts +31 -0
  106. package/utils/benefit-icon.tsx +145 -0
  107. package/utils/brand-palette.ts +64 -0
  108. package/utils/brand.ts +518 -0
  109. package/utils/contact-location.ts +44 -0
  110. package/utils/format.ts +64 -0
  111. package/utils/hero-content.ts +35 -0
  112. package/utils/image-srcset.ts +157 -0
  113. package/utils/markdown.tsx +237 -0
  114. package/utils/match-variant.ts +31 -0
  115. package/utils/menu-link.ts +41 -0
  116. package/utils/placeholder-resolver.ts +184 -0
  117. package/utils/product-media.ts +77 -0
  118. package/utils/product-normalizer.ts +323 -0
  119. package/utils/vendor-host.ts +173 -0
  120. package/utils/video-embed.ts +38 -0
package/apps/apps.css ADDED
@@ -0,0 +1,613 @@
1
+ /* ── Storefront Apps — Core-owned components ─────────────── */
2
+ /* Themes override via `.theme-X .core-*` scoped selectors. */
3
+
4
+ /* ── WhatsApp Chat ───────────────────────────────────────── */
5
+ .core-whatsapp-chat {
6
+ position: fixed;
7
+ z-index: 200;
8
+ width: 56px;
9
+ height: 56px;
10
+ border-radius: 50%;
11
+ background: #25D366;
12
+ color: #fff;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ box-shadow: 0 4px 12px rgba(37, 211, 102, 0.4);
17
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .core-whatsapp-chat:hover {
22
+ transform: scale(1.08);
23
+ box-shadow: 0 6px 20px rgba(37, 211, 102, 0.5);
24
+ }
25
+
26
+ /* WhatsApp positions — bottom offset is set via inline style */
27
+ .core-whatsapp-chat--bottom-right { right: 1.25rem; }
28
+ .core-whatsapp-chat--bottom-left { left: 1.25rem; }
29
+ .core-whatsapp-chat--center-right { top: 50%; right: 1.25rem; transform: translateY(-50%); }
30
+ .core-whatsapp-chat--center-left { top: 50%; left: 1.25rem; transform: translateY(-50%); }
31
+ .core-whatsapp-chat--center-right:hover,
32
+ .core-whatsapp-chat--center-left:hover { transform: translateY(-50%) scale(1.08); }
33
+
34
+ /* ── Follow Card ─────────────────────────────────────────── */
35
+ .core-follow-card {
36
+ font-family: var(--font-body, system-ui, sans-serif);
37
+ color: var(--brand-text, #1a1a1a);
38
+ }
39
+
40
+ .core-follow-card--floating {
41
+ position: fixed;
42
+ z-index: 190;
43
+ width: 280px;
44
+ background: var(--brand-background, #fff);
45
+ border: 1px solid var(--brand-border, rgba(0,0,0,0.1));
46
+ border-radius: 12px;
47
+ padding: 1rem;
48
+ box-shadow: 0 8px 30px rgba(0,0,0,0.12);
49
+ }
50
+
51
+ /* Follow Card positions */
52
+ .core-follow-card--bottom-right { bottom: 5.5rem; right: 1.25rem; }
53
+ .core-follow-card--bottom-left { bottom: 5.5rem; left: 1.25rem; }
54
+ .core-follow-card--center-right { top: 50%; right: 1.25rem; transform: translateY(-50%); }
55
+ .core-follow-card--center-left { top: 50%; left: 1.25rem; transform: translateY(-50%); }
56
+
57
+ .core-follow-card--inline {
58
+ background: var(--brand-surface, #f5f5f5);
59
+ border-radius: 8px;
60
+ padding: 1.25rem;
61
+ max-width: 400px;
62
+ }
63
+
64
+ .core-follow-card__dismiss {
65
+ position: absolute;
66
+ top: 0.5rem;
67
+ right: 0.5rem;
68
+ width: 24px;
69
+ height: 24px;
70
+ border: 0;
71
+ background: transparent;
72
+ color: var(--brand-text-muted, #888);
73
+ cursor: pointer;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ border-radius: 50%;
78
+ transition: background 0.15s;
79
+ }
80
+
81
+ .core-follow-card__dismiss:hover {
82
+ background: var(--brand-surface, rgba(0,0,0,0.05));
83
+ }
84
+
85
+ .core-follow-card__body {
86
+ display: flex;
87
+ align-items: flex-start;
88
+ gap: 0.75rem;
89
+ margin-bottom: 0.75rem;
90
+ }
91
+
92
+ .core-follow-card__logo {
93
+ width: 36px;
94
+ height: 36px;
95
+ border-radius: 50%;
96
+ object-fit: cover;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .core-follow-card__text {
101
+ flex: 1;
102
+ min-width: 0;
103
+ }
104
+
105
+ .core-follow-card__title {
106
+ font-size: 0.85rem;
107
+ font-weight: 600;
108
+ margin: 0 0 0.15rem;
109
+ line-height: 1.3;
110
+ }
111
+
112
+ .core-follow-card__sub {
113
+ font-size: 0.75rem;
114
+ color: var(--brand-text-muted, #666);
115
+ margin: 0 0 0.25rem;
116
+ line-height: 1.4;
117
+ }
118
+
119
+ .core-follow-card__count {
120
+ font-size: 0.7rem;
121
+ color: var(--brand-text-muted, #999);
122
+ margin: 0;
123
+ }
124
+
125
+ .core-follow-card__btn {
126
+ width: 100%;
127
+ padding: 0.5rem 1rem;
128
+ border: 0;
129
+ border-radius: 6px;
130
+ font-size: 0.8rem;
131
+ font-weight: 600;
132
+ cursor: pointer;
133
+ transition: background 0.15s, transform 0.1s;
134
+ background: var(--brand-button, var(--brand-accent, #000));
135
+ color: var(--brand-button-text, #fff);
136
+ }
137
+
138
+ .core-follow-card__btn:hover {
139
+ opacity: 0.9;
140
+ transform: translateY(-1px);
141
+ }
142
+
143
+ .core-follow-card__btn:disabled {
144
+ opacity: 0.6;
145
+ cursor: not-allowed;
146
+ transform: none;
147
+ }
148
+
149
+ .core-follow-card__consent {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 0.5rem;
153
+ margin-bottom: 0.5rem;
154
+ font-size: 0.7rem;
155
+ color: var(--brand-text-muted, #666);
156
+ cursor: pointer;
157
+ line-height: 1.3;
158
+ }
159
+
160
+ .core-follow-card__checkbox {
161
+ width: 14px;
162
+ height: 14px;
163
+ border-radius: 3px;
164
+ accent-color: var(--brand-accent, #000);
165
+ cursor: pointer;
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ @media (max-width: 600px) {
170
+ .core-follow-card--floating {
171
+ left: 1rem;
172
+ right: 1rem;
173
+ width: auto;
174
+ bottom: 5rem;
175
+ }
176
+ /* Reset position overrides on mobile — always full-width bottom */
177
+ .core-follow-card--bottom-left,
178
+ .core-follow-card--center-right,
179
+ .core-follow-card--center-left {
180
+ left: 1rem;
181
+ right: 1rem;
182
+ width: auto;
183
+ bottom: 5rem;
184
+ top: auto;
185
+ transform: none;
186
+ }
187
+
188
+ .core-whatsapp-chat {
189
+ width: 48px;
190
+ height: 48px;
191
+ }
192
+ /* Reset WhatsApp position on mobile — always bottom */
193
+ .core-whatsapp-chat--bottom-right,
194
+ .core-whatsapp-chat--bottom-left { bottom: 1rem; }
195
+ .core-whatsapp-chat--bottom-left { left: 1rem; right: auto; }
196
+ .core-whatsapp-chat--center-right,
197
+ .core-whatsapp-chat--center-left {
198
+ top: auto;
199
+ bottom: 1rem;
200
+ transform: none;
201
+ }
202
+ .core-whatsapp-chat--center-right { right: 1rem; }
203
+ .core-whatsapp-chat--center-left { left: 1rem; right: auto; }
204
+ }
205
+
206
+ /* ── Subscribe form (newsletter capture) ─────────────────── */
207
+ .core-subscribe {
208
+ font-family: var(--font-body, system-ui, sans-serif);
209
+ color: var(--brand-text, #1a1a1a);
210
+ }
211
+
212
+ .core-subscribe__heading {
213
+ font-family: var(--font-heading, var(--font-body, system-ui, sans-serif));
214
+ font-size: 1rem;
215
+ font-weight: 600;
216
+ margin: 0 0 0.35rem;
217
+ }
218
+
219
+ .core-subscribe__tagline {
220
+ font-size: 0.875rem;
221
+ opacity: 0.75;
222
+ margin: 0 0 0.85rem;
223
+ line-height: 1.5;
224
+ }
225
+
226
+ .core-subscribe__label {
227
+ position: absolute;
228
+ width: 1px;
229
+ height: 1px;
230
+ overflow: hidden;
231
+ clip: rect(0 0 0 0);
232
+ white-space: nowrap;
233
+ }
234
+
235
+ .core-subscribe__form {
236
+ display: flex;
237
+ gap: 0.5rem;
238
+ flex-wrap: wrap;
239
+ }
240
+
241
+ .core-subscribe--stacked .core-subscribe__form { flex-direction: column; }
242
+
243
+ .core-subscribe__input {
244
+ flex: 1 1 12rem;
245
+ min-width: 0;
246
+ padding: 0.7rem 0.85rem;
247
+ font: inherit;
248
+ font-size: 0.9375rem;
249
+ color: inherit;
250
+ background: var(--brand-background, #fff);
251
+ border: 1px solid var(--brand-border, rgba(0, 0, 0, 0.18));
252
+ border-radius: var(--shape-radius, 8px);
253
+ }
254
+
255
+ /* In --stacked (flex-direction: column), the main axis is vertical, so the
256
+ row layout's `flex: 1 1 12rem` above would set a 12rem *height* basis
257
+ instead of width — inflating the input into a tall box. Reset to natural
258
+ content height and span the column via width instead. */
259
+ .core-subscribe--stacked .core-subscribe__input,
260
+ .core-subscribe--stacked .core-subscribe__button {
261
+ flex: 0 0 auto;
262
+ width: 100%;
263
+ }
264
+
265
+ .core-subscribe__input:focus-visible {
266
+ outline: 2px solid var(--brand-primary, #00a884);
267
+ outline-offset: 1px;
268
+ }
269
+
270
+ .core-subscribe__button {
271
+ padding: 0.7rem 1.25rem;
272
+ font: inherit;
273
+ font-size: 0.9375rem;
274
+ font-weight: 600;
275
+ cursor: pointer;
276
+ color: var(--brand-on-primary, #fff);
277
+ background: var(--brand-primary, #00a884);
278
+ border: 1px solid transparent;
279
+ border-radius: var(--shape-radius, 8px);
280
+ transition: opacity 0.2s ease;
281
+ }
282
+
283
+ .core-subscribe__button:hover:not(:disabled) { opacity: 0.88; }
284
+ .core-subscribe__button:disabled { opacity: 0.6; cursor: default; }
285
+
286
+ .core-subscribe__error {
287
+ margin: 0.6rem 0 0;
288
+ font-size: 0.8125rem;
289
+ color: var(--brand-danger, #c0392b);
290
+ }
291
+
292
+ .core-subscribe__done {
293
+ margin: 0;
294
+ font-size: 0.9375rem;
295
+ line-height: 1.5;
296
+ }
297
+
298
+ /* ── Subscribe — inline variant (generic, above the footer) ─ */
299
+ .core-subscribe-section {
300
+ max-width: 1200px;
301
+ margin: 0 auto;
302
+ padding: 3rem 1.25rem;
303
+ }
304
+
305
+ /* ── Subscribe — floating variant ────────────────────────── */
306
+ /* Defaults to bottom-left — WhatsApp chat + FollowCard already stack in the
307
+ bottom-right corner by default, so this avoids a default collision. */
308
+ .core-subscribe-floating {
309
+ position: fixed;
310
+ z-index: 190;
311
+ left: 1.25rem;
312
+ bottom: 5.5rem;
313
+ width: 280px;
314
+ background: var(--brand-background, #fff);
315
+ border: 1px solid var(--brand-border, rgba(0, 0, 0, 0.1));
316
+ border-radius: 12px;
317
+ padding: 1rem;
318
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
319
+ }
320
+
321
+ .core-subscribe-floating__dismiss {
322
+ position: absolute;
323
+ top: 0.5rem;
324
+ right: 0.5rem;
325
+ width: 24px;
326
+ height: 24px;
327
+ border: 0;
328
+ background: transparent;
329
+ color: var(--brand-text-muted, #888);
330
+ cursor: pointer;
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ border-radius: 50%;
335
+ transition: background 0.15s;
336
+ }
337
+
338
+ .core-subscribe-floating__dismiss:hover {
339
+ background: var(--brand-surface, rgba(0, 0, 0, 0.05));
340
+ }
341
+
342
+ @media (max-width: 600px) {
343
+ .core-subscribe-floating {
344
+ left: 1rem;
345
+ right: 1rem;
346
+ width: auto;
347
+ bottom: 5rem;
348
+ }
349
+ }
350
+
351
+ /* ── Subscribe — modal variant ───────────────────────────── */
352
+ /* Above the floating widgets (190/200) so it isn't hidden behind them while
353
+ open, below the cookie-consent banner (300) — a legal notice should never
354
+ be obscured by a marketing popup. */
355
+ .core-subscribe-modal__backdrop {
356
+ position: fixed;
357
+ inset: 0;
358
+ z-index: 250;
359
+ background: rgba(0, 0, 0, 0.5);
360
+ display: flex;
361
+ align-items: center;
362
+ justify-content: center;
363
+ padding: 1.25rem;
364
+ animation: core-subscribe-modal-fade 0.2s ease;
365
+ }
366
+
367
+ .core-subscribe-modal {
368
+ position: relative;
369
+ width: 100%;
370
+ max-width: 400px;
371
+ background: var(--brand-background, #fff);
372
+ border-radius: var(--radius-lg, 12px);
373
+ padding: 2.5rem 2rem 2rem;
374
+ box-shadow: var(--shadow-3, 0 20px 60px rgba(0, 0, 0, 0.25));
375
+ animation: core-subscribe-modal-pop 0.25s ease;
376
+ }
377
+
378
+ .core-subscribe-modal:focus {
379
+ outline: none;
380
+ }
381
+
382
+ /* The shared .core-subscribe__* rules are sized for an ambient footer strip
383
+ or a small corner card — too quiet for a modal, whose entire job is to be
384
+ the visual focus of the screen while it's open. Scoped overrides here
385
+ instead of changing the shared rules, so inline/floating keep their
386
+ original, already-appropriate scale. */
387
+ .core-subscribe-modal .core-subscribe__heading {
388
+ font-size: 1.5rem;
389
+ font-weight: 700;
390
+ line-height: 1.2;
391
+ letter-spacing: -0.01em;
392
+ margin: 0 0 0.5rem;
393
+ }
394
+
395
+ .core-subscribe-modal .core-subscribe__tagline {
396
+ font-size: 0.95rem;
397
+ line-height: 1.5;
398
+ margin: 0 0 1.5rem;
399
+ }
400
+
401
+ .core-subscribe-modal .core-subscribe__form {
402
+ gap: 0.65rem;
403
+ }
404
+
405
+ .core-subscribe-modal .core-subscribe__input {
406
+ padding: 0.85rem 1rem;
407
+ }
408
+
409
+ .core-subscribe-modal .core-subscribe__button {
410
+ padding: 0.9rem 1.25rem;
411
+ font-size: 1rem;
412
+ margin-top: 0.25rem;
413
+ }
414
+
415
+ .core-subscribe-modal__dismiss {
416
+ position: absolute;
417
+ top: 0.75rem;
418
+ right: 0.75rem;
419
+ width: 28px;
420
+ height: 28px;
421
+ border: 0;
422
+ background: transparent;
423
+ color: var(--brand-text-muted, #888);
424
+ cursor: pointer;
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: center;
428
+ border-radius: 50%;
429
+ transition: background 0.15s;
430
+ }
431
+
432
+ .core-subscribe-modal__dismiss:hover {
433
+ background: var(--brand-surface, rgba(0, 0, 0, 0.05));
434
+ }
435
+
436
+ @keyframes core-subscribe-modal-fade {
437
+ from { opacity: 0; }
438
+ to { opacity: 1; }
439
+ }
440
+
441
+ @keyframes core-subscribe-modal-pop {
442
+ from { opacity: 0; transform: scale(0.96) translateY(8px); }
443
+ to { opacity: 1; transform: scale(1) translateY(0); }
444
+ }
445
+
446
+ @media (max-width: 600px) {
447
+ .core-subscribe-modal {
448
+ max-width: none;
449
+ padding: 1.75rem 1.5rem 1.5rem;
450
+ }
451
+ }
452
+
453
+ /* ── Cookie Consent ──────────────────────────────────────── */
454
+ .core-consent-banner {
455
+ position: fixed;
456
+ z-index: 300;
457
+ left: 0;
458
+ right: 0;
459
+ bottom: 0;
460
+ display: flex;
461
+ flex-wrap: wrap;
462
+ align-items: center;
463
+ justify-content: space-between;
464
+ gap: 1rem;
465
+ padding: 1rem 1.25rem;
466
+ background: var(--brand-background, #fff);
467
+ border-top: 1px solid var(--brand-border, rgba(0, 0, 0, 0.1));
468
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
469
+ font-family: var(--font-body, system-ui, sans-serif);
470
+ color: var(--brand-text, #1a1a1a);
471
+ }
472
+
473
+ .core-consent-banner__text {
474
+ flex: 1 1 20rem;
475
+ margin: 0;
476
+ font-size: 0.8125rem;
477
+ line-height: 1.5;
478
+ opacity: 0.85;
479
+ }
480
+
481
+ .core-consent-banner__link {
482
+ color: inherit;
483
+ text-decoration: underline;
484
+ }
485
+
486
+ .core-consent-banner__actions {
487
+ display: flex;
488
+ gap: 0.5rem;
489
+ flex-shrink: 0;
490
+ }
491
+
492
+ .core-consent-banner__decline,
493
+ .core-consent-banner__accept {
494
+ padding: 0.55rem 1.1rem;
495
+ font: inherit;
496
+ font-size: 0.8125rem;
497
+ font-weight: 600;
498
+ cursor: pointer;
499
+ border-radius: var(--shape-radius, 8px);
500
+ border: 1px solid transparent;
501
+ transition: opacity 0.15s ease;
502
+ }
503
+
504
+ .core-consent-banner__decline {
505
+ background: transparent;
506
+ border-color: var(--brand-border, rgba(0, 0, 0, 0.18));
507
+ color: inherit;
508
+ }
509
+
510
+ .core-consent-banner__accept {
511
+ background: var(--brand-primary, #00a884);
512
+ color: var(--brand-on-primary, #fff);
513
+ }
514
+
515
+ .core-consent-banner__decline:hover,
516
+ .core-consent-banner__accept:hover { opacity: 0.85; }
517
+
518
+ .core-consent-settings-link {
519
+ position: fixed;
520
+ z-index: 150;
521
+ left: 0.75rem;
522
+ bottom: 0.5rem;
523
+ padding: 0.25rem 0.5rem;
524
+ font: inherit;
525
+ font-size: 0.6875rem;
526
+ color: var(--brand-text-muted, #888);
527
+ background: transparent;
528
+ border: 0;
529
+ cursor: pointer;
530
+ opacity: 0.6;
531
+ text-decoration: underline;
532
+ }
533
+
534
+ .core-consent-settings-link:hover { opacity: 1; }
535
+
536
+ @media (max-width: 600px) {
537
+ .core-consent-banner {
538
+ flex-direction: column;
539
+ align-items: stretch;
540
+ }
541
+ .core-consent-banner__actions {
542
+ justify-content: flex-end;
543
+ }
544
+ }
545
+
546
+ /* ── Unsubscribe page ────────────────────────────────────── */
547
+ .core-unsub {
548
+ display: flex;
549
+ align-items: center;
550
+ justify-content: center;
551
+ min-height: 60vh;
552
+ padding: 3rem 1.25rem;
553
+ font-family: var(--font-body, system-ui, sans-serif);
554
+ color: var(--brand-text, #1a1a1a);
555
+ }
556
+
557
+ .core-unsub__card {
558
+ width: 100%;
559
+ max-width: 30rem;
560
+ text-align: center;
561
+ padding: 2.25rem 1.75rem;
562
+ background: var(--brand-background, #fff);
563
+ border: 1px solid var(--brand-border, rgba(0, 0, 0, 0.1));
564
+ border-radius: calc(var(--shape-radius, 8px) * 1.5);
565
+ }
566
+
567
+ .core-unsub__title {
568
+ font-family: var(--font-heading, var(--font-body, system-ui, sans-serif));
569
+ font-size: 1.375rem;
570
+ font-weight: 600;
571
+ margin: 0 0 0.75rem;
572
+ }
573
+
574
+ .core-unsub__body {
575
+ font-size: 0.9375rem;
576
+ line-height: 1.6;
577
+ opacity: 0.85;
578
+ margin: 0 0 1.5rem;
579
+ }
580
+
581
+ .core-unsub__button {
582
+ display: inline-block;
583
+ padding: 0.7rem 1.5rem;
584
+ font: inherit;
585
+ font-size: 0.9375rem;
586
+ font-weight: 600;
587
+ cursor: pointer;
588
+ color: var(--brand-on-primary, #fff);
589
+ background: var(--brand-primary, #00a884);
590
+ border: 1px solid transparent;
591
+ border-radius: var(--shape-radius, 8px);
592
+ transition: opacity 0.2s ease;
593
+ }
594
+
595
+ .core-unsub__button:hover:not(:disabled) { opacity: 0.88; }
596
+ .core-unsub__button:disabled { opacity: 0.6; cursor: default; }
597
+
598
+ .core-unsub__muted {
599
+ margin: 0.9rem 0 0;
600
+ font-size: 0.8125rem;
601
+ opacity: 0.65;
602
+ }
603
+
604
+ .core-unsub__link {
605
+ display: inline-block;
606
+ margin-top: 1.35rem;
607
+ font-size: 0.875rem;
608
+ color: inherit;
609
+ opacity: 0.7;
610
+ text-decoration: underline;
611
+ }
612
+
613
+ .core-unsub__link:hover { opacity: 1; }