@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,610 @@
|
|
|
1
|
+
{% liquid
|
|
2
|
+
assign widget_settings = widget.settings
|
|
3
|
+
assign widget_data = widget.data
|
|
4
|
+
assign widget_content = widget.content
|
|
5
|
+
|
|
6
|
+
comment
|
|
7
|
+
Dynamic Widget: Product comes from widget_data.products (enriched by WidgetService)
|
|
8
|
+
Single Product widget displays one product with PDP-like layout
|
|
9
|
+
Settings come from widget_settings
|
|
10
|
+
endcomment
|
|
11
|
+
assign products = widget_data.products
|
|
12
|
+
assign product = products.first
|
|
13
|
+
|
|
14
|
+
comment
|
|
15
|
+
Title can come from widget_settings.title, widget.Title, or widget_content.ProductName
|
|
16
|
+
endcomment
|
|
17
|
+
assign heading = widget_settings.title | default: widget.Title | default: widget_content.ProductName | default: widget_content.productName
|
|
18
|
+
|
|
19
|
+
assign description = widget_settings.subtitle | default: widget.Subtitle
|
|
20
|
+
assign show_widget_title_raw = widget_settings.showWidgetTitle | default: widget_content.ShowWidgetTitle | default: 'Yes'
|
|
21
|
+
|
|
22
|
+
if show_widget_title_raw == null or show_widget_title_raw == blank or show_widget_title_raw == 'null'
|
|
23
|
+
assign show_widget_title = true
|
|
24
|
+
else
|
|
25
|
+
if show_widget_title_raw == 'Yes'
|
|
26
|
+
assign show_widget_title = true
|
|
27
|
+
elsif show_widget_title_raw == true
|
|
28
|
+
assign show_widget_title = true
|
|
29
|
+
else
|
|
30
|
+
assign show_widget_title = false
|
|
31
|
+
endif
|
|
32
|
+
endif
|
|
33
|
+
|
|
34
|
+
assign widget_title_alignment_raw = widget_settings.widgetTitleAlignment | default: widget_content.WidgetTitleAlignment | default: 'center'
|
|
35
|
+
assign widget_title_alignment = widget_title_alignment_raw | downcase
|
|
36
|
+
if widget_title_alignment != 'left' and widget_title_alignment != 'right' and widget_title_alignment != 'center'
|
|
37
|
+
assign widget_title_alignment = 'center'
|
|
38
|
+
endif
|
|
39
|
+
|
|
40
|
+
assign background_color = widget_settings.backgroundColor | default: widget_settings.BackgroundColor | default: widget_content.BackgroundColor | default: widget_content.backgroundColor
|
|
41
|
+
assign text_color = widget_settings.textColor | default: widget_settings.TextColor | default: widget_content.TextColor | default: widget_content.textColor
|
|
42
|
+
assign show_add_to_cart = widget_settings.showAddToCartButton | default: widget_content.ShowAddToCartButton | default: true
|
|
43
|
+
|
|
44
|
+
comment
|
|
45
|
+
Extract short description (first 200 chars or until first paragraph)
|
|
46
|
+
endcomment
|
|
47
|
+
if product and product.description
|
|
48
|
+
assign full_description = product.description
|
|
49
|
+
assign short_description = full_description | truncate: 200
|
|
50
|
+
if full_description contains '<p>'
|
|
51
|
+
assign first_para = full_description | split: '<p>' | last | split: '</p>' | first
|
|
52
|
+
assign short_description = first_para | truncate: 200
|
|
53
|
+
endif
|
|
54
|
+
endif
|
|
55
|
+
%}
|
|
56
|
+
|
|
57
|
+
<section class="widget widget-single-product" data-widget-id="{{ widget.id }}"{% if background_color and background_color != blank and background_color != 'null' %} style="background-color: {{ background_color }};"{% endif %}>
|
|
58
|
+
<div class="widget-single-product__inner">
|
|
59
|
+
{% if show_widget_title and heading and heading != blank %}
|
|
60
|
+
<div class="widget-header" style="text-align: {{ widget_title_alignment }};{% if text_color and text_color != blank and text_color != 'null' %} color: {{ text_color }};{% endif %}">
|
|
61
|
+
<h2 class="widget-title">{{ heading }}</h2>
|
|
62
|
+
{% if description and description != blank %}
|
|
63
|
+
<p class="widget-subtitle">{{ description }}</p>
|
|
64
|
+
{% endif %}
|
|
65
|
+
</div>
|
|
66
|
+
{% endif %}
|
|
67
|
+
|
|
68
|
+
{% if product %}
|
|
69
|
+
<div class="single-product-pdp">
|
|
70
|
+
<div class="single-product-pdp__layout">
|
|
71
|
+
<!-- Product Gallery -->
|
|
72
|
+
<div class="single-product-pdp__gallery">
|
|
73
|
+
<div class="gallery-main">
|
|
74
|
+
{% if product.images and product.images.size > 0 %}
|
|
75
|
+
{% for image in product.images %}
|
|
76
|
+
{% assign image_url = image.url | default: image %}
|
|
77
|
+
<img
|
|
78
|
+
src="{{ image_url }}"
|
|
79
|
+
alt="{{ product.name | default: product.title }} - {{ forloop.index }}"
|
|
80
|
+
class="gallery-main-image {% if forloop.first %}active{% endif %}"
|
|
81
|
+
data-index="{{ forloop.index0 }}"
|
|
82
|
+
loading="{% if forloop.first %}eager{% else %}lazy{% endif %}"
|
|
83
|
+
width="600"
|
|
84
|
+
height="600"
|
|
85
|
+
>
|
|
86
|
+
{% endfor %}
|
|
87
|
+
{% elsif product.thumbnailImage %}
|
|
88
|
+
{% assign thumbImage = product.thumbnailImage %}
|
|
89
|
+
<img
|
|
90
|
+
src="{{ thumbImage.url | default: thumbImage }}"
|
|
91
|
+
alt="{{ product.name | default: product.title }}"
|
|
92
|
+
class="gallery-main-image active"
|
|
93
|
+
loading="eager"
|
|
94
|
+
width="600"
|
|
95
|
+
height="600"
|
|
96
|
+
>
|
|
97
|
+
{% else %}
|
|
98
|
+
<div class="gallery-placeholder">
|
|
99
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
100
|
+
<rect x="16" y="16" width="32" height="32" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
101
|
+
<path d="M16 24L24 32L32 24L40 32L48 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
102
|
+
</svg>
|
|
103
|
+
</div>
|
|
104
|
+
{% endif %}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Thumbnails -->
|
|
108
|
+
{% if product.images and product.images.size > 1 %}
|
|
109
|
+
<div class="gallery-thumbnails">
|
|
110
|
+
{% for image in product.images limit: 8 %}
|
|
111
|
+
{% assign image_url = image.url | default: image %}
|
|
112
|
+
<button
|
|
113
|
+
class="gallery-thumbnail {% if forloop.first %}active{% endif %}"
|
|
114
|
+
data-image="{{ image_url }}"
|
|
115
|
+
data-index="{{ forloop.index0 }}"
|
|
116
|
+
aria-label="View image {{ forloop.index }}"
|
|
117
|
+
type="button"
|
|
118
|
+
>
|
|
119
|
+
<img src="{{ image_url }}" alt="{{ product.name }} - {{ forloop.index }}" loading="lazy" width="80" height="80">
|
|
120
|
+
</button>
|
|
121
|
+
{% endfor %}
|
|
122
|
+
</div>
|
|
123
|
+
{% endif %}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<!-- Product Info -->
|
|
127
|
+
<div class="single-product-pdp__info"{% if text_color and text_color != blank and text_color != 'null' %} style="color: {{ text_color }};"{% endif %}>
|
|
128
|
+
<!-- Vendor/Brand -->
|
|
129
|
+
{% if product.vendor or product.brand %}
|
|
130
|
+
<div class="product-vendor">
|
|
131
|
+
{{ product.vendor | default: product.brand.name }}
|
|
132
|
+
</div>
|
|
133
|
+
{% endif %}
|
|
134
|
+
|
|
135
|
+
<!-- Product Title -->
|
|
136
|
+
<h1 class="product-title">{{ product.name | default: product.title }}</h1>
|
|
137
|
+
|
|
138
|
+
<!-- Price -->
|
|
139
|
+
<div class="product-price-wrapper">
|
|
140
|
+
<span class="price-current" id="singleProductPrice">
|
|
141
|
+
{{ product.prices.price | money_with_settings: shop.settings }}
|
|
142
|
+
</span>
|
|
143
|
+
{% if product.prices.mrp and product.prices.mrp > product.prices.price %}
|
|
144
|
+
<span class="price-compare">
|
|
145
|
+
{{ product.prices.mrp | money_with_settings: shop.settings }}
|
|
146
|
+
</span>
|
|
147
|
+
{% endif %}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- Short Description -->
|
|
151
|
+
{% if short_description and short_description != blank %}
|
|
152
|
+
<div class="product-short-description">
|
|
153
|
+
{{ short_description }}
|
|
154
|
+
</div>
|
|
155
|
+
{% endif %}
|
|
156
|
+
|
|
157
|
+
<!-- Product Form -->
|
|
158
|
+
<form class="product-form" id="singleProductForm" data-product-id="{{ product.productId }}">
|
|
159
|
+
<!-- Variations -->
|
|
160
|
+
{% if product.variations and product.variations.size > 0 %}
|
|
161
|
+
{% assign default_variant = null %}
|
|
162
|
+
{% for variant in product.variations %}
|
|
163
|
+
{% assign isAvailable = variant.inStock | default: variant.available | default: true %}
|
|
164
|
+
{% if default_variant == null and isAvailable %}
|
|
165
|
+
{% assign default_variant = variant %}
|
|
166
|
+
{% endif %}
|
|
167
|
+
{% endfor %}
|
|
168
|
+
{% if default_variant == null %}
|
|
169
|
+
{% assign default_variant = product.variations.first %}
|
|
170
|
+
{% endif %}
|
|
171
|
+
|
|
172
|
+
<div class="product-variation-selector">
|
|
173
|
+
<label for="singleProductVariant" class="variation-label">Select Variant:</label>
|
|
174
|
+
<select id="singleProductVariant" class="variation-select" name="variantId" aria-label="Select product variant">
|
|
175
|
+
{% for variant in product.variations %}
|
|
176
|
+
{% assign isAvailable = variant.inStock | default: variant.available | default: true %}
|
|
177
|
+
{% assign variant_name = variant.name | default: '' %}
|
|
178
|
+
{% if variant_name == '' and variant.options and variant.options.size > 0 %}
|
|
179
|
+
{% assign variant_name = '' %}
|
|
180
|
+
{% for option in variant.options %}
|
|
181
|
+
{% if variant_name != '' %}
|
|
182
|
+
{% assign variant_name = variant_name | append: ' / ' %}
|
|
183
|
+
{% endif %}
|
|
184
|
+
{% assign variant_name = variant_name | append: option.value %}
|
|
185
|
+
{% endfor %}
|
|
186
|
+
{% endif %}
|
|
187
|
+
{% if variant_name == '' %}
|
|
188
|
+
{% assign variant_name = 'Option ' | append: forloop.index %}
|
|
189
|
+
{% endif %}
|
|
190
|
+
{% assign variant_price = variant.prices.price | default: product.prices.price %}
|
|
191
|
+
<option
|
|
192
|
+
value="{{ variant.productId }}"
|
|
193
|
+
data-variant-price="{{ variant_price }}"
|
|
194
|
+
data-variant-mrp="{{ variant.prices.mrp | default: 0 }}"
|
|
195
|
+
data-in-stock="{{ isAvailable }}"
|
|
196
|
+
{% if variant.productId == default_variant.productId %}selected{% endif %}>
|
|
197
|
+
{{ variant_name }}{% unless isAvailable %} — Out of Stock{% endunless %}
|
|
198
|
+
</option>
|
|
199
|
+
{% endfor %}
|
|
200
|
+
</select>
|
|
201
|
+
</div>
|
|
202
|
+
{% endif %}
|
|
203
|
+
|
|
204
|
+
<!-- Quantity (optional) -->
|
|
205
|
+
<div class="product-quantity">
|
|
206
|
+
<label for="singleProductQuantity">Quantity:</label>
|
|
207
|
+
<input type="number" id="singleProductQuantity" name="quantity" value="1" min="1" class="quantity-input">
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<!-- Add to Cart Button -->
|
|
211
|
+
{% if show_add_to_cart != false %}
|
|
212
|
+
{% assign default_variant_id = product.productId %}
|
|
213
|
+
{% if product.variations and product.variations.size > 0 %}
|
|
214
|
+
{% assign default_variant_id = default_variant.productId %}
|
|
215
|
+
{% endif %}
|
|
216
|
+
|
|
217
|
+
<button
|
|
218
|
+
type="submit"
|
|
219
|
+
class="btn-add-to-cart"
|
|
220
|
+
data-product-id="{{ default_variant_id }}"
|
|
221
|
+
data-base-product-id="{{ product.productId }}">
|
|
222
|
+
Add to Cart
|
|
223
|
+
</button>
|
|
224
|
+
{% endif %}
|
|
225
|
+
</form>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
{% else %}
|
|
230
|
+
<div class="widget-empty">
|
|
231
|
+
<p>No product configured for this widget.</p>
|
|
232
|
+
</div>
|
|
233
|
+
{% endif %}
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<style>
|
|
237
|
+
.widget-single-product {
|
|
238
|
+
padding: 40px 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.widget-single-product__inner {
|
|
242
|
+
max-width: 1200px;
|
|
243
|
+
margin: 0 auto;
|
|
244
|
+
padding: 0 24px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.widget-single-product .widget-header {
|
|
248
|
+
margin-bottom: 32px;
|
|
249
|
+
text-align: center;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.widget-single-product .widget-title {
|
|
253
|
+
font-size: 1.4rem;
|
|
254
|
+
font-weight: 500;
|
|
255
|
+
line-height: 1.3;
|
|
256
|
+
margin: 0 0 8px 0;
|
|
257
|
+
color: #111;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.widget-single-product .widget-subtitle {
|
|
261
|
+
font-size: 1.3rem;
|
|
262
|
+
font-weight: 400;
|
|
263
|
+
line-height: 1.5;
|
|
264
|
+
margin: 0;
|
|
265
|
+
color: #666;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* PDP Layout */
|
|
269
|
+
.single-product-pdp__layout {
|
|
270
|
+
display: grid;
|
|
271
|
+
grid-template-columns: 1fr 1fr;
|
|
272
|
+
gap: 48px;
|
|
273
|
+
align-items: start;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Gallery */
|
|
277
|
+
.single-product-pdp__gallery {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
gap: 16px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.gallery-main {
|
|
284
|
+
position: relative;
|
|
285
|
+
aspect-ratio: 1;
|
|
286
|
+
width: 100%;
|
|
287
|
+
background: #f5f5f5;
|
|
288
|
+
border-radius: var(--border-radius-medium);
|
|
289
|
+
overflow: hidden;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.gallery-main-image {
|
|
293
|
+
display: none;
|
|
294
|
+
width: 100%;
|
|
295
|
+
height: 100%;
|
|
296
|
+
object-fit: cover;
|
|
297
|
+
position: absolute;
|
|
298
|
+
top: 0;
|
|
299
|
+
left: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.gallery-main-image.active {
|
|
303
|
+
display: block;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.gallery-placeholder {
|
|
307
|
+
width: 100%;
|
|
308
|
+
height: 100%;
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
justify-content: center;
|
|
312
|
+
color: #999;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.gallery-thumbnails {
|
|
316
|
+
display: flex;
|
|
317
|
+
gap: 12px;
|
|
318
|
+
overflow-x: auto;
|
|
319
|
+
padding-bottom: 8px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.gallery-thumbnail {
|
|
323
|
+
flex-shrink: 0;
|
|
324
|
+
width: 80px;
|
|
325
|
+
height: 80px;
|
|
326
|
+
border: 2px solid transparent;
|
|
327
|
+
border-radius: 6px;
|
|
328
|
+
overflow: hidden;
|
|
329
|
+
background: #f5f5f5;
|
|
330
|
+
cursor: pointer;
|
|
331
|
+
padding: 0;
|
|
332
|
+
transition: border-color 0.2s ease;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.gallery-thumbnail.active {
|
|
336
|
+
border-color: #111;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.gallery-thumbnail img {
|
|
340
|
+
width: 100%;
|
|
341
|
+
height: 100%;
|
|
342
|
+
object-fit: cover;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Product Info */
|
|
346
|
+
.single-product-pdp__info {
|
|
347
|
+
display: flex;
|
|
348
|
+
flex-direction: column;
|
|
349
|
+
gap: 20px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.product-vendor {
|
|
353
|
+
font-size: 14px;
|
|
354
|
+
text-transform: uppercase;
|
|
355
|
+
letter-spacing: 0.05em;
|
|
356
|
+
color: #666;
|
|
357
|
+
font-weight: 500;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.product-title {
|
|
361
|
+
font-size: 32px;
|
|
362
|
+
font-weight: 600;
|
|
363
|
+
line-height: 1.2;
|
|
364
|
+
margin: 0;
|
|
365
|
+
color: #111;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.product-price-wrapper {
|
|
369
|
+
display: flex;
|
|
370
|
+
align-items: baseline;
|
|
371
|
+
gap: 12px;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.price-current {
|
|
375
|
+
font-size: 28px;
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
color: #111;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.price-compare {
|
|
381
|
+
font-size: 20px;
|
|
382
|
+
color: #999;
|
|
383
|
+
text-decoration: line-through;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.product-short-description {
|
|
387
|
+
font-size: 16px;
|
|
388
|
+
line-height: 1.6;
|
|
389
|
+
color: #666;
|
|
390
|
+
margin-top: 8px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Form */
|
|
394
|
+
.product-form {
|
|
395
|
+
display: flex;
|
|
396
|
+
flex-direction: column;
|
|
397
|
+
gap: 20px;
|
|
398
|
+
margin-top: 24px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.product-variation-selector {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex-direction: column;
|
|
404
|
+
gap: 8px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.variation-label {
|
|
408
|
+
font-size: 14px;
|
|
409
|
+
font-weight: 600;
|
|
410
|
+
color: #111;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.variation-select {
|
|
414
|
+
width: 100%;
|
|
415
|
+
padding: 12px 16px;
|
|
416
|
+
font-size: 16px;
|
|
417
|
+
border: 1px solid #e0e0e0;
|
|
418
|
+
border-radius: var(--border-radius-medium);
|
|
419
|
+
background: #fff;
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
transition: border-color 0.2s ease;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.variation-select:hover {
|
|
425
|
+
border-color: #999;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.variation-select:focus {
|
|
429
|
+
outline: none;
|
|
430
|
+
border-color: #111;
|
|
431
|
+
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.product-quantity {
|
|
435
|
+
display: flex;
|
|
436
|
+
flex-direction: column;
|
|
437
|
+
gap: 8px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.product-quantity label {
|
|
441
|
+
font-size: 14px;
|
|
442
|
+
font-weight: 600;
|
|
443
|
+
color: #111;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.quantity-input {
|
|
447
|
+
width: 120px;
|
|
448
|
+
padding: 12px 16px;
|
|
449
|
+
font-size: 16px;
|
|
450
|
+
border: 1px solid #e0e0e0;
|
|
451
|
+
border-radius: 8px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.btn-add-to-cart {
|
|
455
|
+
width: 100%;
|
|
456
|
+
padding: 16px 24px;
|
|
457
|
+
font-size: 18px;
|
|
458
|
+
font-weight: 600;
|
|
459
|
+
color: #fff;
|
|
460
|
+
background: #111;
|
|
461
|
+
border: none;
|
|
462
|
+
border-radius: 8px;
|
|
463
|
+
cursor: pointer;
|
|
464
|
+
transition: background 0.2s ease, transform 0.1s ease;
|
|
465
|
+
margin-top: 8px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.btn-add-to-cart:hover:not(:disabled) {
|
|
469
|
+
background: #333;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.btn-add-to-cart:active:not(:disabled) {
|
|
473
|
+
transform: scale(0.98);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.btn-add-to-cart:disabled {
|
|
477
|
+
background: #d1d5db;
|
|
478
|
+
cursor: not-allowed;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.widget-empty {
|
|
482
|
+
padding: 60px 24px;
|
|
483
|
+
text-align: center;
|
|
484
|
+
color: #6b7280;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.widget-empty p {
|
|
488
|
+
margin: 0;
|
|
489
|
+
font-size: 14px;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* Mobile */
|
|
493
|
+
@media (max-width: 768px) {
|
|
494
|
+
.widget-single-product {
|
|
495
|
+
padding: 32px 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.widget-single-product__inner {
|
|
499
|
+
padding: 0 16px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.single-product-pdp__layout {
|
|
503
|
+
grid-template-columns: 1fr;
|
|
504
|
+
gap: 32px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.product-title {
|
|
508
|
+
font-size: 24px;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.price-current {
|
|
512
|
+
font-size: 24px;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.price-compare {
|
|
516
|
+
font-size: 18px;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
@media (max-width: 480px) {
|
|
521
|
+
.widget-single-product {
|
|
522
|
+
padding: 24px 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.widget-single-product__inner {
|
|
526
|
+
padding: 0 12px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.gallery-thumbnails {
|
|
530
|
+
gap: 8px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.gallery-thumbnail {
|
|
534
|
+
width: 60px;
|
|
535
|
+
height: 60px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.product-title {
|
|
539
|
+
font-size: 20px;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.btn-add-to-cart {
|
|
543
|
+
padding: 14px 20px;
|
|
544
|
+
font-size: 16px;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
</style>
|
|
548
|
+
|
|
549
|
+
{% if product %}
|
|
550
|
+
<script>
|
|
551
|
+
(function() {
|
|
552
|
+
const form = document.getElementById('singleProductForm');
|
|
553
|
+
if (!form) return;
|
|
554
|
+
|
|
555
|
+
const variantSelect = document.getElementById('singleProductVariant');
|
|
556
|
+
const priceElement = document.getElementById('singleProductPrice');
|
|
557
|
+
const addToCartBtn = form.querySelector('.btn-add-to-cart');
|
|
558
|
+
|
|
559
|
+
// Handle variant selection
|
|
560
|
+
if (variantSelect) {
|
|
561
|
+
variantSelect.addEventListener('change', function() {
|
|
562
|
+
const selectedOption = this.options[this.selectedIndex];
|
|
563
|
+
const variantPrice = parseFloat(selectedOption.dataset.variantPrice);
|
|
564
|
+
const variantMrp = parseFloat(selectedOption.dataset.variantMrp) || 0;
|
|
565
|
+
const isInStock = selectedOption.dataset.inStock === 'true';
|
|
566
|
+
|
|
567
|
+
// Update price
|
|
568
|
+
if (priceElement) {
|
|
569
|
+
const currencySymbol = '{{ shop.settings.currencySymbol | default: shop.currency | default: "$" }}';
|
|
570
|
+
priceElement.textContent = currencySymbol + variantPrice.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Update add to cart button
|
|
574
|
+
if (addToCartBtn) {
|
|
575
|
+
addToCartBtn.setAttribute('data-product-id', selectedOption.value);
|
|
576
|
+
addToCartBtn.disabled = !isInStock;
|
|
577
|
+
addToCartBtn.textContent = isInStock ? 'Add to Cart' : 'Out of Stock';
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Handle form submission
|
|
583
|
+
form.addEventListener('submit', function(e) {
|
|
584
|
+
e.preventDefault();
|
|
585
|
+
|
|
586
|
+
const productId = addToCartBtn ? addToCartBtn.getAttribute('data-product-id') : form.getAttribute('data-product-id');
|
|
587
|
+
const quantity = parseInt(document.getElementById('singleProductQuantity')?.value || '1');
|
|
588
|
+
|
|
589
|
+
// Prefer Theme.addToCart: unified add-to-cart flow + notifications + CartManager integration
|
|
590
|
+
if (window.Theme && typeof window.Theme.addToCart === 'function') {
|
|
591
|
+
window.Theme.addToCart(productId, quantity);
|
|
592
|
+
} else if (window.CartManager && typeof window.CartManager.addToCart === 'function') {
|
|
593
|
+
// Fallback to CartManager if Theme.addToCart is not available
|
|
594
|
+
window.CartManager.addToCart(productId, quantity)
|
|
595
|
+
.then(function() {
|
|
596
|
+
console.log('Product added to cart');
|
|
597
|
+
})
|
|
598
|
+
.catch(function(error) {
|
|
599
|
+
console.error('Failed to add to cart:', error);
|
|
600
|
+
alert('Failed to add product to cart. Please try again.');
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
console.warn('No add-to-cart handler available (Theme.addToCart / CartManager.addToCart)');
|
|
604
|
+
alert('Cart functionality not available. Please try again later.');
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
})();
|
|
608
|
+
</script>
|
|
609
|
+
{% endif %}
|
|
610
|
+
</section>
|