@o2vend/theme-cli 1.0.36 → 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 (90) hide show
  1. package/README.md +4 -0
  2. package/lib/lib/dev-server.js +344 -48
  3. package/lib/lib/liquid-engine.js +3 -1
  4. package/lib/lib/mock-data.js +473 -119
  5. package/lib/lib/widget-service.js +12 -4
  6. package/package.json +2 -2
  7. package/test-theme/assets/async-sections.js +32 -24
  8. package/test-theme/assets/cart-drawer.js +20 -22
  9. package/test-theme/assets/cart-manager.js +1 -15
  10. package/test-theme/assets/checkout-price-handler.js +12 -11
  11. package/test-theme/assets/checkout.css +1415 -0
  12. package/test-theme/assets/checkout.js +3174 -0
  13. package/test-theme/assets/components.css +178 -29
  14. package/test-theme/assets/delivery-zone.js +1 -1
  15. package/test-theme/assets/product-detail.css +1050 -0
  16. package/test-theme/assets/product-detail.js +2940 -0
  17. package/test-theme/assets/theme.css +95 -120
  18. package/test-theme/assets/theme.js +781 -186
  19. package/test-theme/layout/theme.liquid +91 -17
  20. package/test-theme/sections/content.liquid +64 -57
  21. package/test-theme/sections/footer-fallback.liquid +57 -7
  22. package/test-theme/sections/footer.liquid +63 -12
  23. package/test-theme/sections/header-fallback.liquid +41 -41
  24. package/test-theme/sections/header.liquid +41 -51
  25. package/test-theme/sections/hero-fallback.liquid +1 -1
  26. package/test-theme/sections/hero.liquid +159 -136
  27. package/test-theme/snippets/account-sidebar.liquid +121 -29
  28. package/test-theme/snippets/add-to-cart-modal.liquid +258 -206
  29. package/test-theme/snippets/breadcrumbs.liquid +98 -11
  30. package/test-theme/snippets/cart-drawer.liquid +93 -0
  31. package/test-theme/snippets/delivery-zone-city-selector.liquid +101 -15
  32. package/test-theme/snippets/delivery-zone-modal.liquid +529 -84
  33. package/test-theme/snippets/delivery-zone-search.liquid +104 -18
  34. package/test-theme/snippets/login-modal.liquid +269 -82
  35. package/test-theme/snippets/mega-menu.liquid +130 -43
  36. package/test-theme/snippets/news-thumbnail.liquid +120 -28
  37. package/test-theme/snippets/pagination.liquid +1 -1
  38. package/test-theme/snippets/price.liquid +100 -9
  39. package/test-theme/snippets/product-card-related.liquid +22 -4
  40. package/test-theme/snippets/product-card-simple.liquid +521 -25
  41. package/test-theme/snippets/product-card.liquid +145 -232
  42. package/test-theme/snippets/rating.liquid +100 -9
  43. package/test-theme/snippets/skeleton-collection-grid.liquid +94 -8
  44. package/test-theme/snippets/skeleton-product-card.liquid +102 -16
  45. package/test-theme/snippets/skeleton-product-grid.liquid +87 -1
  46. package/test-theme/snippets/social-sharing.liquid +133 -32
  47. package/test-theme/templates/account/dashboard.liquid +30 -0
  48. package/test-theme/templates/account/loyalty-redemption.liquid +29 -28
  49. package/test-theme/templates/account/loyalty.liquid +45 -43
  50. package/test-theme/templates/account/order-detail.liquid +15 -8
  51. package/test-theme/templates/account/orders.liquid +189 -35
  52. package/test-theme/templates/account/profile.liquid +509 -114
  53. package/test-theme/templates/account/register.liquid +18 -8
  54. package/test-theme/templates/account/return-orders.liquid +31 -30
  55. package/test-theme/templates/account/store-credit.liquid +27 -26
  56. package/test-theme/templates/account/subscriptions.liquid +22 -5
  57. package/test-theme/templates/account/wishlist.liquid +88 -19
  58. package/test-theme/templates/address-book.liquid +166 -69
  59. package/test-theme/templates/categories.liquid +90 -30
  60. package/test-theme/templates/checkout.liquid +137 -3834
  61. package/test-theme/templates/error.liquid +23 -21
  62. package/test-theme/templates/index.liquid +29 -0
  63. package/test-theme/templates/login.liquid +33 -6
  64. package/test-theme/templates/order-confirmation.liquid +67 -9
  65. package/test-theme/templates/page.liquid +418 -206
  66. package/test-theme/templates/product-detail.liquid +124 -3878
  67. package/test-theme/templates/products.liquid +155 -30
  68. package/test-theme/templates/search.liquid +739 -225
  69. package/test-theme/widgets/brand-carousel.liquid +102 -82
  70. package/test-theme/widgets/brand.liquid +78 -50
  71. package/test-theme/widgets/carousel.liquid +253 -121
  72. package/test-theme/widgets/category-list-carousel.liquid +32 -8
  73. package/test-theme/widgets/category-list.liquid +21 -6
  74. package/test-theme/widgets/category.liquid +104 -37
  75. package/test-theme/widgets/discount-time.liquid +326 -119
  76. package/test-theme/widgets/footer-menu.liquid +115 -23
  77. package/test-theme/widgets/footer.liquid +118 -5
  78. package/test-theme/widgets/gallery.liquid +29 -5
  79. package/test-theme/widgets/header-menu.liquid +25 -13
  80. package/test-theme/widgets/header.liquid +64 -26
  81. package/test-theme/widgets/html.liquid +29 -6
  82. package/test-theme/widgets/news.liquid +6 -0
  83. package/test-theme/widgets/product-canvas.liquid +20 -12
  84. package/test-theme/widgets/product-carousel.liquid +118 -56
  85. package/test-theme/widgets/shared/product-grid.liquid +12 -0
  86. package/test-theme/widgets/single-product.liquid +688 -250
  87. package/test-theme/widgets/spacebar-carousel.liquid +39 -10
  88. package/test-theme/widgets/spacebar.liquid +77 -6
  89. package/test-theme/widgets/splash.liquid +40 -30
  90. package/test-theme/widgets/testimonial-carousel.liquid +111 -67
@@ -1,176 +1,383 @@
1
- {% liquid
2
- assign widget_settings = widget.settings
3
- assign widget_content = widget.content
4
- assign widget_data = widget.data
1
+ {% assign widget_settings = widget.settings %}
2
+ {% assign widget_data = widget.data %}
3
+ {% assign coupon = widget_data.coupon %}
5
4
 
6
- assign heading = widget_settings.title | default: widget_content.title | default: 'Limited time offer'
7
- assign subheading = widget_settings.subtitle | default: widget_content.subtitle
8
- assign end_time = widget_settings.ends_at | default: widget_content.ends_at | default: widget_data.ends_at
9
- assign highlight_text = widget_settings.highlight | default: widget_content.highlight
10
- assign background_image = widget_settings.background_image | default: widget_content.background_image
11
- %}
5
+ {% if widget_data.couponValid == true and coupon != null %}
6
+ {% assign discount_amount = coupon.discountAmount | default: 0 %}
7
+ {% assign coupon_code = coupon.couponCode | default: '' %}
8
+ {% assign description = coupon.description | default: '' %}
9
+ {% assign end_on = coupon.endOn | default: '' %}
10
+ {% assign start_on = coupon.startOn | default: '' %}
11
+ {% assign rule_to_apply = coupon.ruleToApply | default: 'cart_fixed' %}
12
+
13
+ {% comment %}Background image: Use FileName first, fallback to ImageUrl{% endcomment %}
14
+ {% assign background_image = coupon.fileName | default: coupon.imageUrl | default: '' %}
15
+
16
+ {% comment %}Get currency symbol from shop settings or theme settings{% endcomment %}
17
+ {% assign currency_symbol = shop.settings.currencySymbol | default: settings.currencySymbol | default: '$' %}
18
+
19
+ {% comment %}Format discount display based on RuleToApply{% endcomment %}
20
+ {% if rule_to_apply == 'by_percent' %}
21
+ {% assign discount_display = discount_amount | append: '%' %}
22
+ {% else %}
23
+ {% assign discount_display = currency_symbol | append: discount_amount | round: 2 %}
24
+ {% endif %}
12
25
 
13
- <section class="widget widget-discount-time" data-widget-id="{{ widget.id }}" data-countdown="{{ end_time }}">
14
- <div class="widget-discount-time__background" {% if background_image %}style="background-image: url('{{ background_image }}');"{% endif %}></div>
15
- <div class="widget-discount-time__overlay"></div>
26
+ <section class="widget widget-discount-time" data-widget-id="{{ widget.id }}" data-countdown="{{ end_on }}">
27
+ {% if background_image != '' %}
28
+ <div class="widget-discount-time__background" style="background-image: url('{{ background_image }}');"></div>
29
+ {% endif %}
16
30
  <div class="widget-discount-time__content">
17
- <h2>{{ heading }}</h2>
18
- {% if subheading %}
19
- <p>{{ subheading }}</p>
20
- {% endif %}
21
- {% if highlight_text %}
22
- <span class="widget-discount-time__highlight">{{ highlight_text }}</span>
23
- {% endif %}
24
- {% if end_time %}
25
- <div class="widget-discount-time__countdown" data-countdown-display>
26
- <div><strong data-countdown-days>00</strong><span>Days</span></div>
27
- <div><strong data-countdown-hours>00</strong><span>Hours</span></div>
28
- <div><strong data-countdown-minutes>00</strong><span>Minutes</span></div>
29
- <div><strong data-countdown-seconds>00</strong><span>Seconds</span></div>
30
- </div>
31
- {% endif %}
32
- {% if widget_settings.cta_label %}
33
- <a href="{{ widget_settings.cta_link | default: '#' }}" class="widget-discount-time__cta">
34
- {{ widget_settings.cta_label }}
35
- </a>
36
- {% endif %}
31
+ <div class="widget-discount-time__inner">
32
+ {% if discount_amount > 0 %}
33
+ <div class="widget-discount-time__discount">
34
+ <span class="widget-discount-time__discount-amount">{{ discount_display }}</span>
35
+ <span class="widget-discount-time__discount-label">OFF</span>
36
+ </div>
37
+ {% endif %}
38
+
39
+ {% if coupon_code != '' %}
40
+ <div class="widget-discount-time__code">
41
+ <span class="widget-discount-time__code-label">Use Code:</span>
42
+ <span class="widget-discount-time__code-value">{{ coupon_code }}</span>
43
+ </div>
44
+ {% endif %}
45
+
46
+ {% if description != '' %}
47
+ <p class="widget-discount-time__description">{{ description }}</p>
48
+ {% endif %}
49
+
50
+ {% if end_on != '' %}
51
+ <div class="widget-discount-time__countdown" data-countdown-display>
52
+ <div class="widget-discount-time__countdown-item">
53
+ <strong data-countdown-days>00</strong>
54
+ <span>Days</span>
55
+ </div>
56
+ <div class="widget-discount-time__countdown-item">
57
+ <strong data-countdown-hours>00</strong>
58
+ <span>Hours</span>
59
+ </div>
60
+ <div class="widget-discount-time__countdown-item">
61
+ <strong data-countdown-minutes>00</strong>
62
+ <span>Minutes</span>
63
+ </div>
64
+ <div class="widget-discount-time__countdown-item">
65
+ <strong data-countdown-seconds>00</strong>
66
+ <span>Seconds</span>
67
+ </div>
68
+ </div>
69
+ {% endif %}
70
+ </div>
37
71
  </div>
38
72
 
39
73
  <style>
74
+ :root {
75
+ --discount-time-white: var(--color-white);
76
+ --discount-time-spacing-component: var(--spacing-component);
77
+ --discount-time-spacing-element: var(--spacing-element);
78
+ --discount-time-radius-full: var(--radius-full);
79
+ }
80
+
40
81
  .widget-discount-time {
41
82
  position: relative;
42
- padding: 4rem 1rem;
43
- color: {{ widget_settings.text_color | default: '#ffffff' }};
83
+ width: 100%;
84
+ min-height: 400px;
85
+ padding: 3rem 2rem;
86
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
44
87
  overflow: hidden;
45
- border-radius: var(--border-radius-medium);
46
- margin: 2rem auto;
47
- max-width: {{ settings.container_width }}px;
88
+ border-radius: var(--border-radius-medium, 8px);
89
+ margin: var(--spacing-large, 2rem) 0;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: flex-start;
48
93
  }
94
+
49
95
  .widget-discount-time__background {
50
96
  position: absolute;
51
97
  inset: 0;
52
- background: linear-gradient(135deg, {{ widget_settings.background_from | default: '#1f2937' }}, {{ widget_settings.background_to | default: '#2563eb' }});
53
98
  background-size: cover;
54
99
  background-position: center;
55
- filter: saturate(1.1);
56
- }
57
- .widget-discount-time__overlay {
58
- position: absolute;
59
- inset: 0;
60
- background: rgba(15, 23, 42, 0.4);
100
+ background-repeat: no-repeat;
101
+ z-index: 1;
61
102
  }
103
+
62
104
  .widget-discount-time__content {
63
105
  position: relative;
64
106
  z-index: 2;
65
- text-align: center;
66
- max-width: 640px;
107
+ width: 100%;
108
+ max-width: 1200px;
67
109
  margin: 0 auto;
110
+ padding: 0;
68
111
  }
69
- .widget-discount-time h2 {
70
- font-size: clamp(2rem, 4vw, 2.8rem);
71
- margin: 0 0 1rem;
112
+
113
+ .widget-discount-time__inner {
114
+ display: flex;
115
+ flex-direction: column;
116
+ align-items: flex-start;
117
+ gap: 1.5rem;
118
+ max-width: 600px;
72
119
  }
73
- .widget-discount-time p {
74
- margin: 0 0 1.5rem;
75
- font-size: 1.1rem;
76
- opacity: 0.9;
120
+
121
+ .widget-discount-time__discount {
122
+ display: flex;
123
+ align-items: baseline;
124
+ gap: 0.75rem;
125
+ margin: 0;
126
+ line-height: 1;
127
+ }
128
+
129
+ .widget-discount-time__discount-amount {
130
+ font-size: clamp(4rem, 8vw, 6.5rem);
131
+ font-weight: 900;
132
+ line-height: 1;
133
+ color: var(--color-primary, {{ settings.color_primary | default: '#1a1a1a' }});
134
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
77
135
  }
78
- .widget-discount-time__highlight {
136
+
137
+ .widget-discount-time__discount-label {
138
+ font-size: clamp(2rem, 4vw, 3rem);
139
+ font-weight: 700;
140
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
141
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
142
+ }
143
+
144
+ .widget-discount-time__code {
79
145
  display: inline-flex;
80
146
  align-items: center;
81
- gap: 0.5rem;
82
- padding: 0.5rem 1rem;
83
- border-radius: 999px;
84
- background: rgba(255, 255, 255, 0.15);
85
- font-weight: 600;
86
- margin-bottom: 2rem;
147
+ gap: 0.75rem;
148
+ padding: 0.875rem 1.75rem;
149
+ border-radius: 50px;
150
+ background: rgba(255, 255, 255, 0.2);
151
+ backdrop-filter: blur(10px);
152
+ -webkit-backdrop-filter: blur(10px);
153
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
154
+ }
155
+
156
+ .widget-discount-time__code-label {
157
+ font-size: 0.95rem;
158
+ font-weight: 500;
159
+ opacity: 0.95;
160
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
161
+ }
162
+
163
+ .widget-discount-time__code-value {
164
+ font-size: 1.1rem;
165
+ font-weight: 700;
166
+ letter-spacing: 0.05em;
167
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
168
+ }
169
+
170
+ .widget-discount-time__description {
171
+ margin: 0;
172
+ font-size: clamp(1rem, 2vw, 1.15rem);
173
+ opacity: 0.95;
174
+ max-width: 500px;
175
+ line-height: 1.6;
176
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
177
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
87
178
  }
179
+
88
180
  .widget-discount-time__countdown {
89
- display: inline-flex;
90
- gap: 1rem;
91
- margin: 2.5rem 0;
92
- padding: 0.5rem 1rem;
93
- border-radius: 999px;
94
- background: rgba(255, 255, 255, 0.12);
95
- backdrop-filter: blur(8px);
96
- }
97
- .widget-discount-time__countdown div {
181
+ display: flex;
182
+ gap: 1.25rem;
183
+ margin: 0.5rem 0 0;
184
+ padding: 1.25rem 2rem;
185
+ border-radius: 50px;
186
+ background: rgba(255, 255, 255, 0.15);
187
+ backdrop-filter: blur(10px);
188
+ -webkit-backdrop-filter: blur(10px);
189
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
190
+ }
191
+
192
+ .widget-discount-time__countdown-item {
98
193
  display: flex;
99
194
  flex-direction: column;
100
195
  align-items: center;
101
- min-width: 70px;
196
+ gap: 0.5rem;
197
+ min-width: 75px;
102
198
  }
103
- .widget-discount-time__countdown strong {
104
- font-size: 1.75rem;
105
- font-weight: 700;
199
+
200
+ .widget-discount-time__countdown-item strong {
201
+ font-size: 2rem;
202
+ font-weight: 800;
203
+ line-height: 1;
204
+ color: var(--color-primary, {{ settings.color_primary | default: '#1a1a1a' }});
205
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
106
206
  }
107
- .widget-discount-time__countdown span {
207
+
208
+ .widget-discount-time__countdown-item span {
108
209
  font-size: 0.75rem;
210
+ font-weight: 600;
109
211
  text-transform: uppercase;
110
212
  letter-spacing: 0.1em;
213
+ opacity: 0.9;
214
+ color: var(--color-text, {{ settings.color_text | default: '#ffffff' }});
111
215
  }
112
- .widget-discount-time__cta {
113
- display: inline-flex;
114
- align-items: center;
115
- gap: 0.5rem;
116
- padding: 0.85rem 1.75rem;
117
- border-radius: 999px;
118
- background: {{ widget_settings.cta_background | default: '#ffffff' }};
119
- color: {{ widget_settings.cta_text | default: '#111827' }};
120
- text-decoration: none;
121
- font-weight: 600;
216
+
217
+ .widget-discount-time--expired {
218
+ display: none;
122
219
  }
123
- @media (max-width: 640px) {
220
+
221
+ @media (max-width: 768px) {
222
+ .widget-discount-time {
223
+ min-height: 350px;
224
+ padding: 2rem 1.5rem;
225
+ justify-content: center;
226
+ }
227
+
228
+ .widget-discount-time__inner {
229
+ align-items: center;
230
+ text-align: center;
231
+ max-width: 100%;
232
+ }
233
+
234
+ .widget-discount-time__discount {
235
+ flex-direction: column;
236
+ align-items: center;
237
+ gap: 0.5rem;
238
+ }
239
+
124
240
  .widget-discount-time__countdown {
125
241
  flex-wrap: wrap;
126
242
  justify-content: center;
243
+ gap: 1rem;
244
+ padding: 1rem 1.5rem;
245
+ }
246
+
247
+ .widget-discount-time__countdown-item {
248
+ min-width: 65px;
249
+ }
250
+
251
+ .widget-discount-time__countdown-item strong {
252
+ font-size: 1.75rem;
253
+ }
254
+ }
255
+
256
+ @media (max-width: 480px) {
257
+ .widget-discount-time {
258
+ padding: 1.5rem 1rem;
259
+ }
260
+
261
+ .widget-discount-time__countdown {
127
262
  gap: 0.75rem;
263
+ padding: 0.875rem 1.25rem;
128
264
  }
129
- .widget-discount-time__countdown div {
130
- min-width: 60px;
265
+
266
+ .widget-discount-time__countdown-item {
267
+ min-width: 55px;
268
+ }
269
+
270
+ .widget-discount-time__countdown-item strong {
271
+ font-size: 1.5rem;
131
272
  }
132
273
  }
133
274
  </style>
134
275
 
135
- {% if end_time %}
276
+ {% if end_on != '' %}
136
277
  <script>
137
- document.addEventListener('DOMContentLoaded', function() {
138
- const widget = document.querySelector('[data-widget-id="{{ widget.id }}"]');
139
- if (!widget) return;
278
+ (() => {
279
+ function initCountdown() {
280
+ const widget = document.querySelector('[data-widget-id="{{ widget.id }}"]');
281
+ if (!widget) {
282
+ // Retry if DOM not ready
283
+ if (document.readyState === 'loading') {
284
+ document.addEventListener('DOMContentLoaded', initCountdown);
285
+ }
286
+ return;
287
+ }
140
288
 
141
- const targetDate = new Date(widget.dataset.countdown);
142
- if (!targetDate.getTime()) return;
289
+ const countdownDisplay = widget.querySelector('[data-countdown-display]');
290
+ if (!countdownDisplay) return;
143
291
 
144
- const parts = {
145
- days: widget.querySelector('[data-countdown-days]'),
146
- hours: widget.querySelector('[data-countdown-hours]'),
147
- minutes: widget.querySelector('[data-countdown-minutes]'),
148
- seconds: widget.querySelector('[data-countdown-seconds]')
149
- };
292
+ const endOnValue = '{{ end_on }}';
293
+ if (!endOnValue || endOnValue === '') {
294
+ console.warn('[DiscountTime] No end date provided');
295
+ return;
296
+ }
297
+
298
+ // Parse the date - handle ISO format and other formats
299
+ let targetDate;
300
+ try {
301
+ // Try parsing as-is first
302
+ targetDate = new Date(endOnValue);
303
+
304
+ // If invalid, try adding timezone info if missing
305
+ if (isNaN(targetDate.getTime())) {
306
+ // Try appending timezone if it's just a date
307
+ if (endOnValue.match(/^\d{4}-\d{2}-\d{2}$/)) {
308
+ targetDate = new Date(endOnValue + 'T23:59:59');
309
+ } else if (endOnValue.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)) {
310
+ // Add timezone if missing
311
+ targetDate = new Date(endOnValue + 'Z');
312
+ } else {
313
+ targetDate = new Date(endOnValue);
314
+ }
315
+ }
316
+ } catch (e) {
317
+ console.error('[DiscountTime] Error parsing date:', endOnValue, e);
318
+ return;
319
+ }
150
320
 
151
- const tick = () => {
152
- const now = new Date();
153
- let diff = targetDate - now;
154
- if (diff <= 0) {
155
- diff = 0;
156
- clearInterval(timer);
321
+ if (!targetDate.getTime() || isNaN(targetDate.getTime())) {
322
+ console.warn('[DiscountTime] Invalid end date format:', endOnValue);
157
323
  widget.classList.add('widget-discount-time--expired');
324
+ widget.style.display = 'none';
325
+ return;
158
326
  }
159
- const seconds = Math.floor((diff / 1000) % 60);
160
- const minutes = Math.floor((diff / (1000 * 60)) % 60);
161
- const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
162
- const days = Math.floor(diff / (1000 * 60 * 60 * 24));
163
327
 
164
- if (parts.days) parts.days.textContent = String(days).padStart(2, '0');
165
- if (parts.hours) parts.hours.textContent = String(hours).padStart(2, '0');
166
- if (parts.minutes) parts.minutes.textContent = String(minutes).padStart(2, '0');
167
- if (parts.seconds) parts.seconds.textContent = String(seconds).padStart(2, '0');
168
- };
328
+ const parts = {
329
+ days: widget.querySelector('[data-countdown-days]'),
330
+ hours: widget.querySelector('[data-countdown-hours]'),
331
+ minutes: widget.querySelector('[data-countdown-minutes]'),
332
+ seconds: widget.querySelector('[data-countdown-seconds]')
333
+ };
169
334
 
170
- tick();
171
- const timer = setInterval(tick, 1000);
172
- });
335
+ // Check if all parts exist
336
+ if (!parts.days || !parts.hours || !parts.minutes || !parts.seconds) {
337
+ console.warn('[DiscountTime] Countdown elements not found');
338
+ return;
339
+ }
340
+
341
+ let timer = null;
342
+
343
+ const tick = () => {
344
+ const now = new Date();
345
+ let diff = targetDate.getTime() - now.getTime();
346
+
347
+ if (diff <= 0) {
348
+ diff = 0;
349
+ if (timer) clearInterval(timer);
350
+ widget.classList.add('widget-discount-time--expired');
351
+ widget.style.display = 'none';
352
+ return;
353
+ }
354
+
355
+ const seconds = Math.floor((diff / 1000) % 60);
356
+ const minutes = Math.floor((diff / (1000 * 60)) % 60);
357
+ const hours = Math.floor((diff / (1000 * 60 * 60)) % 24);
358
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
359
+
360
+ if (parts.days) parts.days.textContent = String(days).padStart(2, '0');
361
+ if (parts.hours) parts.hours.textContent = String(hours).padStart(2, '0');
362
+ if (parts.minutes) parts.minutes.textContent = String(minutes).padStart(2, '0');
363
+ if (parts.seconds) parts.seconds.textContent = String(seconds).padStart(2, '0');
364
+ };
365
+
366
+ // Initial tick
367
+ tick();
368
+
369
+ // Set up interval
370
+ timer = setInterval(tick, 1000);
371
+ }
372
+
373
+ // Initialize when DOM is ready
374
+ if (document.readyState === 'loading') {
375
+ document.addEventListener('DOMContentLoaded', initCountdown);
376
+ } else {
377
+ initCountdown();
378
+ }
379
+ })();
173
380
  </script>
174
381
  {% endif %}
175
382
  </section>
176
-
383
+ {% endif %}