@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
@@ -6,25 +6,56 @@
6
6
 
7
7
  {% liquid
8
8
  assign widget_settings = widget.settings
9
+ assign widget_content = widget.content
9
10
  assign widget_data = widget.data
10
11
 
11
12
  comment
12
13
  Static Widget: Content comes from widget_data.content (header configuration)
13
14
  Settings come from widget_settings
14
15
  endcomment
15
-
16
+
17
+ if widget.htmlData
18
+ assign html_content = widget.htmlData
19
+ elsif widget.HtmlData
20
+ assign html_content = widget.HtmlData
21
+ elsif widget.htmlContent
22
+ assign html_content = widget.htmlContent
23
+ elsif widget.HtmlContent
24
+ assign html_content = widget.HtmlContent
25
+ elsif widget_data.htmlData
26
+ assign html_content = widget_data.htmlData
27
+ elsif widget_data.htmlContent
28
+ assign html_content = widget_data.htmlContent
29
+ elsif widget_content.Html
30
+ assign html_content = widget_content.Html
31
+ elsif widget_content.html
32
+ assign html_content = widget_content.html
33
+ elsif widget_content.HtmlContent
34
+ assign html_content = widget_content.HtmlContent
35
+ elsif widget_content.htmlContent
36
+ assign html_content = widget_content.htmlContent
37
+ elsif widget_content.Body
38
+ assign html_content = widget_content.Body
39
+ elsif widget_content.body
40
+ assign html_content = widget_content.body
41
+ elsif widget_content.Content
42
+ assign html_content = widget_content.Content
43
+ elsif widget_content.content
44
+ assign html_content = widget_content.content
45
+ elsif widget_settings.html
46
+ assign html_content = widget_settings.html
47
+ elsif widget_data.html
48
+ assign html_content = widget_data.html
49
+ else
50
+ assign html_content = ''
51
+ endif
52
+
16
53
  assign content_data = widget_data.content | default: widget.content
17
54
  if content_data.size > 0
18
55
  assign header_config = content_data.first
19
56
  else
20
57
  assign header_config = content_data
21
58
  endif
22
-
23
- assign html_content_raw = header_config.HtmlContent | default: header_config.htmlContent
24
- assign html_content = blank
25
- if html_content_raw and html_content_raw != blank and html_content_raw != 'null'
26
- assign html_content = html_content_raw
27
- endif
28
59
  assign hide_close_button = widget_settings.hideCloseButton
29
60
  if hide_close_button == nil and header_config and header_config.HideCloseButton != nil
30
61
  assign hide_close_button = header_config.HideCloseButton
@@ -38,26 +69,33 @@
38
69
  assign text_color = header_config.TextColor
39
70
  endif
40
71
  %}
41
-
42
- {% if html_content and html_content != blank and html_content != 'null' %}
43
- <div class="widget-header-announcement" data-widget-id="{{ widget.id }}">
44
- <div class="widget-header-announcement__bar"{% if background_color and background_color != 'null' and background_color != blank %} style="background-color: {{ background_color }};"{% endif %}>
45
- <div class="widget-header-announcement__content"{% if text_color and text_color != 'null' and text_color != blank %} style="color: {{ text_color }};"{% endif %}>
46
- {{ html_content }}
47
- </div>
48
- {% unless hide_close_button %}
49
- <button class="widget-header-announcement__close" aria-label="Close announcement" data-announcement-close>
50
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
51
- <line x1="18" y1="6" x2="6" y2="18"></line>
52
- <line x1="6" y1="6" x2="18" y2="18"></line>
53
- </svg>
54
- </button>
55
- {% endunless %}
72
+ {% if html_content and html_content != blank %}
73
+ <div class="widget-header-announcement" data-widget-id="{{ widget.id }}">
74
+ <div class="widget-header-announcement__bar"{% if background_color and background_color != 'null' and background_color != blank %} style="background-color: {{ background_color }};"{% endif %}>
75
+ <div class="widget-header-announcement__content"{% if text_color and text_color != 'null' and text_color != blank %} style="color: {{ text_color }};"{% endif %}>
76
+ {{ html_content }}
56
77
  </div>
78
+ {% unless hide_close_button %}
79
+ <button class="widget-header-announcement__close" aria-label="Close announcement" data-announcement-close>
80
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
81
+ <line x1="18" y1="6" x2="6" y2="18"></line>
82
+ <line x1="6" y1="6" x2="18" y2="18"></line>
83
+ </svg>
84
+ </button>
85
+ {% endunless %}
57
86
  </div>
87
+ </div>
58
88
  {% endif %}
59
-
60
89
  <style>
90
+ :root {
91
+ --header-white: var(--color-white);
92
+ --header-text: var(--color-text);
93
+ --header-spacing-element: var(--spacing-component);
94
+ --header-spacing-small: var(--spacing-small);
95
+ --header-transition-fast: var(--transition-fast);
96
+ --header-text-sm: var(--text-sm);
97
+ }
98
+
61
99
  /* Announcement Bar Styles */
62
100
  .widget-header-announcement__bar {
63
101
  display: flex;
@@ -131,11 +169,11 @@
131
169
 
132
170
  <script>
133
171
  // Handle announcement bar close button
134
- (function() {
172
+ (() => {
135
173
  const announcementCloseBtn = document.querySelector('[data-announcement-close]');
136
174
  if (announcementCloseBtn) {
137
- announcementCloseBtn.addEventListener('click', function() {
138
- const announcement = this.closest('.widget-header-announcement__bar');
175
+ announcementCloseBtn.addEventListener('click', () => {
176
+ const announcement = announcementCloseBtn.closest('.widget-header-announcement__bar');
139
177
  if (announcement) {
140
178
  announcement.classList.add('hidden');
141
179
  // Store in sessionStorage to remember it's closed for this session
@@ -7,17 +7,25 @@
7
7
 
8
8
  comment
9
9
  HTML content priority order:
10
- 1. HtmlContent / htmlContent - direct field from widget (highest priority)
11
- 2. widget.data.htmlContent - normalized HtmlContent
12
- 3. widget_content.HtmlContent / htmlContent - from content JSON
13
- 4. widget_content.Html / html - from content JSON
14
- 5. Other fallbacks for backward compatibility
10
+ 1. htmlData / HtmlData - separate property saved via editor (highest priority)
11
+ 2. HtmlContent / htmlContent - direct field from widget
12
+ 3. widget.data.htmlContent - normalized HtmlContent
13
+ 4. widget.data.htmlData - normalized HtmlData
14
+ 5. widget_content.HtmlContent / htmlContent - from content JSON
15
+ 6. widget_content.Html / html - from content JSON
16
+ 7. Other fallbacks for backward compatibility
15
17
  endcomment
16
18
 
17
- if widget.htmlContent
19
+ if widget.htmlData
20
+ assign html_content = widget.htmlData
21
+ elsif widget.HtmlData
22
+ assign html_content = widget.HtmlData
23
+ elsif widget.htmlContent
18
24
  assign html_content = widget.htmlContent
19
25
  elsif widget.HtmlContent
20
26
  assign html_content = widget.HtmlContent
27
+ elsif widget_data.htmlData
28
+ assign html_content = widget_data.htmlData
21
29
  elsif widget_data.htmlContent
22
30
  assign html_content = widget_data.htmlContent
23
31
  elsif widget_content.Html
@@ -87,6 +95,21 @@
87
95
  </div>
88
96
 
89
97
  <style>
98
+ :root {
99
+ --html-white: var(--color-white);
100
+ --html-text: var(--color-text);
101
+ --html-text-muted: var(--color-text-muted);
102
+ --html-border: var(--color-border);
103
+ --html-spacing-section: var(--spacing-section);
104
+ --html-spacing-component: var(--spacing-component);
105
+ --html-spacing-element: var(--spacing-element);
106
+ --html-spacing-xsmall: var(--spacing-xsmall);
107
+ --html-container-padding: var(--container-padding);
108
+ --html-radius-lg: var(--radius-lg);
109
+ --html-radius: var(--radius);
110
+ --html-text-sm: var(--text-sm);
111
+ }
112
+
90
113
  .widget-html {
91
114
  padding: {{ padding_top | default: '40' }}px 0 {{ padding_bottom | default: '40' }}px;
92
115
  }
@@ -114,6 +114,12 @@
114
114
  {% endif %}
115
115
 
116
116
  <style>
117
+ :root {
118
+ --news-spacing-section: var(--spacing-section);
119
+ --news-spacing-component: var(--spacing-component);
120
+ --news-text-sm: var(--text-sm);
121
+ }
122
+
117
123
  .widget-news {
118
124
  padding: 40px 0;
119
125
  }
@@ -89,6 +89,16 @@
89
89
  </section>
90
90
 
91
91
  <style>
92
+ :root {
93
+ --product-canvas-white: var(--color-white);
94
+ --product-canvas-text: var(--color-text);
95
+ --product-canvas-spacing-element: var(--spacing-element);
96
+ --product-canvas-spacing-small: var(--spacing-small);
97
+ --product-canvas-container-padding: var(--container-padding);
98
+ --product-canvas-radius-full: var(--radius-full);
99
+ --product-canvas-transition: var(--transition);
100
+ }
101
+
92
102
  /* Product Canvas Widget */
93
103
  .widget-product-canvas {
94
104
  width: 100%;
@@ -120,13 +130,12 @@
120
130
  width: 48px;
121
131
  height: 48px;
122
132
  background: rgba(255, 255, 255, 0.9);
123
- border: 2px solid {{ settings.color_text }};
133
+ border: 2px solid {{ settings.color_text | default: '#000000' }};
124
134
  border-radius: 50%;
125
- color: {{ settings.color_text }};
135
+ color: {{ settings.color_text | default: '#000000' }};
126
136
  text-decoration: none;
127
137
  transition: all 0.3s ease;
128
138
  z-index: 10;
129
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
130
139
  }
131
140
 
132
141
  .widget-product-canvas .anchor-icon svg {
@@ -135,10 +144,9 @@
135
144
  }
136
145
 
137
146
  .widget-product-canvas .anchor-icon:hover {
138
- background: {{ settings.color_text }};
139
- color: {{ settings.color_background }};
147
+ background: {{ settings.color_text | default: '#000000' }};
148
+ color: {{ settings.color_background | default: '#ffffff' }};
140
149
  transform: scale(1.15);
141
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
142
150
  }
143
151
 
144
152
  /* Pulse Grow on Hover Animation */
@@ -188,9 +196,9 @@
188
196
 
189
197
  /* Container */
190
198
  .widget-product-canvas .container {
191
- max-width: {{ settings.container_width }}px;
199
+ max-width: {{ settings.container_width | default: 1200 }}px;
192
200
  margin: 0 auto;
193
- padding: 0 {{ settings.container_padding }}px;
201
+ padding: 0 {{ settings.container_padding | default: 16 }}px;
194
202
  }
195
203
 
196
204
  /* Mobile Responsive */
@@ -206,7 +214,7 @@
206
214
  }
207
215
 
208
216
  .widget-product-canvas .container {
209
- padding: 0 {{ settings.spacing_element }}px;
217
+ padding: 0 {{ settings.spacing_element | default: 16 }}px;
210
218
  }
211
219
  }
212
220
 
@@ -222,14 +230,14 @@
222
230
  }
223
231
 
224
232
  .widget-product-canvas .container {
225
- padding: 0 {{ settings.spacing_small }}px;
233
+ padding: 0 {{ settings.spacing_small | default: 8 }}px;
226
234
  }
227
235
  }
228
236
  </style>
229
237
  {% else %}
230
238
  <section class="widget widget-product-canvas widget-product-canvas--empty" data-widget-id="{{ widget.id }}">
231
- <div class="widget-empty" style="padding: {{ settings.spacing_section }}px {{ settings.container_padding }}px; text-align: center;">
232
- <p style="color: {{ settings.color_text_muted }}; margin: 0;">{{ widget_settings.empty_state | default: 'Please configure an image URL for the product canvas.' }}</p>
239
+ <div class="widget-empty" style="padding: {{ settings.spacing_section | default: 64 }}px {{ settings.container_padding | default: 16 }}px; text-align: center;">
240
+ <p style="color: {{ settings.color_text_muted | default: '#666666' }}; margin: 0;">{{ widget_settings.empty_state | default: 'Please configure an image URL for the product canvas.' }}</p>
233
241
  </div>
234
242
  </section>
235
243
  {% endif %}
@@ -2,32 +2,51 @@
2
2
  assign widget_settings = widget.settings
3
3
  assign widget_data = widget.data
4
4
 
5
- comment
6
- Dynamic Widget: Products come from widget_data.products (enriched by WidgetService)
7
- Settings come from widget_settings
8
- endcomment
5
+ # Products
9
6
  assign products = widget_data.products
7
+
8
+ # Heading / Description
10
9
  assign heading = widget_settings.title
11
10
  assign description = widget_settings.subtitle
11
+
12
+ # Show title logic
12
13
  assign show_widget_title_raw = widget_settings.showWidgetTitle | default: 'Yes'
14
+
13
15
  if show_widget_title_raw == null or show_widget_title_raw == blank or show_widget_title_raw == 'null'
14
16
  assign show_widget_title = true
17
+ elsif show_widget_title_raw == 'Yes' or show_widget_title_raw == true
18
+ assign show_widget_title = true
15
19
  else
16
- if show_widget_title_raw == 'Yes'
17
- assign show_widget_title = true
18
- elsif show_widget_title_raw == true
19
- assign show_widget_title = true
20
- else
21
- assign show_widget_title = false
22
- endif
20
+ assign show_widget_title = false
23
21
  endif
22
+
23
+ # Title alignment
24
24
  assign widget_title_alignment_raw = widget_settings.widgetTitleAlignment | default: 'center'
25
25
  assign widget_title_alignment = widget_title_alignment_raw | downcase
26
+
26
27
  if widget_title_alignment != 'left' and widget_title_alignment != 'right' and widget_title_alignment != 'center'
27
28
  assign widget_title_alignment = 'center'
28
29
  endif
30
+
31
+ # Background
29
32
  assign background_color = widget_settings.backgroundColor
33
+
34
+ # Priority Count (clamped 0–12)
35
+ assign priority_count = widget_settings.priorityCount | default: widget_settings.prioritizeCount | default: widget_settings.priority | default: 1 | plus: 0
36
+
37
+ if priority_count > 12
38
+ assign priority_count = 12
39
+ endif
40
+
41
+ if priority_count < 0
42
+ assign priority_count = 0
43
+ endif
30
44
  %}
45
+ {% assign is_hero_priority_widget = false %}
46
+ {% if is_hero_first == true or is_hero_first == 'true' or is_hero_first == 1 or is_hero_first == '1' %}
47
+ {% assign is_hero_priority_widget = true %}
48
+ {% endif %}
49
+
31
50
 
32
51
  <section class="widget widget-product-carousel" data-widget-id="{{ widget.id }}" data-carousel-widget{% if background_color and background_color != blank and background_color != 'null' %} style="background-color: {{ background_color }};"{% endif %}>
33
52
  <div class="carousel-container">
@@ -73,7 +92,33 @@
73
92
  <div class="carousel-track" data-carousel-track>
74
93
  {% for product in products %}
75
94
  <div class="carousel-slide" data-slide-index="{{ forloop.index0 }}">
76
- {% render 'snippets/product-card', product: product, widget: widget %}
95
+ {% comment %} Determine product first image for preload (simple fallback) {% endcomment %}
96
+ {% assign product_image = nil %}
97
+ {% if product.thumbnailImage1 and product.thumbnailImage1.url %}
98
+ {% assign product_image = product.thumbnailImage1.url %}
99
+ {% elsif product.images and product.images.size > 0 %}
100
+ {% assign first_image = product.images | first %}
101
+ {% if first_image.url %}
102
+ {% assign product_image = first_image.url %}
103
+ {% endif %}
104
+ {% elsif product.thumbnailImage and product.thumbnailImage.url %}
105
+ {% assign product_image = product.thumbnailImage.url %}
106
+ {% endif %}
107
+
108
+ {% if forloop.index0 < priority_count %}
109
+ {% comment %} Preload only prioritized card image; avoid aggressive document/fetch preloads that can hurt LCP {% endcomment %}
110
+ {% if product_image and is_hero_priority_widget %}
111
+ <link rel="preload" as="image" href="{{ product_image }}" fetchpriority="high">
112
+ {% endif %}
113
+
114
+ {% if is_hero_priority_widget %}
115
+ {% render 'snippets/product-card', product: product, widget: widget, loading: 'eager', image_fetchpriority: 'high' %}
116
+ {% else %}
117
+ {% render 'snippets/product-card', product: product, widget: widget, loading: 'eager' %}
118
+ {% endif %}
119
+ {% else %}
120
+ {% render 'snippets/product-card', product: product, widget: widget %}
121
+ {% endif %}
77
122
  </div>
78
123
  {% endfor %}
79
124
  </div>
@@ -96,16 +141,34 @@
96
141
  </div>
97
142
 
98
143
  <style>
144
+ :root {
145
+ --product-carousel-white: var(--color-white);
146
+ --product-carousel-text: var(--color-text);
147
+ --product-carousel-text-muted: var(--color-text-muted);
148
+ --product-carousel-border: var(--color-border);
149
+ --product-carousel-spacing-section: var(--spacing-section);
150
+ --product-carousel-spacing-large: var(--spacing-large);
151
+ --product-carousel-spacing-component: var(--spacing-component);
152
+ --product-carousel-spacing-element: var(--spacing-element);
153
+ --product-carousel-spacing-small: var(--spacing-small);
154
+ --product-carousel-spacing-xsmall: var(--spacing-xsmall);
155
+ --product-carousel-container-padding: var(--container-padding);
156
+ --product-carousel-radius-full: var(--radius-full);
157
+ --product-carousel-radius: var(--radius);
158
+ --product-carousel-transition-fast: var(--transition-fast);
159
+ --product-carousel-text-sm: var(--text-sm);
160
+ }
161
+
99
162
  /* Product Carousel - True Carousel Behavior */
100
163
  .widget-product-carousel {
101
- padding: 48px 0;
164
+ padding: var(--product-carousel-spacing-section) 0;
102
165
  overflow: hidden;
103
166
  }
104
167
 
105
168
  .widget-product-carousel .carousel-container {
106
169
  max-width: 1400px;
107
170
  margin: 0 auto;
108
- padding: 0 24px;
171
+ padding: 0 var(--product-carousel-container-padding);
109
172
  }
110
173
 
111
174
  /* Header */
@@ -113,8 +176,8 @@
113
176
  display: flex;
114
177
  align-items: center;
115
178
  justify-content: space-between;
116
- margin-bottom: 28px;
117
- gap: 16px;
179
+ margin-bottom: var(--product-carousel-spacing-component);
180
+ gap: var(--product-carousel-spacing-element);
118
181
  position: static;
119
182
  z-index: auto;
120
183
  }
@@ -124,45 +187,45 @@
124
187
  }
125
188
 
126
189
  .widget-product-carousel .carousel-title {
127
- font-size: 1.4rem;
190
+ font-size: var(--product-carousel-text-sm);
128
191
  font-weight: 500;
129
192
  line-height: 1.3;
130
- color: #111;
193
+ color: var(--product-carousel-text);
131
194
  margin: 0;
132
195
  letter-spacing: -0.01em;
133
196
  }
134
197
 
135
198
  .widget-product-carousel .carousel-subtitle {
136
- font-size: 14px;
137
- color: #6b7280;
138
- margin: 6px 0 0 0;
199
+ font-size: var(--product-carousel-text-sm);
200
+ color: var(--product-carousel-text-muted);
201
+ margin: var(--product-carousel-spacing-xsmall) 0 0 0;
139
202
  }
140
203
 
141
204
  /* Header Nav Arrows */
142
205
  .widget-product-carousel .carousel-nav {
143
206
  display: flex;
144
- gap: 8px;
207
+ gap: var(--product-carousel-spacing-xsmall);
145
208
  }
146
209
 
147
210
  .widget-product-carousel .carousel-arrow {
148
211
  width: 40px;
149
212
  height: 40px;
150
- border-radius: 50%;
151
- border: 1px solid #e0e0e0;
152
- background: #fff;
213
+ border-radius: var(--product-carousel-radius-full);
214
+ border: 1px solid var(--product-carousel-border);
215
+ background: var(--product-carousel-white);
153
216
  display: flex;
154
217
  align-items: center;
155
218
  justify-content: center;
156
219
  cursor: pointer;
157
- color: #333;
158
- transition: all 0.2s ease;
220
+ color: var(--product-carousel-text);
221
+ transition: all var(--product-carousel-transition-fast);
159
222
  flex-shrink: 0;
160
223
  }
161
224
 
162
225
  .widget-product-carousel .carousel-arrow:hover:not(:disabled) {
163
- background: #111;
164
- color: #fff;
165
- border-color: #111;
226
+ background: var(--product-carousel-text);
227
+ color: var(--product-carousel-white);
228
+ border-color: var(--product-carousel-text);
166
229
  }
167
230
 
168
231
  .widget-product-carousel .carousel-arrow:disabled {
@@ -175,7 +238,7 @@
175
238
  position: relative;
176
239
  display: flex;
177
240
  align-items: center;
178
- gap: 12px;
241
+ gap: var(--product-carousel-spacing-small);
179
242
  }
180
243
 
181
244
  /* Side Navigation Arrows */
@@ -183,22 +246,21 @@
183
246
  display: none; /* Hidden by default, shown on desktop */
184
247
  width: 30px;
185
248
  height: 30px;
186
- border-radius: 50%;
249
+ border-radius: var(--product-carousel-radius-full);
187
250
  border: none;
188
- background: #fff;
189
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
251
+ background: var(--product-carousel-white);
190
252
  align-items: center;
191
253
  justify-content: center;
192
254
  cursor: pointer;
193
- color: #333;
194
- transition: all 0.2s ease;
255
+ color: var(--product-carousel-text);
256
+ transition: all var(--product-carousel-transition-fast);
195
257
  flex-shrink: 0;
196
258
  z-index: 10;
197
259
  }
198
260
 
199
261
  .widget-product-carousel .carousel-side-arrow:hover:not(:disabled) {
200
- background: #111;
201
- color: #fff;
262
+ background: var(--product-carousel-text);
263
+ color: var(--product-carousel-white);
202
264
  transform: scale(1.05);
203
265
  }
204
266
 
@@ -217,7 +279,7 @@
217
279
  /* The Scrolling Track */
218
280
  .widget-product-carousel .carousel-track {
219
281
  display: flex !important;
220
- gap: 20px;
282
+ gap: var(--product-carousel-spacing-component);
221
283
  overflow-x: auto !important;
222
284
  overflow-y: hidden !important;
223
285
  scroll-snap-type: x mandatory;
@@ -225,7 +287,7 @@
225
287
  -webkit-overflow-scrolling: touch;
226
288
  scrollbar-width: none;
227
289
  -ms-overflow-style: none;
228
- padding: 4px 0;
290
+ padding: var(--product-carousel-spacing-xsmall) 0;
229
291
  }
230
292
 
231
293
  .widget-product-carousel .carousel-track::-webkit-scrollbar {
@@ -253,29 +315,29 @@
253
315
  .widget-product-carousel .carousel-dots {
254
316
  display: flex;
255
317
  justify-content: center;
256
- gap: 8px;
257
- margin-top: 20px;
318
+ gap: var(--product-carousel-spacing-xsmall);
319
+ margin-top: var(--product-carousel-spacing-component);
258
320
  }
259
321
 
260
322
  .widget-product-carousel .carousel-dot {
261
323
  width: 8px;
262
324
  height: 8px;
263
- border-radius: 50%;
264
- background: #d1d5db;
325
+ border-radius: var(--product-carousel-radius-full);
326
+ background: var(--product-carousel-border);
265
327
  border: none;
266
328
  padding: 0;
267
329
  cursor: pointer;
268
- transition: all 0.2s ease;
330
+ transition: all var(--product-carousel-transition-fast);
269
331
  }
270
332
 
271
333
  .widget-product-carousel .carousel-dot:hover {
272
- background: #9ca3af;
334
+ background: var(--product-carousel-text-muted);
273
335
  }
274
336
 
275
337
  .widget-product-carousel .carousel-dot.active {
276
- background: #111;
338
+ background: var(--product-carousel-text);
277
339
  width: 24px;
278
- border-radius: 4px;
340
+ border-radius: var(--product-carousel-radius);
279
341
  }
280
342
 
281
343
  /* Desktop: Show side arrows, hide header arrows */
@@ -295,23 +357,23 @@
295
357
  max-width: calc(33.333% - 14px) !important;
296
358
  }
297
359
  .widget-product-carousel .carousel-track {
298
- gap: 20px;
360
+ gap: var(--product-carousel-spacing-component);
299
361
  }
300
362
  }
301
363
 
302
364
  /* Mobile: 2 items */
303
365
  @media (max-width: 768px) {
304
366
  .widget-product-carousel {
305
- padding: 36px 0;
367
+ padding: var(--product-carousel-spacing-large) 0;
306
368
  }
307
369
  .widget-product-carousel .carousel-container {
308
- padding: 0 16px;
370
+ padding: 0 var(--product-carousel-spacing-element);
309
371
  }
310
372
  .widget-product-carousel .carousel-title {
311
373
  font-size: 1.3rem;
312
374
  }
313
375
  .widget-product-carousel .carousel-track {
314
- gap: 12px;
376
+ gap: var(--product-carousel-spacing-small);
315
377
  padding-left: 0;
316
378
  padding-right: 0;
317
379
  }
@@ -324,17 +386,17 @@
324
386
  height: 36px;
325
387
  }
326
388
  .widget-product-carousel .carousel-dots {
327
- margin-top: 16px;
389
+ margin-top: var(--product-carousel-spacing-element);
328
390
  }
329
391
  }
330
392
 
331
393
  /* Small Mobile */
332
394
  @media (max-width: 480px) {
333
395
  .widget-product-carousel .carousel-container {
334
- padding: 0 12px;
396
+ padding: 0 var(--product-carousel-spacing-small);
335
397
  }
336
398
  .widget-product-carousel .carousel-track {
337
- gap: 10px;
399
+ gap: var(--product-carousel-spacing-small);
338
400
  }
339
401
  .widget-product-carousel .carousel-slide {
340
402
  flex: 0 0 calc(50% - 5px) !important;
@@ -347,7 +409,7 @@
347
409
  </style>
348
410
 
349
411
  <script>
350
- (function() {
412
+ (() => {
351
413
  // Wait for DOM
352
414
  if (document.readyState === 'loading') {
353
415
  document.addEventListener('DOMContentLoaded', initCarousel);
@@ -117,6 +117,18 @@
117
117
  {% endif %}
118
118
 
119
119
  <style>
120
+ :root {
121
+ --product-grid-white: var(--color-white);
122
+ --product-grid-text: var(--color-text);
123
+ --product-grid-text-muted: var(--color-text-muted);
124
+ --product-grid-spacing-section: var(--spacing-section);
125
+ --product-grid-spacing-component: var(--spacing-component);
126
+ --product-grid-spacing-element: var(--spacing-element);
127
+ --product-grid-spacing-small: var(--spacing-small);
128
+ --product-grid-spacing-xsmall: var(--spacing-xsmall);
129
+ --product-grid-text-sm: var(--text-sm);
130
+ }
131
+
120
132
  /* Widget Products Grid - Responsive Layout */
121
133
  /* CRITICAL: All product cards MUST be identical size */
122
134