@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.
- package/README.md +425 -0
- package/assets/Logo_o2vend.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-white.png +0 -0
- package/bin/o2vend +42 -0
- package/config/widget-map.json +50 -0
- package/lib/commands/check.js +201 -0
- package/lib/commands/generate.js +33 -0
- package/lib/commands/init.js +214 -0
- package/lib/commands/optimize.js +216 -0
- package/lib/commands/package.js +208 -0
- package/lib/commands/serve.js +105 -0
- package/lib/commands/validate.js +191 -0
- package/lib/lib/api-client.js +357 -0
- package/lib/lib/dev-server.js +2618 -0
- package/lib/lib/file-watcher.js +80 -0
- package/lib/lib/hot-reload.js +106 -0
- package/lib/lib/liquid-engine.js +822 -0
- package/lib/lib/liquid-filters.js +671 -0
- package/lib/lib/mock-api-server.js +989 -0
- package/lib/lib/mock-data.js +1468 -0
- package/lib/lib/widget-service.js +321 -0
- package/package.json +70 -0
- package/test-theme/README.md +27 -0
- package/test-theme/assets/async-sections.js +446 -0
- package/test-theme/assets/cart-drawer.js +463 -0
- package/test-theme/assets/cart-manager.js +223 -0
- package/test-theme/assets/checkout-price-handler.js +368 -0
- package/test-theme/assets/components.css +4629 -0
- package/test-theme/assets/delivery-zone.css +299 -0
- package/test-theme/assets/delivery-zone.js +396 -0
- package/test-theme/assets/logo.png +0 -0
- package/test-theme/assets/sections.css +48 -0
- package/test-theme/assets/theme.css +3500 -0
- package/test-theme/assets/theme.js +3745 -0
- package/test-theme/config/settings_data.json +292 -0
- package/test-theme/config/settings_schema.json +1050 -0
- package/test-theme/layout/theme.liquid +195 -0
- package/test-theme/locales/en.default.json +260 -0
- package/test-theme/sections/content-fallback.liquid +53 -0
- package/test-theme/sections/content.liquid +57 -0
- package/test-theme/sections/footer-fallback.liquid +328 -0
- package/test-theme/sections/footer.liquid +278 -0
- package/test-theme/sections/header-fallback.liquid +1805 -0
- package/test-theme/sections/header.liquid +1145 -0
- package/test-theme/sections/hero-fallback.liquid +212 -0
- package/test-theme/sections/hero.liquid +136 -0
- package/test-theme/snippets/account-sidebar.liquid +200 -0
- package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
- package/test-theme/snippets/breadcrumbs.liquid +134 -0
- package/test-theme/snippets/cart-drawer.liquid +467 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
- package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
- package/test-theme/snippets/delivery-zone-search.liquid +78 -0
- package/test-theme/snippets/icon.liquid +105 -0
- package/test-theme/snippets/login-modal.liquid +346 -0
- package/test-theme/snippets/mega-menu.liquid +812 -0
- package/test-theme/snippets/news-thumbnail.liquid +187 -0
- package/test-theme/snippets/pagination.liquid +120 -0
- package/test-theme/snippets/price.liquid +92 -0
- package/test-theme/snippets/product-card-related.liquid +78 -0
- package/test-theme/snippets/product-card-simple.liquid +41 -0
- package/test-theme/snippets/product-card.liquid +697 -0
- package/test-theme/snippets/rating.liquid +85 -0
- package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
- package/test-theme/snippets/skeleton-product-card.liquid +124 -0
- package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
- package/test-theme/snippets/social-sharing.liquid +185 -0
- package/test-theme/templates/account/dashboard.liquid +401 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
- package/test-theme/templates/account/loyalty.liquid +588 -0
- package/test-theme/templates/account/order-detail.liquid +230 -0
- package/test-theme/templates/account/orders.liquid +349 -0
- package/test-theme/templates/account/profile.liquid +758 -0
- package/test-theme/templates/account/register.liquid +232 -0
- package/test-theme/templates/account/return-orders.liquid +348 -0
- package/test-theme/templates/account/store-credit.liquid +464 -0
- package/test-theme/templates/account/subscriptions.liquid +601 -0
- package/test-theme/templates/account/wishlist.liquid +419 -0
- package/test-theme/templates/address-book.liquid +1092 -0
- package/test-theme/templates/categories.liquid +452 -0
- package/test-theme/templates/checkout.liquid +4511 -0
- package/test-theme/templates/error.liquid +384 -0
- package/test-theme/templates/index.liquid +11 -0
- package/test-theme/templates/login.liquid +185 -0
- package/test-theme/templates/order-confirmation.liquid +720 -0
- package/test-theme/templates/page.liquid +297 -0
- package/test-theme/templates/product-detail.liquid +4363 -0
- package/test-theme/templates/products.liquid +518 -0
- package/test-theme/templates/search.liquid +922 -0
- package/test-theme/theme.json.example +19 -0
- package/test-theme/widgets/brand-carousel.liquid +676 -0
- package/test-theme/widgets/brand.liquid +245 -0
- package/test-theme/widgets/carousel.liquid +843 -0
- package/test-theme/widgets/category-list-carousel.liquid +656 -0
- package/test-theme/widgets/category-list.liquid +340 -0
- package/test-theme/widgets/category.liquid +475 -0
- package/test-theme/widgets/discount-time.liquid +176 -0
- package/test-theme/widgets/footer-menu.liquid +695 -0
- package/test-theme/widgets/footer.liquid +179 -0
- package/test-theme/widgets/gallery.liquid +271 -0
- package/test-theme/widgets/header-menu.liquid +932 -0
- package/test-theme/widgets/header.liquid +159 -0
- package/test-theme/widgets/html.liquid +214 -0
- package/test-theme/widgets/news.liquid +217 -0
- package/test-theme/widgets/product-canvas.liquid +235 -0
- package/test-theme/widgets/product-carousel.liquid +502 -0
- package/test-theme/widgets/product.liquid +45 -0
- package/test-theme/widgets/recently-viewed.liquid +26 -0
- package/test-theme/widgets/shared/product-grid.liquid +339 -0
- package/test-theme/widgets/simple-product.liquid +42 -0
- package/test-theme/widgets/single-product.liquid +610 -0
- package/test-theme/widgets/spacebar-carousel.liquid +663 -0
- package/test-theme/widgets/spacebar.liquid +279 -0
- package/test-theme/widgets/splash.liquid +378 -0
- package/test-theme/widgets/testimonial-carousel.liquid +709 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "test-theme",
|
|
3
|
+
"name": "Test Theme",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Theme Developer",
|
|
6
|
+
"description": "A beautiful O2VEND theme: test-theme",
|
|
7
|
+
"migration": {
|
|
8
|
+
"files": {
|
|
9
|
+
"modified": [],
|
|
10
|
+
"added": [],
|
|
11
|
+
"deleted": []
|
|
12
|
+
},
|
|
13
|
+
"script": null
|
|
14
|
+
},
|
|
15
|
+
"compatibility": {
|
|
16
|
+
"minO2VENDVersion": "1.0.0",
|
|
17
|
+
"dependencies": []
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
{% liquid
|
|
2
|
+
assign widget_settings = widget.settings
|
|
3
|
+
assign widget_data = widget.data
|
|
4
|
+
|
|
5
|
+
comment
|
|
6
|
+
Dynamic Widget: Brands come from widget_data.brands (enriched by WidgetService)
|
|
7
|
+
Settings come from widget_settings
|
|
8
|
+
endcomment
|
|
9
|
+
assign brands = widget_data.Brands | default: widget_data.brands
|
|
10
|
+
assign heading = widget_settings.title | default: widget.Title | default: widget.title
|
|
11
|
+
assign subtitle = widget_settings.subtitle
|
|
12
|
+
assign background_color = widget_settings.backgroundColor
|
|
13
|
+
assign text_color = widget_settings.textColor
|
|
14
|
+
assign show_container = widget_settings.showContainer
|
|
15
|
+
assign show_bottom_margin = widget_settings.showWidgetBottomMargin
|
|
16
|
+
assign show_widget_title_raw = widget_settings.showWidgetTitle | default: 'Yes'
|
|
17
|
+
if show_widget_title_raw == null or show_widget_title_raw == blank or show_widget_title_raw == 'null'
|
|
18
|
+
assign show_widget_title = true
|
|
19
|
+
else
|
|
20
|
+
if show_widget_title_raw == 'Yes'
|
|
21
|
+
assign show_widget_title = true
|
|
22
|
+
elsif show_widget_title_raw == true
|
|
23
|
+
assign show_widget_title = true
|
|
24
|
+
else
|
|
25
|
+
assign show_widget_title = false
|
|
26
|
+
endif
|
|
27
|
+
endif
|
|
28
|
+
assign widget_title_alignment_raw = widget_settings.widgetTitleAlignment | default: 'center'
|
|
29
|
+
assign widget_title_alignment = widget_title_alignment_raw | downcase
|
|
30
|
+
if widget_title_alignment != 'left' and widget_title_alignment != 'right' and widget_title_alignment != 'center'
|
|
31
|
+
assign widget_title_alignment = 'center'
|
|
32
|
+
endif
|
|
33
|
+
%}
|
|
34
|
+
|
|
35
|
+
<section class="widget widget-brand-carousel" data-widget-id="{{ widget.id }}" data-brand-carousel{% if background_color and background_color != blank and background_color != 'null' %} style="background-color: {{ background_color }};"{% endif %}>
|
|
36
|
+
<div class="brand-carousel-container">
|
|
37
|
+
{% if show_widget_title %}
|
|
38
|
+
{% if heading or subtitle %}
|
|
39
|
+
<header class="brand-carousel-header">
|
|
40
|
+
<div class="brand-carousel-header__text" style="text-align: {{ widget_title_alignment }};">
|
|
41
|
+
{% if heading %}
|
|
42
|
+
<h2 class="brand-carousel-title"{% if text_color and text_color != 'null' %} style="color: {{ text_color }};"{% endif %}>{{ heading }}</h2>
|
|
43
|
+
{% endif %}
|
|
44
|
+
{% if subtitle %}
|
|
45
|
+
<p class="brand-carousel-subtitle"{% if text_color and text_color != 'null' %} style="color: {{ text_color }};"{% endif %}>{{ subtitle }}</p>
|
|
46
|
+
{% endif %}
|
|
47
|
+
</div>
|
|
48
|
+
{% if brands and brands.size > 0 %}
|
|
49
|
+
<div class="brand-carousel-nav">
|
|
50
|
+
<button type="button" class="brand-carousel-arrow brand-carousel-arrow--prev" data-carousel-prev aria-label="Previous">
|
|
51
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
52
|
+
<polyline points="15 18 9 12 15 6"></polyline>
|
|
53
|
+
</svg>
|
|
54
|
+
</button>
|
|
55
|
+
<button type="button" class="brand-carousel-arrow brand-carousel-arrow--next" data-carousel-next aria-label="Next">
|
|
56
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
57
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
{% endif %}
|
|
62
|
+
</header>
|
|
63
|
+
{% endif %}
|
|
64
|
+
{% endif %}
|
|
65
|
+
|
|
66
|
+
{% if brands and brands.size > 0 %}
|
|
67
|
+
<div class="brand-carousel-wrapper">
|
|
68
|
+
<!-- Side Navigation Arrows (Desktop) -->
|
|
69
|
+
<button type="button" class="brand-carousel-side-arrow brand-carousel-side-arrow--prev" data-carousel-prev aria-label="Previous">
|
|
70
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
71
|
+
<polyline points="15 18 9 12 15 6"></polyline>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
<div class="brand-carousel-track-wrapper" data-carousel-viewport>
|
|
76
|
+
<div class="brand-carousel-track" data-carousel-track>
|
|
77
|
+
{% for brand in brands %}
|
|
78
|
+
{% assign brand_id = brand.Id | default: brand.id | default: brand.brandId %}
|
|
79
|
+
{% assign brand_name = brand.Name | default: brand.name %}
|
|
80
|
+
{% assign brand_slug = brand.Slug | default: brand.slug %}
|
|
81
|
+
{% assign brand_url = brand.Url | default: brand.url | default: brand.link %}
|
|
82
|
+
{% if brand_url == blank %}
|
|
83
|
+
{% if brand_slug and brand_slug != blank %}
|
|
84
|
+
{% assign brand_url = brand_slug %}
|
|
85
|
+
{% else %}
|
|
86
|
+
{% assign brand_url = '#' %}
|
|
87
|
+
{% endif %}
|
|
88
|
+
{% endif %}
|
|
89
|
+
|
|
90
|
+
{% comment %} Image fallback: thumbnailImage.url > Logo > logo > image {% endcomment %}
|
|
91
|
+
{% assign brand_image = nil %}
|
|
92
|
+
{% if brand.thumbnailImage and brand.thumbnailImage.url %}
|
|
93
|
+
{% assign brand_image = brand.thumbnailImage.url %}
|
|
94
|
+
{% elsif brand.ThumbnailImage and brand.ThumbnailImage.Url %}
|
|
95
|
+
{% assign brand_image = brand.ThumbnailImage.Url %}
|
|
96
|
+
{% elsif brand.Logo %}
|
|
97
|
+
{% assign brand_image = brand.Logo %}
|
|
98
|
+
{% elsif brand.logo %}
|
|
99
|
+
{% assign brand_image = brand.logo %}
|
|
100
|
+
{% elsif brand.image %}
|
|
101
|
+
{% assign brand_image = brand.image %}
|
|
102
|
+
{% elsif brand.Image %}
|
|
103
|
+
{% assign brand_image = brand.Image %}
|
|
104
|
+
{% endif %}
|
|
105
|
+
|
|
106
|
+
<div class="brand-carousel-slide" data-slide-index="{{ forloop.index0 }}">
|
|
107
|
+
<a href="{{ brand_url }}" class="brand-carousel-item" aria-label="{{ brand_name }}">
|
|
108
|
+
{% if brand_image != blank and brand_image != '' and brand_image != 'null' %}
|
|
109
|
+
<div class="brand-carousel-item__image">
|
|
110
|
+
<img src="{{ brand_image }}" alt="{{ brand_name }}" width="200" height="60" loading="lazy" onerror="this.style.display='none'; this.parentElement.nextElementSibling.style.display='flex';">
|
|
111
|
+
</div>
|
|
112
|
+
<span class="brand-carousel-item__name brand-carousel-item__name--fallback" style="display: none;">{{ brand_name }}</span>
|
|
113
|
+
{% else %}
|
|
114
|
+
<span class="brand-carousel-item__name">{{ brand_name }}</span>
|
|
115
|
+
{% endif %}
|
|
116
|
+
</a>
|
|
117
|
+
</div>
|
|
118
|
+
{% endfor %}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<button type="button" class="brand-carousel-side-arrow brand-carousel-side-arrow--next" data-carousel-next aria-label="Next">
|
|
123
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
124
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
125
|
+
</svg>
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Dot Indicators -->
|
|
130
|
+
<div class="brand-carousel-dots" data-carousel-dots></div>
|
|
131
|
+
{% else %}
|
|
132
|
+
<div class="widget-empty">
|
|
133
|
+
<p>{{ widget_settings.empty_state | default: 'No brands to display yet.' }}</p>
|
|
134
|
+
</div>
|
|
135
|
+
{% endif %}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<style>
|
|
139
|
+
.widget-brand-carousel {
|
|
140
|
+
{% if show_bottom_margin == 'Yes' %}
|
|
141
|
+
padding: 2.5rem 0;
|
|
142
|
+
{% elsif show_bottom_margin == 'No' %}
|
|
143
|
+
padding: 2.5rem 0 0;
|
|
144
|
+
{% else %}
|
|
145
|
+
padding: 2.5rem 0;
|
|
146
|
+
{% endif %}
|
|
147
|
+
background: {{ background_color | default: widget_settings.background_color | default: 'transparent' }};
|
|
148
|
+
}
|
|
149
|
+
.brand-carousel-container {
|
|
150
|
+
{% if show_container == 'Yes' %}
|
|
151
|
+
max-width: 1400px;
|
|
152
|
+
margin-left: auto;
|
|
153
|
+
margin-right: auto;
|
|
154
|
+
{% endif %}
|
|
155
|
+
padding-left: 24px;
|
|
156
|
+
padding-right: 24px;
|
|
157
|
+
}
|
|
158
|
+
.brand-carousel-slide {
|
|
159
|
+
position: relative;
|
|
160
|
+
border-radius: 12px;
|
|
161
|
+
background: #fff;
|
|
162
|
+
min-height: 100px;
|
|
163
|
+
text-decoration: none;
|
|
164
|
+
color: inherit;
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
|
|
167
|
+
box-shadow:
|
|
168
|
+
0 0 8px rgba(15, 23, 42, 0.06),
|
|
169
|
+
0 0 16px rgba(15, 23, 42, 0.04);
|
|
170
|
+
|
|
171
|
+
transition:
|
|
172
|
+
transform 0.25s ease,
|
|
173
|
+
box-shadow 0.25s ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Hover / Focus */
|
|
177
|
+
.brand-carousel-slide:hover,
|
|
178
|
+
.brand-carousel-slide:focus-visible {
|
|
179
|
+
transform: translateY(-2px);
|
|
180
|
+
|
|
181
|
+
box-shadow:
|
|
182
|
+
0 4px 12px rgba(15, 23, 42, 0.10),
|
|
183
|
+
0 8px 24px rgba(15, 23, 42, 0.06);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Brand Carousel - True Carousel Behavior */
|
|
187
|
+
.widget-brand-carousel {
|
|
188
|
+
padding: 48px 0;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.widget-brand-carousel .brand-carousel-container {
|
|
193
|
+
max-width: 1400px;
|
|
194
|
+
margin: 0 auto;
|
|
195
|
+
padding: 0 24px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Header */
|
|
199
|
+
.widget-brand-carousel .brand-carousel-header {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: space-between;
|
|
203
|
+
margin-bottom: 28px;
|
|
204
|
+
gap: 16px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.widget-brand-carousel .brand-carousel-header__text {
|
|
208
|
+
flex: 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.widget-brand-carousel .brand-carousel-header {
|
|
212
|
+
position: static;
|
|
213
|
+
z-index: auto;
|
|
214
|
+
}
|
|
215
|
+
.widget-brand-carousel .brand-carousel-title {
|
|
216
|
+
font-size: 1.4rem;
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
line-height: 1.3;
|
|
219
|
+
color: #111;
|
|
220
|
+
margin: 0;
|
|
221
|
+
letter-spacing: -0.01em;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.widget-brand-carousel .brand-carousel-subtitle {
|
|
225
|
+
font-size: 14px;
|
|
226
|
+
color: #6b7280;
|
|
227
|
+
margin: 6px 0 0 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Header Nav Arrows */
|
|
231
|
+
.widget-brand-carousel .brand-carousel-nav {
|
|
232
|
+
display: flex;
|
|
233
|
+
gap: 8px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.widget-brand-carousel .brand-carousel-arrow {
|
|
237
|
+
width: 40px;
|
|
238
|
+
height: 40px;
|
|
239
|
+
border-radius: 50%;
|
|
240
|
+
border: 1px solid #e0e0e0;
|
|
241
|
+
background: #fff;
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: center;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
color: #333;
|
|
247
|
+
transition: all 0.2s ease;
|
|
248
|
+
flex-shrink: 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.widget-brand-carousel .brand-carousel-arrow:hover:not(:disabled) {
|
|
252
|
+
background: #111;
|
|
253
|
+
color: #fff;
|
|
254
|
+
border-color: #111;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.widget-brand-carousel .brand-carousel-arrow:disabled {
|
|
258
|
+
opacity: 0.3;
|
|
259
|
+
cursor: not-allowed;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Carousel Wrapper with Side Arrows */
|
|
263
|
+
.widget-brand-carousel .brand-carousel-wrapper {
|
|
264
|
+
position: relative;
|
|
265
|
+
display: flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 12px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Side Navigation Arrows */
|
|
271
|
+
.widget-brand-carousel .brand-carousel-side-arrow {
|
|
272
|
+
display: none; /* Hidden by default, shown on desktop */
|
|
273
|
+
width: 30px;
|
|
274
|
+
height: 30px;
|
|
275
|
+
border-radius: 50%;
|
|
276
|
+
border: none;
|
|
277
|
+
background: #fff;
|
|
278
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
|
|
279
|
+
align-items: center;
|
|
280
|
+
justify-content: center;
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
color: #333;
|
|
283
|
+
transition: all 0.2s ease;
|
|
284
|
+
flex-shrink: 0;
|
|
285
|
+
z-index: 10;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.widget-brand-carousel .brand-carousel-side-arrow:hover:not(:disabled) {
|
|
289
|
+
background: #111;
|
|
290
|
+
color: #fff;
|
|
291
|
+
transform: scale(1.05);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.widget-brand-carousel .brand-carousel-side-arrow:disabled {
|
|
295
|
+
opacity: 0;
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Track Wrapper */
|
|
300
|
+
.widget-brand-carousel .brand-carousel-track-wrapper {
|
|
301
|
+
flex: 1;
|
|
302
|
+
overflow: hidden;
|
|
303
|
+
position: relative;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* The Scrolling Track */
|
|
307
|
+
.widget-brand-carousel .brand-carousel-track {
|
|
308
|
+
display: flex !important;
|
|
309
|
+
gap: 20px;
|
|
310
|
+
overflow-x: auto !important;
|
|
311
|
+
overflow-y: hidden !important;
|
|
312
|
+
scroll-snap-type: x mandatory;
|
|
313
|
+
scroll-behavior: smooth;
|
|
314
|
+
-webkit-overflow-scrolling: touch;
|
|
315
|
+
scrollbar-width: none;
|
|
316
|
+
-ms-overflow-style: none;
|
|
317
|
+
padding: 4px 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.widget-brand-carousel .brand-carousel-track::-webkit-scrollbar {
|
|
321
|
+
display: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Individual Slides - Desktop: 6 items */
|
|
325
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
326
|
+
flex: 0 0 calc(16.666% - 17px);
|
|
327
|
+
min-width: 0;
|
|
328
|
+
max-width: calc(16.666% - 17px);
|
|
329
|
+
scroll-snap-align: start;
|
|
330
|
+
scroll-snap-stop: normal;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Brand Item Card */
|
|
334
|
+
.widget-brand-carousel .brand-carousel-item {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
padding: 20px;
|
|
339
|
+
background: #fff;
|
|
340
|
+
min-height: 100px;
|
|
341
|
+
text-decoration: none;
|
|
342
|
+
color: inherit;
|
|
343
|
+
overflow: hidden;
|
|
344
|
+
position: relative;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
.widget-brand-carousel .brand-carousel-item__image {
|
|
349
|
+
display: flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
justify-content: center;
|
|
352
|
+
width: 100%;
|
|
353
|
+
height: 60px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.widget-brand-carousel .brand-carousel-item__image img {
|
|
357
|
+
max-width: 100%;
|
|
358
|
+
max-height: 60px;
|
|
359
|
+
width: auto;
|
|
360
|
+
height: auto;
|
|
361
|
+
object-fit: contain;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.widget-brand-carousel .brand-carousel-item__name {
|
|
365
|
+
font-size: 14px;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
color: #111;
|
|
368
|
+
text-align: center;
|
|
369
|
+
line-height: 1.3;
|
|
370
|
+
word-break: break-word;
|
|
371
|
+
letter-spacing: -0.01em;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.widget-brand-carousel .brand-carousel-item__name--fallback {
|
|
375
|
+
position: absolute;
|
|
376
|
+
top: 50%;
|
|
377
|
+
left: 50%;
|
|
378
|
+
transform: translate(-50%, -50%);
|
|
379
|
+
width: 100%;
|
|
380
|
+
padding: 0 8px;
|
|
381
|
+
box-sizing: border-box;
|
|
382
|
+
display: none;
|
|
383
|
+
align-items: center;
|
|
384
|
+
justify-content: center;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Dot Indicators */
|
|
388
|
+
.widget-brand-carousel .brand-carousel-dots {
|
|
389
|
+
display: flex;
|
|
390
|
+
justify-content: center;
|
|
391
|
+
gap: 8px;
|
|
392
|
+
margin-top: 20px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.widget-brand-carousel .brand-carousel-dot {
|
|
396
|
+
width: 8px;
|
|
397
|
+
height: 8px;
|
|
398
|
+
border-radius: 50%;
|
|
399
|
+
background: #d1d5db;
|
|
400
|
+
border: none;
|
|
401
|
+
padding: 0;
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
transition: all 0.2s ease;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.widget-brand-carousel .brand-carousel-dot:hover {
|
|
407
|
+
background: #9ca3af;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.widget-brand-carousel .brand-carousel-dot.active {
|
|
411
|
+
background: #111;
|
|
412
|
+
width: 24px;
|
|
413
|
+
border-radius: 4px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* Desktop: Show side arrows, hide header arrows */
|
|
417
|
+
@media (min-width: 1025px) {
|
|
418
|
+
.widget-brand-carousel .brand-carousel-side-arrow {
|
|
419
|
+
display: flex;
|
|
420
|
+
}
|
|
421
|
+
.widget-brand-carousel .brand-carousel-nav {
|
|
422
|
+
display: none;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* Large Tablet: 5 items */
|
|
427
|
+
@media (max-width: 1200px) {
|
|
428
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
429
|
+
flex: 0 0 calc(20% - 16px);
|
|
430
|
+
max-width: calc(20% - 16px);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* Tablet: 4 items */
|
|
435
|
+
@media (max-width: 1024px) {
|
|
436
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
437
|
+
flex: 0 0 calc(25% - 15px);
|
|
438
|
+
max-width: calc(25% - 15px);
|
|
439
|
+
}
|
|
440
|
+
.widget-brand-carousel .brand-carousel-track {
|
|
441
|
+
gap: 20px;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Small Tablet: 3 items */
|
|
446
|
+
@media (max-width: 768px) {
|
|
447
|
+
.widget-brand-carousel {
|
|
448
|
+
padding: 36px 0;
|
|
449
|
+
}
|
|
450
|
+
.widget-brand-carousel .brand-carousel-container {
|
|
451
|
+
padding: 0 16px;
|
|
452
|
+
}
|
|
453
|
+
.widget-brand-carousel .brand-carousel-title {
|
|
454
|
+
font-size: 1.3rem;
|
|
455
|
+
}
|
|
456
|
+
.widget-brand-carousel .brand-carousel-track {
|
|
457
|
+
gap: 12px;
|
|
458
|
+
}
|
|
459
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
460
|
+
flex: 0 0 calc(33.333% - 8px);
|
|
461
|
+
max-width: calc(33.333% - 8px);
|
|
462
|
+
}
|
|
463
|
+
.widget-brand-carousel .brand-carousel-item {
|
|
464
|
+
padding: 16px;
|
|
465
|
+
min-height: 80px;
|
|
466
|
+
}
|
|
467
|
+
.widget-brand-carousel .brand-carousel-item__image {
|
|
468
|
+
height: 45px;
|
|
469
|
+
}
|
|
470
|
+
.widget-brand-carousel .brand-carousel-item__image img {
|
|
471
|
+
max-height: 45px;
|
|
472
|
+
}
|
|
473
|
+
.widget-brand-carousel .brand-carousel-arrow {
|
|
474
|
+
width: 36px;
|
|
475
|
+
height: 36px;
|
|
476
|
+
}
|
|
477
|
+
.widget-brand-carousel .brand-carousel-dots {
|
|
478
|
+
margin-top: 16px;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Mobile: 2.5 items (partial view for hint) */
|
|
483
|
+
@media (max-width: 480px) {
|
|
484
|
+
.widget-brand-carousel .brand-carousel-container {
|
|
485
|
+
padding: 0 12px;
|
|
486
|
+
}
|
|
487
|
+
.widget-brand-carousel .brand-carousel-track {
|
|
488
|
+
gap: 10px;
|
|
489
|
+
}
|
|
490
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
491
|
+
flex: 0 0 calc(40% - 6px);
|
|
492
|
+
max-width: calc(40% - 6px);
|
|
493
|
+
}
|
|
494
|
+
.widget-brand-carousel .brand-carousel-item {
|
|
495
|
+
padding: 12px;
|
|
496
|
+
min-height: 70px;
|
|
497
|
+
border-radius: 8px;
|
|
498
|
+
}
|
|
499
|
+
.widget-brand-carousel .brand-carousel-item__image {
|
|
500
|
+
height: 40px;
|
|
501
|
+
}
|
|
502
|
+
.widget-brand-carousel .brand-carousel-item__image img {
|
|
503
|
+
max-height: 40px;
|
|
504
|
+
}
|
|
505
|
+
.widget-brand-carousel .brand-carousel-title {
|
|
506
|
+
font-size: 1.2rem;
|
|
507
|
+
}
|
|
508
|
+
.widget-brand-carousel .brand-carousel-item__name {
|
|
509
|
+
font-size: 12px;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Extra Small: 2 items */
|
|
514
|
+
@media (max-width: 360px) {
|
|
515
|
+
.widget-brand-carousel .brand-carousel-slide {
|
|
516
|
+
flex: 0 0 calc(50% - 5px);
|
|
517
|
+
max-width: calc(50% - 5px);
|
|
518
|
+
}
|
|
519
|
+
.widget-brand-carousel .brand-carousel-track {
|
|
520
|
+
gap: 8px;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
</style>
|
|
524
|
+
|
|
525
|
+
<script>
|
|
526
|
+
(function() {
|
|
527
|
+
// Wait for DOM
|
|
528
|
+
if (document.readyState === 'loading') {
|
|
529
|
+
document.addEventListener('DOMContentLoaded', initBrandCarousel);
|
|
530
|
+
} else {
|
|
531
|
+
initBrandCarousel();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function initBrandCarousel() {
|
|
535
|
+
const widget = document.querySelector('[data-widget-id="{{ widget.id }}"][data-brand-carousel]');
|
|
536
|
+
if (!widget) return;
|
|
537
|
+
|
|
538
|
+
const track = widget.querySelector('[data-carousel-track]');
|
|
539
|
+
const viewport = widget.querySelector('[data-carousel-viewport]');
|
|
540
|
+
const prevBtns = widget.querySelectorAll('[data-carousel-prev]');
|
|
541
|
+
const nextBtns = widget.querySelectorAll('[data-carousel-next]');
|
|
542
|
+
const dotsContainer = widget.querySelector('[data-carousel-dots]');
|
|
543
|
+
const slides = widget.querySelectorAll('.brand-carousel-slide');
|
|
544
|
+
|
|
545
|
+
if (!track || slides.length === 0) return;
|
|
546
|
+
|
|
547
|
+
// Calculate items per view based on screen width
|
|
548
|
+
function getItemsPerView() {
|
|
549
|
+
const width = window.innerWidth;
|
|
550
|
+
if (width <= 360) return 2;
|
|
551
|
+
if (width <= 480) return 2.5;
|
|
552
|
+
if (width <= 768) return 3;
|
|
553
|
+
if (width <= 1024) return 4;
|
|
554
|
+
if (width <= 1200) return 5;
|
|
555
|
+
return 6;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Calculate number of pages
|
|
559
|
+
function getPageCount() {
|
|
560
|
+
const itemsPerView = Math.floor(getItemsPerView());
|
|
561
|
+
return Math.ceil(slides.length / itemsPerView);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Get current page based on scroll position
|
|
565
|
+
function getCurrentPage() {
|
|
566
|
+
if (!track.scrollWidth || track.scrollWidth <= viewport.clientWidth) return 0;
|
|
567
|
+
const scrollRatio = track.scrollLeft / (track.scrollWidth - viewport.clientWidth);
|
|
568
|
+
return Math.round(scrollRatio * (getPageCount() - 1));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Create dot indicators
|
|
572
|
+
function createDots() {
|
|
573
|
+
if (!dotsContainer) return;
|
|
574
|
+
dotsContainer.innerHTML = '';
|
|
575
|
+
const pageCount = getPageCount();
|
|
576
|
+
if (pageCount <= 1) return;
|
|
577
|
+
|
|
578
|
+
for (let i = 0; i < pageCount; i++) {
|
|
579
|
+
const dot = document.createElement('button');
|
|
580
|
+
dot.className = 'brand-carousel-dot' + (i === 0 ? ' active' : '');
|
|
581
|
+
dot.setAttribute('aria-label', 'Go to page ' + (i + 1));
|
|
582
|
+
dot.addEventListener('click', () => goToPage(i));
|
|
583
|
+
dotsContainer.appendChild(dot);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Update active dot
|
|
588
|
+
function updateDots() {
|
|
589
|
+
if (!dotsContainer) return;
|
|
590
|
+
const dots = dotsContainer.querySelectorAll('.brand-carousel-dot');
|
|
591
|
+
const currentPage = getCurrentPage();
|
|
592
|
+
dots.forEach((dot, index) => {
|
|
593
|
+
dot.classList.toggle('active', index === currentPage);
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Update arrow states
|
|
598
|
+
function updateArrows() {
|
|
599
|
+
const atStart = track.scrollLeft <= 5;
|
|
600
|
+
const atEnd = track.scrollLeft >= track.scrollWidth - viewport.clientWidth - 5;
|
|
601
|
+
|
|
602
|
+
prevBtns.forEach(btn => {
|
|
603
|
+
btn.disabled = atStart;
|
|
604
|
+
});
|
|
605
|
+
nextBtns.forEach(btn => {
|
|
606
|
+
btn.disabled = atEnd;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Navigate to specific page
|
|
611
|
+
function goToPage(pageIndex) {
|
|
612
|
+
const itemsPerView = Math.floor(getItemsPerView());
|
|
613
|
+
const slide = slides[0];
|
|
614
|
+
if (!slide) return;
|
|
615
|
+
|
|
616
|
+
const gap = parseFloat(getComputedStyle(track).gap) || 20;
|
|
617
|
+
const slideWidth = slide.offsetWidth + gap;
|
|
618
|
+
const scrollTo = pageIndex * itemsPerView * slideWidth;
|
|
619
|
+
|
|
620
|
+
track.scrollTo({ left: scrollTo, behavior: 'smooth' });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Scroll by items
|
|
624
|
+
function scrollByItems(direction) {
|
|
625
|
+
const itemsPerView = Math.floor(getItemsPerView());
|
|
626
|
+
const slide = slides[0];
|
|
627
|
+
if (!slide) return;
|
|
628
|
+
|
|
629
|
+
const gap = parseFloat(getComputedStyle(track).gap) || 20;
|
|
630
|
+
const scrollAmount = (slide.offsetWidth + gap) * Math.max(1, itemsPerView - 1);
|
|
631
|
+
|
|
632
|
+
track.scrollBy({ left: direction * scrollAmount, behavior: 'smooth' });
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Event listeners
|
|
636
|
+
prevBtns.forEach(btn => {
|
|
637
|
+
btn.addEventListener('click', (e) => {
|
|
638
|
+
e.preventDefault();
|
|
639
|
+
scrollByItems(-1);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
nextBtns.forEach(btn => {
|
|
644
|
+
btn.addEventListener('click', (e) => {
|
|
645
|
+
e.preventDefault();
|
|
646
|
+
scrollByItems(1);
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Track scroll events
|
|
651
|
+
let scrollTimeout;
|
|
652
|
+
track.addEventListener('scroll', () => {
|
|
653
|
+
clearTimeout(scrollTimeout);
|
|
654
|
+
scrollTimeout = setTimeout(() => {
|
|
655
|
+
updateDots();
|
|
656
|
+
updateArrows();
|
|
657
|
+
}, 50);
|
|
658
|
+
}, { passive: true });
|
|
659
|
+
|
|
660
|
+
// Handle resize
|
|
661
|
+
let resizeTimeout;
|
|
662
|
+
window.addEventListener('resize', () => {
|
|
663
|
+
clearTimeout(resizeTimeout);
|
|
664
|
+
resizeTimeout = setTimeout(() => {
|
|
665
|
+
createDots();
|
|
666
|
+
updateArrows();
|
|
667
|
+
}, 200);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Initialize
|
|
671
|
+
createDots();
|
|
672
|
+
updateArrows();
|
|
673
|
+
}
|
|
674
|
+
})();
|
|
675
|
+
</script>
|
|
676
|
+
</section>
|