@o2vend/theme-cli 1.0.37 → 1.0.38

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 (89) hide show
  1. package/lib/lib/dev-server.js +309 -40
  2. package/lib/lib/liquid-engine.js +3 -1
  3. package/lib/lib/mock-data.js +36 -124
  4. package/lib/lib/widget-service.js +12 -4
  5. package/package.json +1 -1
  6. package/test-theme/assets/async-sections.js +32 -24
  7. package/test-theme/assets/cart-drawer.js +20 -22
  8. package/test-theme/assets/cart-manager.js +1 -15
  9. package/test-theme/assets/checkout-price-handler.js +12 -11
  10. package/test-theme/assets/checkout.css +1415 -0
  11. package/test-theme/assets/checkout.js +3174 -0
  12. package/test-theme/assets/components.css +178 -29
  13. package/test-theme/assets/delivery-zone.js +1 -1
  14. package/test-theme/assets/product-detail.css +1050 -0
  15. package/test-theme/assets/product-detail.js +2940 -0
  16. package/test-theme/assets/theme.css +95 -120
  17. package/test-theme/assets/theme.js +781 -186
  18. package/test-theme/layout/theme.liquid +91 -17
  19. package/test-theme/sections/content.liquid +64 -57
  20. package/test-theme/sections/footer-fallback.liquid +57 -7
  21. package/test-theme/sections/footer.liquid +63 -12
  22. package/test-theme/sections/header-fallback.liquid +41 -41
  23. package/test-theme/sections/header.liquid +41 -51
  24. package/test-theme/sections/hero-fallback.liquid +1 -1
  25. package/test-theme/sections/hero.liquid +159 -136
  26. package/test-theme/snippets/account-sidebar.liquid +121 -29
  27. package/test-theme/snippets/add-to-cart-modal.liquid +258 -206
  28. package/test-theme/snippets/breadcrumbs.liquid +98 -11
  29. package/test-theme/snippets/cart-drawer.liquid +93 -0
  30. package/test-theme/snippets/delivery-zone-city-selector.liquid +101 -15
  31. package/test-theme/snippets/delivery-zone-modal.liquid +529 -84
  32. package/test-theme/snippets/delivery-zone-search.liquid +104 -18
  33. package/test-theme/snippets/login-modal.liquid +269 -82
  34. package/test-theme/snippets/mega-menu.liquid +130 -43
  35. package/test-theme/snippets/news-thumbnail.liquid +120 -28
  36. package/test-theme/snippets/pagination.liquid +1 -1
  37. package/test-theme/snippets/price.liquid +100 -9
  38. package/test-theme/snippets/product-card-related.liquid +22 -4
  39. package/test-theme/snippets/product-card-simple.liquid +521 -25
  40. package/test-theme/snippets/product-card.liquid +145 -232
  41. package/test-theme/snippets/rating.liquid +100 -9
  42. package/test-theme/snippets/skeleton-collection-grid.liquid +94 -8
  43. package/test-theme/snippets/skeleton-product-card.liquid +102 -16
  44. package/test-theme/snippets/skeleton-product-grid.liquid +87 -1
  45. package/test-theme/snippets/social-sharing.liquid +133 -32
  46. package/test-theme/templates/account/dashboard.liquid +30 -0
  47. package/test-theme/templates/account/loyalty-redemption.liquid +29 -28
  48. package/test-theme/templates/account/loyalty.liquid +45 -43
  49. package/test-theme/templates/account/order-detail.liquid +15 -8
  50. package/test-theme/templates/account/orders.liquid +189 -35
  51. package/test-theme/templates/account/profile.liquid +509 -114
  52. package/test-theme/templates/account/register.liquid +18 -8
  53. package/test-theme/templates/account/return-orders.liquid +31 -30
  54. package/test-theme/templates/account/store-credit.liquid +27 -26
  55. package/test-theme/templates/account/subscriptions.liquid +22 -5
  56. package/test-theme/templates/account/wishlist.liquid +88 -19
  57. package/test-theme/templates/address-book.liquid +166 -69
  58. package/test-theme/templates/categories.liquid +90 -30
  59. package/test-theme/templates/checkout.liquid +137 -3834
  60. package/test-theme/templates/error.liquid +23 -21
  61. package/test-theme/templates/index.liquid +29 -0
  62. package/test-theme/templates/login.liquid +33 -6
  63. package/test-theme/templates/order-confirmation.liquid +67 -9
  64. package/test-theme/templates/page.liquid +418 -206
  65. package/test-theme/templates/product-detail.liquid +124 -3878
  66. package/test-theme/templates/products.liquid +155 -30
  67. package/test-theme/templates/search.liquid +739 -225
  68. package/test-theme/widgets/brand-carousel.liquid +102 -82
  69. package/test-theme/widgets/brand.liquid +78 -50
  70. package/test-theme/widgets/carousel.liquid +253 -121
  71. package/test-theme/widgets/category-list-carousel.liquid +32 -8
  72. package/test-theme/widgets/category-list.liquid +21 -6
  73. package/test-theme/widgets/category.liquid +104 -37
  74. package/test-theme/widgets/discount-time.liquid +326 -119
  75. package/test-theme/widgets/footer-menu.liquid +115 -23
  76. package/test-theme/widgets/footer.liquid +118 -5
  77. package/test-theme/widgets/gallery.liquid +29 -5
  78. package/test-theme/widgets/header-menu.liquid +25 -13
  79. package/test-theme/widgets/header.liquid +64 -26
  80. package/test-theme/widgets/html.liquid +29 -6
  81. package/test-theme/widgets/news.liquid +6 -0
  82. package/test-theme/widgets/product-canvas.liquid +20 -12
  83. package/test-theme/widgets/product-carousel.liquid +118 -56
  84. package/test-theme/widgets/shared/product-grid.liquid +12 -0
  85. package/test-theme/widgets/single-product.liquid +688 -250
  86. package/test-theme/widgets/spacebar-carousel.liquid +39 -10
  87. package/test-theme/widgets/spacebar.liquid +77 -6
  88. package/test-theme/widgets/splash.liquid +40 -30
  89. package/test-theme/widgets/testimonial-carousel.liquid +111 -67
@@ -1,41 +1,537 @@
1
1
  {% comment %}
2
- Simple Product Card Snippet (for Search Results)
2
+ Enhanced Modern Product Card
3
+ {% endcomment %}
4
+ {% assign call_for_pricing_raw = product.showCallForPricing | default: product.ShowCallForPricing | default: product.isCallForPricing | default: product.IsCallForPricing | default: false %}
5
+ {% assign show_call_for_pricing = false %}
6
+ {% if call_for_pricing_raw == true or call_for_pricing_raw == 'true' or call_for_pricing_raw == 1 or call_for_pricing_raw == '1' %}
7
+ {% assign show_call_for_pricing = true %}
8
+ {% endif %}
9
+
10
+ <style>
11
+
12
+ .product-info .product-title {
13
+ font-size: var(--text-1xl);
14
+ font-weight: 700;
15
+ color: var(--color-gray-900);
16
+ line-height: 1.2;
17
+ letter-spacing: -0.025em;
18
+ }
19
+ .product-card {
20
+ background: var(--color-background, #fff);
21
+ border-radius: 10px
22
+ overflow: hidden;
23
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
24
+ max-width: 100%;
25
+ text-align: center;
26
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
27
+ display: flex;
28
+ flex-direction: column;
29
+ border: 1px solid var(--color-border-light, rgba(0,0,0,0.06));
30
+ position: relative;
31
+ }
32
+
33
+ .product-card::before {
34
+ content: '';
35
+ position: absolute;
36
+ top: 0;
37
+ left: 0;
38
+ right: 0;
39
+ bottom: 0;
40
+ background: linear-gradient(135deg, transparent 0%, rgba(0,0,0,0.02) 100%);
41
+ opacity: 0;
42
+ transition: opacity 0.3s ease;
43
+ pointer-events: none;
44
+ z-index: 1;
45
+ }
46
+
47
+ .product-card:hover {
48
+ transform: translateY(-6px);
49
+ border-color: var(--color-primary, rgba(0,0,0,0.15));
50
+ }
51
+
52
+ .product-card:hover::before {
53
+ opacity: 1;
54
+ }
55
+
56
+ .product-link {
57
+ color: inherit;
58
+ text-decoration: none;
59
+ display: block;
60
+ flex: 1;
61
+ position: relative;
62
+ z-index: 2;
63
+ }
64
+
65
+ .product-image {
66
+ width: 100%;
67
+ height: 0;
68
+ padding-bottom: 100%; /* 1:1 aspect ratio */
69
+ background: linear-gradient(135deg, var(--color-card-bg, #f8f9fa) 0%, var(--color-border-light, #f0f1f3) 100%);
70
+ display: block;
71
+ position: relative;
72
+ overflow: hidden;
73
+ }
74
+
75
+ .product-image::after {
76
+ content: '';
77
+ position: absolute;
78
+ top: 0;
79
+ left: 0;
80
+ right: 0;
81
+ bottom: 0;
82
+ background: linear-gradient(to bottom, transparent 60%, rgba(0,0,0,0.03) 100%);
83
+ pointer-events: none;
84
+ }
85
+
86
+ .product-image img {
87
+ position: absolute;
88
+ top: 0;
89
+ left: 0;
90
+ width: 100%;
91
+ height: 100%;
92
+ object-fit: cover;
93
+ transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
94
+ }
95
+
96
+ .product-card:hover .product-image img {
97
+ transform: scale(1.08);
98
+ }
99
+
100
+ .product-info {
101
+ padding: clamp(12px, 2vw, 16px);
102
+ background: var(--color-background, #fff);
103
+ position: relative;
104
+ z-index: 2;
105
+ }
106
+
107
+ .product-title {
108
+ font-size: clamp(0.8125rem, 1.5vw, 0.875rem);
109
+ font-weight: 600;
110
+ color: var(--color-text, #1a1a1a);
111
+ margin: 0 0 6px 0;
112
+ line-height: 1.4;
113
+ height: 2.8em;
114
+ overflow: hidden;
115
+ display: -webkit-box;
116
+ -webkit-line-clamp: 2;
117
+ -webkit-box-orient: vertical;
118
+ text-overflow: ellipsis;
119
+ letter-spacing: -0.01em;
120
+ }
121
+
122
+ .product-description {
123
+ font-size: clamp(0.6875rem, 1.2vw, 0.75rem);
124
+ color: var(--color-text-light, #666);
125
+ margin: 0 0 8px 0;
126
+ line-height: 1.5;
127
+ height: 3em;
128
+ overflow: hidden;
129
+ display: -webkit-box;
130
+ -webkit-line-clamp: 2;
131
+ -webkit-box-orient: vertical;
132
+ text-overflow: ellipsis;
133
+ letter-spacing: 0;
134
+ }
135
+
136
+ .product-price {
137
+ font-size: clamp(0.9375rem, 1.8vw, 1.0625rem);
138
+ font-weight: 700;
139
+ color: var(--color-primary, #000);
140
+ margin-bottom: clamp(10px, 2vw, 14px);
141
+ letter-spacing: -0.02em;
142
+ }
143
+
144
+ .btn-add {
145
+ background: var(--color-primary, #000);
146
+ color: var(--color-background, #fff);
147
+ border: none;
148
+ border-radius: clamp(6px, 1vw, 8px);
149
+ padding: clamp(10px, 2vw, 12px) clamp(14px, 2.5vw, 18px);
150
+ font-size: clamp(0.75rem, 1.4vw, 0.8125rem);
151
+ font-weight: 600;
152
+ cursor: pointer;
153
+ width: calc(100% - clamp(16px, 3vw, 24px));
154
+ margin: 0 clamp(8px, 1.5vw, 12px) clamp(12px, 2vw, 16px);
155
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
156
+ text-transform: uppercase;
157
+ letter-spacing: 0.05em;
158
+ position: relative;
159
+ z-index: 3;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .btn-add::before {
164
+ content: '';
165
+ position: absolute;
166
+ top: 50%;
167
+ left: 50%;
168
+ width: 0;
169
+ height: 0;
170
+ border-radius: 50%;
171
+ background: rgba(255, 255, 255, 0.2);
172
+ transform: translate(-50%, -50%);
173
+ transition: width 0.6s ease, height 0.6s ease;
174
+ }
175
+
176
+ .btn-add:hover::before {
177
+ width: 300px;
178
+ height: 300px;
179
+ }
180
+
181
+ .btn-add:hover {
182
+ background: var(--color-primary-hover, #222);
183
+ transform: translateY(-2px);
184
+ }
185
+
186
+ .btn-add:active {
187
+ transform: translateY(0);
188
+ }
189
+
190
+ /* Grid responsiveness */
191
+ @media (min-width: 1200px) {
192
+ .product-card {
193
+ max-width: 280px;
194
+ }
195
+ }
196
+
197
+ @media (min-width: 768px) and (max-width: 1199px) {
198
+ .product-card {
199
+ max-width: 240px;
200
+ }
201
+ }
202
+
203
+ @media (min-width: 481px) and (max-width: 767px) {
204
+ .product-card {
205
+ max-width: 220px;
206
+ }
207
+
208
+ .product-image {
209
+ padding-bottom: 85%;
210
+ }
211
+ }
212
+
213
+ @media (max-width: 480px) {
214
+ .product-card {
215
+ max-width: 100%;
216
+ border-radius: 12px;
217
+ }
3
218
 
4
- Parameters:
5
- - product: Product object (required)
219
+ .product-image {
220
+ padding-bottom: 90%;
221
+ }
6
222
 
7
- Usage:
8
- {% include 'snippets/product-card-simple', product: product %}
9
- {% endcomment %}
223
+ .product-title {
224
+ font-size: 0.8125rem;
225
+ height: 2.6em;
226
+ }
227
+
228
+ .product-description {
229
+ font-size: 0.6875rem;
230
+ height: 2.8em;
231
+ }
232
+
233
+ .product-price {
234
+ font-size: 0.9375rem;
235
+ margin-bottom: 10px;
236
+ }
237
+
238
+ .btn-add {
239
+ font-size: 0.75rem;
240
+ padding: 10px 14px;
241
+ letter-spacing: 0.03em;
242
+ }
243
+ }
244
+
245
+ @media (max-width: 360px) {
246
+ .product-card {
247
+ border-radius: 10px;
248
+ }
249
+
250
+ .product-info {
251
+ padding: 10px;
252
+ }
253
+
254
+ .product-title {
255
+ font-size: 0.75rem;
256
+ margin-bottom: 4px;
257
+ }
258
+
259
+ .product-description {
260
+ font-size: 0.625rem;
261
+ margin-bottom: 6px;
262
+ }
263
+
264
+ .product-price {
265
+ font-size: 0.875rem;
266
+ }
267
+
268
+ .btn-add {
269
+ font-size: 0.6875rem;
270
+ padding: 8px 12px;
271
+ width: calc(100% - 16px);
272
+ margin: 0 8px 10px;
273
+ }
274
+ }
275
+
276
+ /* Loading state */
277
+ .product-card.loading {
278
+ pointer-events: none;
279
+ opacity: 0.6;
280
+ }
281
+
282
+ .product-card.loading .btn-add {
283
+ position: relative;
284
+ }
285
+
286
+ .product-card.loading .btn-add::after {
287
+ content: '';
288
+ position: absolute;
289
+ top: 50%;
290
+ left: 50%;
291
+ width: 14px;
292
+ height: 14px;
293
+ margin: -7px 0 0 -7px;
294
+ border: 2px solid rgba(255,255,255,0.3);
295
+ border-top-color: #fff;
296
+ border-radius: 50%;
297
+ animation: spin 0.6s linear infinite;
298
+ }
299
+
300
+ @keyframes spin {
301
+ to { transform: rotate(360deg); }
302
+ }
303
+
304
+ /* Accessibility */
305
+ .btn-add:focus-visible {
306
+ outline: 2px solid var(--color-primary, #000);
307
+ outline-offset: 2px;
308
+ }
309
+
310
+ .product-link:focus-visible {
311
+ outline: 2px solid var(--color-primary, #000);
312
+ outline-offset: 2px;
313
+ border-radius: clamp(12px, 1.5vw, 16px);
314
+ }
315
+
316
+ /* Dark mode support */
317
+ @media (prefers-color-scheme: dark) {
318
+ .product-card {
319
+ background: var(--color-background, #1a1a1a);
320
+ border-color: var(--color-border-light, rgba(255,255,255,0.08));
321
+ }
322
+
323
+ .product-title {
324
+ color: var(--color-text, #f5f5f5);
325
+ }
326
+
327
+ .product-description {
328
+ color: var(--color-text-light, #a0a0a0);
329
+ }
330
+
331
+ .product-image {
332
+ background: linear-gradient(135deg, var(--color-card-bg, #2a2a2a) 0%, var(--color-border-light, #333) 100%);
333
+ }
334
+ }
335
+
336
+ /* Print styles */
337
+ @media print {
338
+ .product-card {
339
+ break-inside: avoid;
340
+ border: 1px solid #ddd;
341
+ }
342
+
343
+ .btn-add {
344
+ display: none;
345
+ }
346
+
347
+ .product-card:hover {
348
+ transform: none;
349
+ }
350
+ }
351
+ </style>
10
352
 
11
353
  <div class="product-card">
12
354
  <a href="/{{ product.slug }}" class="product-link">
13
355
  <div class="product-image">
14
- {% if product.images and product.images[0] %}
15
- <img src="{{ product.images[0] }}"
16
- alt="{{ product.title }}"
17
- loading="lazy">
356
+ {% assign product_image_alt = product.name | default: "Product Image" %}
357
+ {% if product.thumbnailImage1 and product.thumbnailImage1.url %}
358
+ {% assign product_image = product.thumbnailImage1.url %}
359
+ {% assign product_image_alt = product.thumbnailImage1.altText | default: product_image_alt %}
360
+ {% elsif product.ThumbnailImage1 and product.ThumbnailImage1.Url %}
361
+ {% assign product_image = product.ThumbnailImage1.Url %}
362
+ {% assign product_image_alt = product.ThumbnailImage1.AltText | default: product_image_alt %}
363
+ {% elsif product.images and product.images.size > 0 %}
364
+ {% assign first_image = product.images | first %}
365
+ {% if first_image.url %}
366
+ {% assign product_image = first_image.url %}
367
+ {% assign product_image_alt = first_image.altText | default: product_image_alt %}
368
+ {% elsif first_image.Url %}
369
+ {% assign product_image = first_image.Url %}
370
+ {% assign product_image_alt = first_image.AltText | default: product_image_alt %}
371
+ {% endif %}
372
+ {% elsif product.thumbnailImage and product.thumbnailImage.url %}
373
+ {% assign product_image = product.thumbnailImage.url %}
374
+ {% assign product_image_alt = product.thumbnailImage.altText | default: product_image_alt %}
375
+ {% elsif product.ThumbnailImage and product.ThumbnailImage.Url %}
376
+ {% assign product_image = product.ThumbnailImage.Url %}
377
+ {% assign product_image_alt = product.ThumbnailImage.AltText | default: product_image_alt %}
378
+ {% elsif product.imageUrl %}
379
+ {% assign product_image = product.imageUrl %}
380
+ {% elsif product.ImageUrl %}
381
+ {% assign product_image = product.ImageUrl %}
382
+ {% endif %}
383
+
384
+ {% if product_image %}
385
+ <img src="{{ product_image }}" alt="{{ product_image_alt }}" loading="lazy">
18
386
  {% else %}
19
- <img src="/assets/placeholder-product.jpg"
20
- alt="{{ product.title }}"
21
- loading="lazy">
387
+ <img src="/assets/placeholder-product.jpg" alt="{{ product_image_alt }}" loading="lazy">
22
388
  {% endif %}
23
389
  </div>
390
+
24
391
  <div class="product-info">
25
- <h3 class="product-title">{{ product.title }}</h3>
26
- <p class="product-description">{{ product.description | truncate: 100 }}</p>
27
- <div class="product-price">
28
- <span class="price">{{ product.prices.priceString | default: product.prices.price | money }}</span>
29
- <span class="currency">{{ shop.currency }}</span>
392
+ <h3 class="product-title">{{ product.name | default: product.title | default: "Product Name" }}</h3>
393
+ <div class="product-price" data-price="{{ product.prices.price | default: product.price | default: 0 }}" data-show-call-for-pricing="{% if show_call_for_pricing %}true{% else %}false{% endif %}">
394
+ {% if show_call_for_pricing %}
395
+ Call for pricing
396
+ {% elsif product.prices and product.prices.priceString %}
397
+ {{ product.prices.priceString }}
398
+ {% elsif product.prices and product.prices.price %}
399
+ {{ product.prices.price | money }}
400
+ {% elsif product.price %}
401
+ {{ product.price | money }}
402
+ {% else %}
403
+ Price not available
404
+ {% endif %}
30
405
  </div>
31
406
  </div>
32
407
  </a>
33
- <div class="product-actions">
34
- <button class="btn btn-secondary add-to-cart"
35
- data-product-id="{{ product.productId }}"
36
- data-variant-id="{{ product.variants[0].id }}">
37
- Add to Cart
38
- </button>
39
- </div>
408
+
409
+ <button class="btn-add add-to-cart-button"
410
+ data-product-id="{{ product.productId | default: product.id | default: '' }}"
411
+ data-variant-id="{{ product.variants[0].id | default: '' }}">
412
+ Add to Cart
413
+ </button>
40
414
  </div>
41
415
 
416
+ <script>
417
+ document.addEventListener('DOMContentLoaded', () => {
418
+ // Format prices using Theme.formatMoneyProductCard (reads shop currency symbol) when available
419
+ document.querySelectorAll('.product-price[data-price]').forEach((el) => {
420
+ try {
421
+ var showCallForPricing = el.getAttribute('data-show-call-for-pricing');
422
+ if (showCallForPricing === 'true' || showCallForPricing === '1') {
423
+ el.textContent = 'Call for pricing';
424
+ return;
425
+ }
426
+ var priceRaw = el.getAttribute('data-price');
427
+ if (!priceRaw) return;
428
+ var num = parseFloat(priceRaw);
429
+ if (isNaN(num)) return;
430
+ if (window.Theme && typeof window.Theme.formatMoneyProductCard === 'function') {
431
+ el.textContent = window.Theme.formatMoneyProductCard(num);
432
+ } else if (window.__SHOP_CURRENCY_SYMBOL__) {
433
+ el.textContent = window.__SHOP_CURRENCY_SYMBOL__ + num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
434
+ }
435
+ } catch (e) {
436
+ // ignore formatting errors and leave server-rendered value
437
+ console.warn('[product-card-simple] price format error', e);
438
+ }
439
+ });
440
+
441
+ document.querySelectorAll('.btn-add.add-to-cart-button').forEach((btn) => {
442
+ if (btn.dataset.simpleBound === '1') return;
443
+ btn.dataset.simpleBound = '1';
444
+
445
+ btn.addEventListener('click', async (e) => {
446
+ e.preventDefault();
447
+ const productId = btn.getAttribute('data-product-id') || '';
448
+ const variantId = btn.getAttribute('data-variant-id') || '';
449
+ const idToUse = variantId || productId;
450
+ if (!idToUse) return;
451
+
452
+ const originalText = btn.textContent.trim();
453
+ btn.disabled = true;
454
+ btn.textContent = 'Adding...';
455
+
456
+ try {
457
+ // Prefer central Theme.addToCart when available (keeps behavior consistent)
458
+ if (window.Theme && typeof window.Theme.addToCart === 'function') {
459
+ await window.Theme.addToCart(idToUse, 1);
460
+ } else {
461
+ // Fallback: direct API call (keeps behavior similar to product-card)
462
+ const response = await fetch('/webstoreapi/carts/add', {
463
+ method: 'POST',
464
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
465
+ body: JSON.stringify({ productId: idToUse, quantity: 1 })
466
+ });
467
+ const data = await response.json();
468
+
469
+ if (!data.success) {
470
+ // If backend indicates authentication is required, open login instead of showing a generic error
471
+ if (data.requiresAuth) {
472
+ if (window.Theme && typeof window.Theme.openLoginModal === 'function') {
473
+ window.Theme.openLoginModal();
474
+ } else if (window.CartManager && typeof window.CartManager.openLoginModal === 'function') {
475
+ window.CartManager.openLoginModal();
476
+ } else {
477
+ const loginTrigger = document.querySelector('[data-login-modal-trigger]');
478
+ if (loginTrigger) loginTrigger.click();
479
+ }
480
+ // Reset button state and exit gracefully
481
+ btn.textContent = originalText;
482
+ btn.disabled = false;
483
+ return;
484
+ }
485
+
486
+ throw new Error(data.error || data.message || 'Failed to add product to cart');
487
+ }
488
+
489
+ // Try to update badge via CartManager
490
+ if (window.CartManager && typeof window.CartManager.getCartCount === 'function') {
491
+ const cartCount = await window.CartManager.getCartCount(true);
492
+ if (window.CartManager.dispatchCartUpdated) {
493
+ window.CartManager.dispatchCartUpdated({ itemCount: cartCount, cart: data.data });
494
+ }
495
+ }
496
+ }
497
+
498
+ // If we used Theme.addToCart above, it is responsible for notifications; only show success toast when using the fallback API path
499
+ if (!(window.Theme && typeof window.Theme.addToCart === 'function')) {
500
+ if (window.Theme && typeof window.Theme.showNotification === 'function') {
501
+ window.Theme.showNotification('Product added to cart!', 'success');
502
+ }
503
+ }
504
+
505
+ btn.textContent = '✓ Added';
506
+ setTimeout(() => {
507
+ btn.textContent = originalText;
508
+ btn.disabled = false;
509
+ }, 2000);
510
+ } catch (err) {
511
+ console.error('product-card-simple addToCart failed', err);
512
+
513
+ // If this is an authentication-related error, open login popup and do not show a generic error toast
514
+ if (err && err.message && (
515
+ err.message.includes('Authentication required') ||
516
+ err.message.includes('Please sign in') ||
517
+ err.message.toLowerCase().includes('unauthorized')
518
+ )) {
519
+ if (window.Theme && typeof window.Theme.openLoginModal === 'function') {
520
+ window.Theme.openLoginModal();
521
+ } else if (window.CartManager && typeof window.CartManager.openLoginModal === 'function') {
522
+ window.CartManager.openLoginModal();
523
+ } else {
524
+ const loginTrigger = document.querySelector('[data-login-modal-trigger]');
525
+ if (loginTrigger) loginTrigger.click();
526
+ }
527
+ } else if (window.Theme && typeof window.Theme.showNotification === 'function') {
528
+ window.Theme.showNotification('Failed to add product to cart. Please sign in or try again.', 'error');
529
+ }
530
+
531
+ btn.textContent = originalText;
532
+ btn.disabled = false;
533
+ }
534
+ });
535
+ });
536
+ });
537
+ </script>