@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.
- package/README.md +4 -0
- package/lib/lib/dev-server.js +344 -48
- package/lib/lib/liquid-engine.js +3 -1
- package/lib/lib/mock-data.js +473 -119
- package/lib/lib/widget-service.js +12 -4
- package/package.json +2 -2
- package/test-theme/assets/async-sections.js +32 -24
- package/test-theme/assets/cart-drawer.js +20 -22
- package/test-theme/assets/cart-manager.js +1 -15
- package/test-theme/assets/checkout-price-handler.js +12 -11
- package/test-theme/assets/checkout.css +1415 -0
- package/test-theme/assets/checkout.js +3174 -0
- package/test-theme/assets/components.css +178 -29
- package/test-theme/assets/delivery-zone.js +1 -1
- package/test-theme/assets/product-detail.css +1050 -0
- package/test-theme/assets/product-detail.js +2940 -0
- package/test-theme/assets/theme.css +95 -120
- package/test-theme/assets/theme.js +781 -186
- package/test-theme/layout/theme.liquid +91 -17
- package/test-theme/sections/content.liquid +64 -57
- package/test-theme/sections/footer-fallback.liquid +57 -7
- package/test-theme/sections/footer.liquid +63 -12
- package/test-theme/sections/header-fallback.liquid +41 -41
- package/test-theme/sections/header.liquid +41 -51
- package/test-theme/sections/hero-fallback.liquid +1 -1
- package/test-theme/sections/hero.liquid +159 -136
- package/test-theme/snippets/account-sidebar.liquid +121 -29
- package/test-theme/snippets/add-to-cart-modal.liquid +258 -206
- package/test-theme/snippets/breadcrumbs.liquid +98 -11
- package/test-theme/snippets/cart-drawer.liquid +93 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +101 -15
- package/test-theme/snippets/delivery-zone-modal.liquid +529 -84
- package/test-theme/snippets/delivery-zone-search.liquid +104 -18
- package/test-theme/snippets/login-modal.liquid +269 -82
- package/test-theme/snippets/mega-menu.liquid +130 -43
- package/test-theme/snippets/news-thumbnail.liquid +120 -28
- package/test-theme/snippets/pagination.liquid +1 -1
- package/test-theme/snippets/price.liquid +100 -9
- package/test-theme/snippets/product-card-related.liquid +22 -4
- package/test-theme/snippets/product-card-simple.liquid +521 -25
- package/test-theme/snippets/product-card.liquid +145 -232
- package/test-theme/snippets/rating.liquid +100 -9
- package/test-theme/snippets/skeleton-collection-grid.liquid +94 -8
- package/test-theme/snippets/skeleton-product-card.liquid +102 -16
- package/test-theme/snippets/skeleton-product-grid.liquid +87 -1
- package/test-theme/snippets/social-sharing.liquid +133 -32
- package/test-theme/templates/account/dashboard.liquid +30 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +29 -28
- package/test-theme/templates/account/loyalty.liquid +45 -43
- package/test-theme/templates/account/order-detail.liquid +15 -8
- package/test-theme/templates/account/orders.liquid +189 -35
- package/test-theme/templates/account/profile.liquid +509 -114
- package/test-theme/templates/account/register.liquid +18 -8
- package/test-theme/templates/account/return-orders.liquid +31 -30
- package/test-theme/templates/account/store-credit.liquid +27 -26
- package/test-theme/templates/account/subscriptions.liquid +22 -5
- package/test-theme/templates/account/wishlist.liquid +88 -19
- package/test-theme/templates/address-book.liquid +166 -69
- package/test-theme/templates/categories.liquid +90 -30
- package/test-theme/templates/checkout.liquid +137 -3834
- package/test-theme/templates/error.liquid +23 -21
- package/test-theme/templates/index.liquid +29 -0
- package/test-theme/templates/login.liquid +33 -6
- package/test-theme/templates/order-confirmation.liquid +67 -9
- package/test-theme/templates/page.liquid +418 -206
- package/test-theme/templates/product-detail.liquid +124 -3878
- package/test-theme/templates/products.liquid +155 -30
- package/test-theme/templates/search.liquid +739 -225
- package/test-theme/widgets/brand-carousel.liquid +102 -82
- package/test-theme/widgets/brand.liquid +78 -50
- package/test-theme/widgets/carousel.liquid +253 -121
- package/test-theme/widgets/category-list-carousel.liquid +32 -8
- package/test-theme/widgets/category-list.liquid +21 -6
- package/test-theme/widgets/category.liquid +104 -37
- package/test-theme/widgets/discount-time.liquid +326 -119
- package/test-theme/widgets/footer-menu.liquid +115 -23
- package/test-theme/widgets/footer.liquid +118 -5
- package/test-theme/widgets/gallery.liquid +29 -5
- package/test-theme/widgets/header-menu.liquid +25 -13
- package/test-theme/widgets/header.liquid +64 -26
- package/test-theme/widgets/html.liquid +29 -6
- package/test-theme/widgets/news.liquid +6 -0
- package/test-theme/widgets/product-canvas.liquid +20 -12
- package/test-theme/widgets/product-carousel.liquid +118 -56
- package/test-theme/widgets/shared/product-grid.liquid +12 -0
- package/test-theme/widgets/single-product.liquid +688 -250
- package/test-theme/widgets/spacebar-carousel.liquid +39 -10
- package/test-theme/widgets/spacebar.liquid +77 -6
- package/test-theme/widgets/splash.liquid +40 -30
- 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
|
-
|
|
43
|
-
<div class="widget-header-
|
|
44
|
-
<div class="widget-header-
|
|
45
|
-
|
|
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
|
-
(
|
|
172
|
+
(() => {
|
|
135
173
|
const announcementCloseBtn = document.querySelector('[data-announcement-close]');
|
|
136
174
|
if (announcementCloseBtn) {
|
|
137
|
-
announcementCloseBtn.addEventListener('click',
|
|
138
|
-
const announcement =
|
|
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.
|
|
11
|
-
2.
|
|
12
|
-
3.
|
|
13
|
-
4.
|
|
14
|
-
5.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{%
|
|
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:
|
|
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
|
|
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:
|
|
117
|
-
gap:
|
|
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:
|
|
190
|
+
font-size: var(--product-carousel-text-sm);
|
|
128
191
|
font-weight: 500;
|
|
129
192
|
line-height: 1.3;
|
|
130
|
-
color:
|
|
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:
|
|
137
|
-
color:
|
|
138
|
-
margin:
|
|
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:
|
|
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:
|
|
151
|
-
border: 1px solid
|
|
152
|
-
background:
|
|
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:
|
|
158
|
-
transition: all
|
|
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:
|
|
164
|
-
color:
|
|
165
|
-
border-color:
|
|
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:
|
|
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:
|
|
249
|
+
border-radius: var(--product-carousel-radius-full);
|
|
187
250
|
border: none;
|
|
188
|
-
background:
|
|
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:
|
|
194
|
-
transition: all
|
|
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:
|
|
201
|
-
color:
|
|
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:
|
|
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:
|
|
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:
|
|
257
|
-
margin-top:
|
|
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:
|
|
264
|
-
background:
|
|
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
|
|
330
|
+
transition: all var(--product-carousel-transition-fast);
|
|
269
331
|
}
|
|
270
332
|
|
|
271
333
|
.widget-product-carousel .carousel-dot:hover {
|
|
272
|
-
background:
|
|
334
|
+
background: var(--product-carousel-text-muted);
|
|
273
335
|
}
|
|
274
336
|
|
|
275
337
|
.widget-product-carousel .carousel-dot.active {
|
|
276
|
-
background:
|
|
338
|
+
background: var(--product-carousel-text);
|
|
277
339
|
width: 24px;
|
|
278
|
-
border-radius:
|
|
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:
|
|
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:
|
|
367
|
+
padding: var(--product-carousel-spacing-large) 0;
|
|
306
368
|
}
|
|
307
369
|
.widget-product-carousel .carousel-container {
|
|
308
|
-
padding: 0
|
|
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:
|
|
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:
|
|
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
|
|
396
|
+
padding: 0 var(--product-carousel-spacing-small);
|
|
335
397
|
}
|
|
336
398
|
.widget-product-carousel .carousel-track {
|
|
337
|
-
gap:
|
|
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
|
-
(
|
|
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
|
|