@o2vend/theme-cli 1.0.32

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 (116) hide show
  1. package/README.md +425 -0
  2. package/assets/Logo_o2vend.png +0 -0
  3. package/assets/favicon.png +0 -0
  4. package/assets/logo-white.png +0 -0
  5. package/bin/o2vend +42 -0
  6. package/config/widget-map.json +50 -0
  7. package/lib/commands/check.js +201 -0
  8. package/lib/commands/generate.js +33 -0
  9. package/lib/commands/init.js +214 -0
  10. package/lib/commands/optimize.js +216 -0
  11. package/lib/commands/package.js +208 -0
  12. package/lib/commands/serve.js +105 -0
  13. package/lib/commands/validate.js +191 -0
  14. package/lib/lib/api-client.js +357 -0
  15. package/lib/lib/dev-server.js +2618 -0
  16. package/lib/lib/file-watcher.js +80 -0
  17. package/lib/lib/hot-reload.js +106 -0
  18. package/lib/lib/liquid-engine.js +822 -0
  19. package/lib/lib/liquid-filters.js +671 -0
  20. package/lib/lib/mock-api-server.js +989 -0
  21. package/lib/lib/mock-data.js +1468 -0
  22. package/lib/lib/widget-service.js +321 -0
  23. package/package.json +70 -0
  24. package/test-theme/README.md +27 -0
  25. package/test-theme/assets/async-sections.js +446 -0
  26. package/test-theme/assets/cart-drawer.js +463 -0
  27. package/test-theme/assets/cart-manager.js +223 -0
  28. package/test-theme/assets/checkout-price-handler.js +368 -0
  29. package/test-theme/assets/components.css +4629 -0
  30. package/test-theme/assets/delivery-zone.css +299 -0
  31. package/test-theme/assets/delivery-zone.js +396 -0
  32. package/test-theme/assets/logo.png +0 -0
  33. package/test-theme/assets/sections.css +48 -0
  34. package/test-theme/assets/theme.css +3500 -0
  35. package/test-theme/assets/theme.js +3745 -0
  36. package/test-theme/config/settings_data.json +292 -0
  37. package/test-theme/config/settings_schema.json +1050 -0
  38. package/test-theme/layout/theme.liquid +195 -0
  39. package/test-theme/locales/en.default.json +260 -0
  40. package/test-theme/sections/content-fallback.liquid +53 -0
  41. package/test-theme/sections/content.liquid +57 -0
  42. package/test-theme/sections/footer-fallback.liquid +328 -0
  43. package/test-theme/sections/footer.liquid +278 -0
  44. package/test-theme/sections/header-fallback.liquid +1805 -0
  45. package/test-theme/sections/header.liquid +1145 -0
  46. package/test-theme/sections/hero-fallback.liquid +212 -0
  47. package/test-theme/sections/hero.liquid +136 -0
  48. package/test-theme/snippets/account-sidebar.liquid +200 -0
  49. package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
  50. package/test-theme/snippets/breadcrumbs.liquid +134 -0
  51. package/test-theme/snippets/cart-drawer.liquid +467 -0
  52. package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
  53. package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
  54. package/test-theme/snippets/delivery-zone-search.liquid +78 -0
  55. package/test-theme/snippets/icon.liquid +105 -0
  56. package/test-theme/snippets/login-modal.liquid +346 -0
  57. package/test-theme/snippets/mega-menu.liquid +812 -0
  58. package/test-theme/snippets/news-thumbnail.liquid +187 -0
  59. package/test-theme/snippets/pagination.liquid +120 -0
  60. package/test-theme/snippets/price.liquid +92 -0
  61. package/test-theme/snippets/product-card-related.liquid +78 -0
  62. package/test-theme/snippets/product-card-simple.liquid +41 -0
  63. package/test-theme/snippets/product-card.liquid +697 -0
  64. package/test-theme/snippets/rating.liquid +85 -0
  65. package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
  66. package/test-theme/snippets/skeleton-product-card.liquid +124 -0
  67. package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
  68. package/test-theme/snippets/social-sharing.liquid +185 -0
  69. package/test-theme/templates/account/dashboard.liquid +401 -0
  70. package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
  71. package/test-theme/templates/account/loyalty.liquid +588 -0
  72. package/test-theme/templates/account/order-detail.liquid +230 -0
  73. package/test-theme/templates/account/orders.liquid +349 -0
  74. package/test-theme/templates/account/profile.liquid +758 -0
  75. package/test-theme/templates/account/register.liquid +232 -0
  76. package/test-theme/templates/account/return-orders.liquid +348 -0
  77. package/test-theme/templates/account/store-credit.liquid +464 -0
  78. package/test-theme/templates/account/subscriptions.liquid +601 -0
  79. package/test-theme/templates/account/wishlist.liquid +419 -0
  80. package/test-theme/templates/address-book.liquid +1092 -0
  81. package/test-theme/templates/categories.liquid +452 -0
  82. package/test-theme/templates/checkout.liquid +4511 -0
  83. package/test-theme/templates/error.liquid +384 -0
  84. package/test-theme/templates/index.liquid +11 -0
  85. package/test-theme/templates/login.liquid +185 -0
  86. package/test-theme/templates/order-confirmation.liquid +720 -0
  87. package/test-theme/templates/page.liquid +297 -0
  88. package/test-theme/templates/product-detail.liquid +4363 -0
  89. package/test-theme/templates/products.liquid +518 -0
  90. package/test-theme/templates/search.liquid +922 -0
  91. package/test-theme/theme.json.example +19 -0
  92. package/test-theme/widgets/brand-carousel.liquid +676 -0
  93. package/test-theme/widgets/brand.liquid +245 -0
  94. package/test-theme/widgets/carousel.liquid +843 -0
  95. package/test-theme/widgets/category-list-carousel.liquid +656 -0
  96. package/test-theme/widgets/category-list.liquid +340 -0
  97. package/test-theme/widgets/category.liquid +475 -0
  98. package/test-theme/widgets/discount-time.liquid +176 -0
  99. package/test-theme/widgets/footer-menu.liquid +695 -0
  100. package/test-theme/widgets/footer.liquid +179 -0
  101. package/test-theme/widgets/gallery.liquid +271 -0
  102. package/test-theme/widgets/header-menu.liquid +932 -0
  103. package/test-theme/widgets/header.liquid +159 -0
  104. package/test-theme/widgets/html.liquid +214 -0
  105. package/test-theme/widgets/news.liquid +217 -0
  106. package/test-theme/widgets/product-canvas.liquid +235 -0
  107. package/test-theme/widgets/product-carousel.liquid +502 -0
  108. package/test-theme/widgets/product.liquid +45 -0
  109. package/test-theme/widgets/recently-viewed.liquid +26 -0
  110. package/test-theme/widgets/shared/product-grid.liquid +339 -0
  111. package/test-theme/widgets/simple-product.liquid +42 -0
  112. package/test-theme/widgets/single-product.liquid +610 -0
  113. package/test-theme/widgets/spacebar-carousel.liquid +663 -0
  114. package/test-theme/widgets/spacebar.liquid +279 -0
  115. package/test-theme/widgets/splash.liquid +378 -0
  116. package/test-theme/widgets/testimonial-carousel.liquid +709 -0
@@ -0,0 +1,697 @@
1
+ {% comment %}
2
+ Product Card Snippet - Simple Professional Design
3
+
4
+ Parameters:
5
+ - product: Product object (required)
6
+ - loading: Image loading attribute (optional, default: "lazy")
7
+
8
+ Usage:
9
+ {% include 'snippets/product-card', product: product %}
10
+ {% endcomment %}
11
+
12
+ {% assign image_loading = loading | default: "lazy" %}
13
+ {% assign variants = product.variations | default: empty %}
14
+
15
+ {% comment %}Calculate default variant for add-to-cart{% endcomment %}
16
+ {% assign defaultVariantForCart = null %}
17
+ {% assign defaultVariantProductId = product.productId %}
18
+ {% if variants and variants.size > 0 %}
19
+ {% for variant in variants %}
20
+ {% assign isAvailable = variant.inStock | default: variant.available | default: true %}
21
+ {% if defaultVariantForCart == null and isAvailable %}
22
+ {% assign defaultVariantForCart = variant %}
23
+ {% assign defaultVariantProductId = variant.productId %}
24
+ {% endif %}
25
+ {% endfor %}
26
+ {% if defaultVariantForCart == null %}
27
+ {% assign defaultVariantForCart = variants.first %}
28
+ {% assign defaultVariantProductId = variants.first.productId %}
29
+ {% endif %}
30
+ {% endif %}
31
+
32
+ {% comment %}Image fallback logic{% endcomment %}
33
+ {% assign product_image = nil %}
34
+ {% assign product_image_alt = product.name | default: product.slug | default: product.id %}
35
+
36
+ {% if product.thumbnailImage1 and product.thumbnailImage1.url %}
37
+ {% assign product_image = product.thumbnailImage1.url %}
38
+ {% assign product_image_alt = product.thumbnailImage1.altText | default: product_image_alt %}
39
+ {% elsif product.ThumbnailImage1 and product.ThumbnailImage1.Url %}
40
+ {% assign product_image = product.ThumbnailImage1.Url %}
41
+ {% assign product_image_alt = product.ThumbnailImage1.AltText | default: product_image_alt %}
42
+ {% elsif product.images and product.images.size > 0 %}
43
+ {% assign first_image = product.images | first %}
44
+ {% if first_image.url %}
45
+ {% assign product_image = first_image.url %}
46
+ {% assign product_image_alt = first_image.altText | default: product_image_alt %}
47
+ {% elsif first_image.Url %}
48
+ {% assign product_image = first_image.Url %}
49
+ {% assign product_image_alt = first_image.AltText | default: product_image_alt %}
50
+ {% endif %}
51
+ {% elsif product.thumbnailImage and product.thumbnailImage.url %}
52
+ {% assign product_image = product.thumbnailImage.url %}
53
+ {% assign product_image_alt = product.thumbnailImage.altText | default: product_image_alt %}
54
+ {% elsif product.ThumbnailImage and product.ThumbnailImage.Url %}
55
+ {% assign product_image = product.ThumbnailImage.Url %}
56
+ {% assign product_image_alt = product.ThumbnailImage.AltText | default: product_image_alt %}
57
+ {% elsif product.imageUrl %}
58
+ {% assign product_image = product.imageUrl %}
59
+ {% elsif product.ImageUrl %}
60
+ {% assign product_image = product.ImageUrl %}
61
+ {% endif %}
62
+
63
+ <div class="product-card"
64
+ data-product-id="{{ product.productId }}"
65
+ data-price="{{ product.prices.price }}"
66
+ data-name="{{ product.name | default: product.slug | default: product.id | downcase }}"
67
+ data-product-type="{{ product.productType | default: product.type | default: 0 }}"
68
+ data-variants-count="{% if variants and variants.size > 0 %}{{ variants.size }}{% else %}0{% endif %}"
69
+ data-availability="{% if product.stockQuantity > 0 %}in-stock{% else %}out-of-stock{% endif %}">
70
+
71
+ <div class="product-card__image-wrapper">
72
+ {% hook 'product_card_image_before' %}
73
+ {% assign product_url = product.url | default: product.Url | default: product.link | default: product.Link %}
74
+ {% if product_url == blank %}
75
+ {% assign product_url = product.slug | default: product.id | default: '#' %}
76
+ {% endif %}
77
+ <a href="{{ product_url }}" class="product-card__link">
78
+ {% if product_image %}
79
+ <img src="{{ product_image }}"
80
+ alt="{{ product_image_alt }}"
81
+ class="product-card__image"
82
+ loading="{{ image_loading }}">
83
+ {% else %}
84
+ <div class="product-card__placeholder">
85
+ <svg class="product-card__placeholder-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
86
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
87
+ <circle cx="8.5" cy="8.5" r="1.5"></circle>
88
+ <polyline points="21 15 16 10 5 21"></polyline>
89
+ </svg>
90
+ </div>
91
+ {% endif %}
92
+ </a>
93
+ {% hook 'product_card_image_after' %}
94
+
95
+ <!-- Product Badges -->
96
+ <div class="product-card__badges">
97
+ {% assign is_new = false %}
98
+ {% if product.newProductDisplayDays and product.createdOn %}
99
+ {% assign created_timestamp = product.createdOn | date: '%s' | plus: 0 %}
100
+ {% assign current_timestamp = 'now' | date: '%s' | plus: 0 %}
101
+ {% assign seconds_diff = current_timestamp | minus: created_timestamp %}
102
+ {% assign days_diff = seconds_diff | divided_by: 86400 %}
103
+ {% assign new_product_days = 30 %}
104
+ {% if days_diff >= 0 and days_diff <= new_product_days %}
105
+ {% assign is_new = true %}
106
+ {% endif %}
107
+ {% endif %}
108
+ {% if is_new %}
109
+ <span class="product-card__badge product-card__badge--new">New</span>
110
+ {% endif %}
111
+
112
+ {% if product.prices.mrp and product.prices.mrp > product.prices.price %}
113
+ {% assign discount = product.prices.mrp | minus: product.prices.price | times: 100 | divided_by: product.prices.mrp | round %}
114
+ <span class="product-card__badge product-card__badge--sale">-{{ discount }}%</span>
115
+ {% endif %}
116
+ {% unless product.inStock or product.available %}
117
+ <span class="product-card__badge product-card__badge--sold-out">Sold Out</span>
118
+ {% endunless %}
119
+ </div>
120
+ </div>
121
+
122
+ <div class="product-card__content">
123
+ {% hook 'product_card_title_before' %}
124
+ <h3 class="product-card__title">
125
+ <a href="{{ product_url }}" class="product-card__title-link">
126
+ {{ product.name | default: product.slug | default: product.id }}
127
+ </a>
128
+ </h3>
129
+ {% hook 'product_card_title_after' %}
130
+
131
+ {% hook 'product_card_price_before' %}
132
+ <div class="product-card__price" data-product-card-price="{{ product.productId }}">
133
+ {% if variants and variants.size > 0 %}
134
+ {% assign defaultVariant = null %}
135
+ {% for variant in variants %}
136
+ {% assign isAvailable = variant.inStock | default: variant.available | default: true %}
137
+ {% if defaultVariant == null and isAvailable %}
138
+ {% assign defaultVariant = variant %}
139
+ {% endif %}
140
+ {% endfor %}
141
+ {% if defaultVariant == null %}
142
+ {% assign defaultVariant = variants.first %}
143
+ {% endif %}
144
+ {% assign defaultPrice = defaultVariant.prices.price | default: product.prices.price %}
145
+ {% assign defaultMrp = defaultVariant.prices.mrp | default: product.prices.mrp | default: 0 %}
146
+ <span class="product-card__price-current" data-price-current="{{ defaultPrice }}">{{ defaultPrice | money_with_settings: shop.settings }}</span>
147
+ {% if defaultMrp > defaultPrice %}
148
+ <span class="product-card__price-original" data-price-original="{{ defaultMrp }}">{{ defaultMrp | money_with_settings: shop.settings }}</span>
149
+ {% endif %}
150
+ {% else %}
151
+ <span class="product-card__price-current">{{ product.prices.price | money_with_settings: shop.settings }}</span>
152
+ {% if product.prices.mrp and product.prices.mrp > product.prices.price %}
153
+ <span class="product-card__price-original">{{ product.prices.mrp | money_with_settings: shop.settings }}</span>
154
+ {% endif %}
155
+ {% endif %}
156
+ </div>
157
+ {% hook 'product_card_price_after' %}
158
+
159
+ <!-- Add to Cart Button -->
160
+ <button class="product-card__cart-btn add-to-cart-btn"
161
+ data-product-id="{{ defaultVariantProductId }}"
162
+ data-base-product-id="{{ product.productId }}"
163
+ data-product-type="{{ product.productType | default: product.type | default: 0 }}"
164
+ {% if variants and variants.size > 0 %}
165
+ {% assign defaultAvailable = defaultVariantForCart.inStock | default: defaultVariantForCart.available | default: true %}
166
+ {% unless defaultAvailable %}disabled{% endunless %}
167
+ {% else %}
168
+ {% unless product.inStock or product.available %}disabled{% endunless %}
169
+ {% endif %}>
170
+ <svg class="product-card__cart-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
171
+ <circle cx="9" cy="21" r="1"></circle>
172
+ <circle cx="20" cy="21" r="1"></circle>
173
+ <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
174
+ </svg>
175
+ <span class="product-card__cart-text">
176
+ {% if variants and variants.size > 0 %}
177
+ {% assign defaultAvailable = defaultVariantForCart.inStock | default: defaultVariantForCart.available | default: true %}
178
+ {% if defaultAvailable %}Add to Cart{% else %}Out of Stock{% endif %}
179
+ {% else %}
180
+ {% if product.inStock or product.available %}Add to Cart{% else %}Out of Stock{% endif %}
181
+ {% endif %}
182
+ </span>
183
+ </button>
184
+ </div>
185
+ </div>
186
+
187
+ <style>
188
+ /* Product Card Variables */
189
+ :root {
190
+ --card-bg: #ffffff;
191
+ --card-border: #e5e7eb;
192
+ --card-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
193
+ --card-radius: 8px;
194
+ --card-padding: 16px;
195
+ --badge-radius: 4px;
196
+ --btn-radius: 6px;
197
+ --color-primary: #111827;
198
+ --color-secondary: #6b7280;
199
+ --color-sale: #ef4444;
200
+ --color-new: #10b981;
201
+ --color-text: #111827;
202
+ --color-text-muted: #6b7280;
203
+ }
204
+
205
+ /* Card Container */
206
+ .product-card {
207
+ position: relative;
208
+ display: flex;
209
+ flex-direction: column;
210
+ background: var(--card-bg);
211
+ overflow: hidden;
212
+ height: 100%;
213
+ box-shadow: var(--card-shadow);
214
+ }
215
+
216
+ /* Image Wrapper */
217
+ .product-card__image-wrapper {
218
+ position: relative;
219
+ width: 100%;
220
+ aspect-ratio: 1 / 1;
221
+ overflow: hidden;
222
+ background: #f9fafb;
223
+ border-radius: var(--border-radius-medium);
224
+ }
225
+
226
+ .product-card__link {
227
+ display: block;
228
+ width: 100%;
229
+ height: 100%;
230
+ }
231
+
232
+ .product-card__image {
233
+ width: 100%;
234
+ height: 100%;
235
+ object-fit: cover;
236
+ display: block;
237
+ }
238
+
239
+ .product-card__placeholder {
240
+ width: 100%;
241
+ height: 100%;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ background: #f3f4f6;
246
+ }
247
+
248
+ .product-card__placeholder-icon {
249
+ width: 48px;
250
+ height: 48px;
251
+ color: #9ca3af;
252
+ opacity: 0.5;
253
+ }
254
+
255
+ /* Badges */
256
+ .product-card__badges {
257
+ position: absolute;
258
+ top: 12px;
259
+ left: 12px;
260
+ display: flex;
261
+ flex-direction: column;
262
+ gap: 6px;
263
+ z-index: 2;
264
+ }
265
+
266
+ .product-card__badge {
267
+ display: inline-flex;
268
+ align-items: center;
269
+ padding: 4px 8px;
270
+ font-size: 11px;
271
+ font-weight: 600;
272
+ text-transform: uppercase;
273
+ letter-spacing: 0.3px;
274
+ }
275
+
276
+ .product-card__badge--new {
277
+ background: var(--color-new);
278
+ color: white;
279
+ }
280
+
281
+ .product-card__badge--sale {
282
+ background: var(--color-sale);
283
+ color: white;
284
+ }
285
+
286
+ .product-card__badge--sold-out {
287
+ background: var(--color-secondary);
288
+ color: white;
289
+ }
290
+
291
+ /* Content Area */
292
+ .product-card__content {
293
+ padding: var(--card-padding);
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 12px;
297
+ flex: 1;
298
+ }
299
+
300
+ .product-card__title {
301
+ margin: 0;
302
+ font-size: 15px;
303
+ font-weight: 500;
304
+ line-height: 1.4;
305
+ }
306
+
307
+ .product-card__title-link {
308
+ color: var(--color-text);
309
+ text-decoration: none;
310
+ display: -webkit-box;
311
+ -webkit-line-clamp: 2;
312
+ -webkit-box-orient: vertical;
313
+ overflow: hidden;
314
+ }
315
+
316
+ .product-card__price {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 8px;
320
+ flex-wrap: wrap;
321
+ }
322
+
323
+ .product-card__price-current {
324
+ font-size: 18px;
325
+ font-weight: 600;
326
+ color: var(--color-text);
327
+ }
328
+
329
+ .product-card__price-original {
330
+ font-size: 14px;
331
+ font-weight: 400;
332
+ color: var(--color-text-muted);
333
+ text-decoration: line-through;
334
+ }
335
+
336
+ /* Add to Cart Button */
337
+ .product-card__cart-btn {
338
+ width: 100%;
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ gap: 8px;
343
+ padding: 12px 16px;
344
+ background: var(--color-primary);
345
+ color: white;
346
+ font-size: 14px;
347
+ font-weight: 500;
348
+ cursor: pointer;
349
+ margin-top: auto;
350
+ border-radius: var(--border-radius-medium);
351
+ border: none;
352
+ }
353
+
354
+ .product-card__cart-btn:disabled {
355
+ background: var(--color-secondary);
356
+ cursor: not-allowed;
357
+ opacity: 0.6;
358
+ }
359
+
360
+ .product-card__cart-icon {
361
+ width: 18px;
362
+ height: 18px;
363
+ flex-shrink: 0;
364
+ }
365
+
366
+ .product-card__cart-text {
367
+ font-weight: 500;
368
+ }
369
+
370
+ /* Tablet */
371
+ @media screen and (max-width: 1024px) and (min-width: 769px) {
372
+ .product-card__content {
373
+ padding: 14px;
374
+ gap: 10px;
375
+ }
376
+
377
+ .product-card__title {
378
+ font-size: 14px;
379
+ }
380
+
381
+ .product-card__price-current {
382
+ font-size: 16px;
383
+ }
384
+
385
+ .product-card__cart-btn {
386
+ padding: 10px 14px;
387
+ font-size: 13px;
388
+ }
389
+ }
390
+
391
+ /* Mobile */
392
+ @media screen and (max-width: 768px) {
393
+ :root {
394
+ --card-radius: 6px;
395
+ --card-padding: 10px;
396
+ --badge-radius: 3px;
397
+ --btn-radius: 5px;
398
+ }
399
+
400
+ .product-card {
401
+ box-shadow: none;
402
+ }
403
+
404
+ .product-card__content {
405
+ padding: var(--card-padding);
406
+ gap: 8px;
407
+ }
408
+
409
+ .product-card__title {
410
+ font-size: 13px;
411
+ }
412
+
413
+ .product-card__price {
414
+ gap: 6px;
415
+ }
416
+
417
+ .product-card__price-current {
418
+ font-size: 15px;
419
+ }
420
+
421
+ .product-card__price-original {
422
+ font-size: 12px;
423
+ }
424
+
425
+ .product-card__badges {
426
+ top: 8px;
427
+ left: 8px;
428
+ gap: 4px;
429
+ }
430
+
431
+ .product-card__badge {
432
+ padding: 3px 6px;
433
+ font-size: 9px;
434
+ }
435
+
436
+ .product-card__cart-btn {
437
+ padding: 8px 12px;
438
+ font-size: 12px;
439
+ gap: 6px;
440
+ }
441
+
442
+ .product-card__cart-icon {
443
+ width: 16px;
444
+ height: 16px;
445
+ }
446
+
447
+ .product-card__placeholder-icon {
448
+ width: 40px;
449
+ height: 40px;
450
+ }
451
+ }
452
+
453
+ /* Small Mobile */
454
+ @media screen and (max-width: 480px) {
455
+ .product-card__content {
456
+ padding: 8px;
457
+ gap: 6px;
458
+ }
459
+
460
+ .product-card__title {
461
+ font-size: 12px;
462
+ }
463
+
464
+ .product-card__price-current {
465
+ font-size: 14px;
466
+ }
467
+
468
+ .product-card__price-original {
469
+ font-size: 11px;
470
+ }
471
+
472
+ .product-card__cart-btn {
473
+ padding: 7px 10px;
474
+ font-size: 11px;
475
+ }
476
+ }
477
+ </style>
478
+
479
+ {% if variants and variants.size > 0 %}
480
+ <script type="application/json" class="product-card-variant-data" data-product-id="{{ product.productId }}">
481
+ {
482
+ "variants": [
483
+ {% for variant in variants %}
484
+ {
485
+ "productId": {{ variant.productId }},
486
+ "price": {{ variant.prices.price | default: product.prices.price }},
487
+ "mrp": {{ variant.prices.mrp | default: 0 }},
488
+ "inStock": {{ variant.inStock | default: variant.available | default: true }},
489
+ "available": {{ variant.available | default: variant.inStock | default: true }}
490
+ }{% unless forloop.last %},{% endunless %}
491
+ {% endfor %}
492
+ ],
493
+ "baseProductId": {{ product.productId }}
494
+ }
495
+ </script>
496
+ {% endif %}
497
+
498
+ <script>
499
+ document.addEventListener('DOMContentLoaded', function() {
500
+ const productCards = document.querySelectorAll('.product-card');
501
+
502
+ productCards.forEach(card => {
503
+ initializeProductCard(card);
504
+ });
505
+
506
+ function initializeProductCard(card) {
507
+ const cartBtn = card.querySelector('.product-card__cart-btn');
508
+ if (cartBtn) {
509
+ const productType = parseInt(card.dataset.productType || cartBtn.dataset.productType || '0', 10);
510
+ const variantsCount = parseInt(card.dataset.variantsCount || '0', 10);
511
+
512
+ if (productType === 0 && variantsCount === 0) {
513
+ cartBtn.addEventListener('click', function(e) {
514
+ e.preventDefault();
515
+ e.stopPropagation();
516
+ handleAddToCart(this, card);
517
+ }, true);
518
+ }
519
+ }
520
+ }
521
+
522
+ function handleAddToCart(button, card) {
523
+ if (button.disabled) return;
524
+
525
+ const productId = button.getAttribute('data-product-id');
526
+ const originalText = button.querySelector('.product-card__cart-text')?.textContent || 'Add to Cart';
527
+
528
+ if (button.querySelector('.product-card__cart-text')) {
529
+ button.querySelector('.product-card__cart-text').textContent = 'Adding...';
530
+ }
531
+ button.disabled = true;
532
+
533
+ addToCartAPI(productId)
534
+ .then(response => {
535
+ if (button.querySelector('.product-card__cart-text')) {
536
+ button.querySelector('.product-card__cart-text').textContent = '✓ Added';
537
+ }
538
+
539
+ updateHeaderCart();
540
+
541
+ // Unified bottom-center toast confirmation
542
+ if (window.Theme && typeof window.Theme.showNotification === 'function') {
543
+ window.Theme.showNotification('Product added to cart!', 'success', 3000);
544
+ }
545
+
546
+ setTimeout(() => {
547
+ if (button.querySelector('.product-card__cart-text')) {
548
+ button.querySelector('.product-card__cart-text').textContent = originalText;
549
+ }
550
+ button.disabled = false;
551
+ }, 2000);
552
+ })
553
+ .catch(error => {
554
+ console.error('Add to cart error:', error);
555
+
556
+ // Check if error message indicates authentication is required
557
+ // This handles cases where the error object might have requiresAuth info
558
+ if (error.message && error.message.includes('Authentication required')) {
559
+ // Try to open login modal
560
+ if (window.Theme && window.Theme.openLoginModal) {
561
+ window.Theme.openLoginModal();
562
+ } else if (window.CartManager && window.CartManager.openLoginModal) {
563
+ window.CartManager.openLoginModal();
564
+ } else {
565
+ const loginTrigger = document.querySelector('[data-login-modal-trigger]');
566
+ if (loginTrigger) {
567
+ loginTrigger.click();
568
+ }
569
+ }
570
+ } else {
571
+ // Show error message
572
+ if (button.querySelector('.product-card__cart-text')) {
573
+ button.querySelector('.product-card__cart-text').textContent = 'Error';
574
+ }
575
+ // Unified error toast
576
+ if (window.Theme && typeof window.Theme.showNotification === 'function') {
577
+ window.Theme.showNotification('Failed to add product to cart. Please try again.', 'error', 3000);
578
+ }
579
+ }
580
+
581
+ setTimeout(() => {
582
+ if (button.querySelector('.product-card__cart-text')) {
583
+ button.querySelector('.product-card__cart-text').textContent = originalText;
584
+ }
585
+ button.disabled = false;
586
+ }, 2000);
587
+ });
588
+ }
589
+
590
+ function addToCartAPI(productId) {
591
+ return fetch('/webstoreapi/carts/add', {
592
+ method: 'POST',
593
+ headers: {
594
+ 'Content-Type': 'application/json',
595
+ 'Accept': 'application/json'
596
+ },
597
+ body: JSON.stringify({
598
+ productId: productId,
599
+ quantity: 1
600
+ })
601
+ })
602
+ .then(response => response.json())
603
+ .then(data => {
604
+ if (data.success) {
605
+ return data;
606
+ } else {
607
+ // Check if authentication is required
608
+ if (data.requiresAuth) {
609
+ // Try to open login modal from Theme instance
610
+ if (window.Theme && window.Theme.openLoginModal) {
611
+ window.Theme.openLoginModal();
612
+ } else if (window.CartManager && window.CartManager.openLoginModal) {
613
+ window.CartManager.openLoginModal();
614
+ } else {
615
+ // Fallback: trigger login modal via data attribute
616
+ const loginTrigger = document.querySelector('[data-login-modal-trigger]');
617
+ if (loginTrigger) {
618
+ loginTrigger.click();
619
+ }
620
+ }
621
+ throw new Error('Authentication required');
622
+ }
623
+ throw new Error(data.error || data.message || 'Failed to add to cart');
624
+ }
625
+ });
626
+ }
627
+
628
+ function updateHeaderCart() {
629
+ if (window.CartManager && typeof window.CartManager.getCartCount === 'function') {
630
+ window.CartManager.getCartCount()
631
+ .then(count => {
632
+ updateCartBadge(count);
633
+ })
634
+ .catch(error => {
635
+ fetchCartFromAPI();
636
+ });
637
+ } else {
638
+ fetchCartFromAPI();
639
+ }
640
+
641
+ function fetchCartFromAPI() {
642
+ fetch('/webstoreapi/cart', {
643
+ method: 'GET',
644
+ headers: {
645
+ 'Accept': 'application/json',
646
+ 'X-Requested-With': 'XMLHttpRequest'
647
+ }
648
+ })
649
+ .then(response => response.json())
650
+ .then(data => {
651
+ if (data.success && data.cart) {
652
+ const itemCount = data.cart.itemCount || data.cart.cartQuantity || (data.cart.items ? data.cart.items.length : 0) || 0;
653
+ updateCartBadge(itemCount);
654
+ } else {
655
+ updateCartBadge(0);
656
+ }
657
+ })
658
+ .catch(error => {
659
+ console.error('Error updating header cart:', error);
660
+ updateCartBadge(0);
661
+ });
662
+ }
663
+
664
+ function updateCartBadge(count) {
665
+ const itemCount = parseInt(count, 10) || 0;
666
+
667
+ const cartCountElements = document.querySelectorAll('.site-header__cart-count, [data-cart-count]');
668
+ cartCountElements.forEach(el => {
669
+ el.textContent = itemCount;
670
+ el.setAttribute('data-cart-count', itemCount.toString());
671
+ el.style.display = itemCount > 0 ? 'flex' : 'none';
672
+ });
673
+
674
+ if (window.CartManager && typeof window.CartManager.updateCartBadge === 'function') {
675
+ window.CartManager.updateCartBadge(itemCount);
676
+ }
677
+
678
+ const event = new CustomEvent('cart:updated', {
679
+ detail: { count: itemCount }
680
+ });
681
+ document.dispatchEvent(event);
682
+ }
683
+ }
684
+
685
+ function initCartUpdate() {
686
+ setTimeout(() => {
687
+ updateHeaderCart();
688
+ }, 100);
689
+ }
690
+
691
+ if (document.readyState === 'loading') {
692
+ document.addEventListener('DOMContentLoaded', initCartUpdate);
693
+ } else {
694
+ initCartUpdate();
695
+ }
696
+ });
697
+ </script>