@runwell/shopify-toolkit 0.1.0 → 0.3.0
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/modules/_shared/css-tokens/assets/runwell-tokens.css +14 -0
- package/modules/_shared/css-tokens/module.json +13 -0
- package/modules/bundle-builder/README.md +40 -0
- package/modules/bundle-builder/assets/runwell-bundle-builder.css +383 -0
- package/modules/bundle-builder/module.json +26 -0
- package/modules/bundle-builder/sections/runwell-bundle-builder.liquid +370 -0
- package/modules/cart-cross-sell/README.md +32 -0
- package/modules/cart-cross-sell/module.json +15 -0
- package/modules/cart-cross-sell/snippets/runwell-cart-xsell.liquid +40 -0
- package/modules/cart-freeship-progress/README.md +29 -0
- package/modules/cart-freeship-progress/module.json +16 -0
- package/modules/cart-freeship-progress/snippets/runwell-cart-freeship.liquid +27 -0
- package/modules/cart-usps/README.md +22 -0
- package/modules/cart-usps/module.json +17 -0
- package/modules/cart-usps/snippets/runwell-cart-usps.liquid +11 -0
- package/modules/editorial-hero/sections/runwell-video-hero.liquid +9 -3
- package/modules/gift-with-purchase/README.md +36 -0
- package/modules/gift-with-purchase/assets/runwell-gwp.js +42 -0
- package/modules/gift-with-purchase/module.json +32 -0
- package/modules/gift-with-purchase/snippets/runwell-gwp.liquid +30 -0
- package/modules/loyalty-tiers/README.md +45 -0
- package/modules/loyalty-tiers/module.json +40 -0
- package/modules/loyalty-tiers/sections/runwell-tier-card.liquid +86 -0
- package/modules/product-badges/README.md +35 -0
- package/modules/product-badges/module.json +16 -0
- package/modules/product-badges/snippets/runwell-product-badges.liquid +19 -0
- package/modules/quantity-breaks/README.md +33 -0
- package/modules/quantity-breaks/module.json +35 -0
- package/modules/quantity-breaks/snippets/runwell-quantity-breaks.liquid +28 -0
- package/modules/quick-view/README.md +36 -0
- package/modules/quick-view/assets/runwell-quickview.js +153 -0
- package/modules/quick-view/module.json +14 -0
- package/modules/quick-view/snippets/runwell-quickview-modal.liquid +14 -0
- package/modules/quick-view/snippets/runwell-quickview-trigger.liquid +19 -0
- package/modules/subscriptions/README.md +37 -0
- package/modules/subscriptions/module.json +36 -0
- package/modules/subscriptions/snippets/runwell-subscription-picker.liquid +35 -0
- package/modules/wishlist/README.md +48 -0
- package/modules/wishlist/assets/runwell-wishlist.js +112 -0
- package/modules/wishlist/module.json +25 -0
- package/modules/wishlist/sections/runwell-wishlist-page.liquid +35 -0
- package/modules/wishlist/snippets/runwell-wishlist-icon.liquid +17 -0
- package/modules/wishlist/templates/page.wishlist.json +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
{{ 'runwell-bundle-builder.css' | asset_url | stylesheet_tag }}
|
|
2
|
+
|
|
3
|
+
<div class="runwell-bundle-builder color-{{ section.settings.color_scheme }} section-{{ section.id }}-padding">
|
|
4
|
+
<div class="page-width">
|
|
5
|
+
<div class="runwell-bundle-builder__grid">
|
|
6
|
+
<div class="runwell-bundle-builder__media">
|
|
7
|
+
{%- assign gallery_images = section.blocks | where: "type", "gallery_image" -%}
|
|
8
|
+
{%- if gallery_images.size > 0 -%}
|
|
9
|
+
<div class="runwell-bundle-builder__slideshow" data-slideshow>
|
|
10
|
+
{%- for block in gallery_images -%}
|
|
11
|
+
<div class="runwell-bundle-builder__slide {% if forloop.first %}runwell-bundle-builder__slide--active{% endif %}" data-slide="{{ forloop.index0 }}" {{ block.shopify_attributes }}>
|
|
12
|
+
{%- if block.settings.image != blank -%}
|
|
13
|
+
{{ block.settings.image | image_url: width: 800 | image_tag: class: 'runwell-bundle-builder__img', loading: forloop.first | default: 'eager', sizes: '(min-width: 990px) 50vw, 100vw' }}
|
|
14
|
+
{%- else -%}
|
|
15
|
+
<div class="runwell-bundle-builder__img runwell-bundle-builder__img--placeholder" aria-label="Image placeholder"></div>
|
|
16
|
+
{%- endif -%}
|
|
17
|
+
</div>
|
|
18
|
+
{%- endfor -%}
|
|
19
|
+
</div>
|
|
20
|
+
<div class="runwell-bundle-builder__thumbnails" data-thumbnails>
|
|
21
|
+
{%- for block in gallery_images -%}
|
|
22
|
+
<button class="runwell-bundle-builder__thumb {% if forloop.first %}runwell-bundle-builder__thumb--active{% endif %}" data-thumb="{{ forloop.index0 }}" aria-label="View image {{ forloop.index }}">
|
|
23
|
+
{%- if block.settings.image != blank -%}
|
|
24
|
+
{{ block.settings.image | image_url: width: 120, height: 120, crop: 'center' | image_tag: loading: 'lazy' }}
|
|
25
|
+
{%- else -%}
|
|
26
|
+
<div class="runwell-bundle-builder__thumb-img runwell-bundle-builder__thumb-img--placeholder" aria-label="Thumbnail placeholder"></div>
|
|
27
|
+
{%- endif -%}
|
|
28
|
+
</button>
|
|
29
|
+
{%- endfor -%}
|
|
30
|
+
</div>
|
|
31
|
+
{%- else -%}
|
|
32
|
+
<div class="runwell-bundle-builder__slideshow runwell-bundle-builder__slideshow--empty" data-slideshow>
|
|
33
|
+
<div class="runwell-bundle-builder__slide runwell-bundle-builder__slide--active" data-slide="0">
|
|
34
|
+
{%- if section.settings.product.featured_image != blank -%}
|
|
35
|
+
{{ section.settings.product.featured_image | image_url: width: 800 | image_tag: class: 'runwell-bundle-builder__img', loading: 'eager', alt: section.settings.product.title }}
|
|
36
|
+
{%- else -%}
|
|
37
|
+
<div class="runwell-bundle-builder__img runwell-bundle-builder__img--placeholder" aria-label="Add gallery images via the section editor"></div>
|
|
38
|
+
{%- endif -%}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
{%- endif -%}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="runwell-bundle-builder__info">
|
|
45
|
+
{% if section.settings.show_rating %}
|
|
46
|
+
<div class="runwell-bundle-builder__rating">
|
|
47
|
+
<span class="runwell-bundle-builder__stars">★★★★★</span>
|
|
48
|
+
<span class="runwell-bundle-builder__rating-text">Rated {{ section.settings.rating_score }} ({{ section.settings.rating_count }} Reviews)</span>
|
|
49
|
+
</div>
|
|
50
|
+
{% endif %}
|
|
51
|
+
|
|
52
|
+
{% if section.settings.product != blank %}
|
|
53
|
+
<h2 class="runwell-bundle-builder__title h1">{{ section.settings.product.title }}</h2>
|
|
54
|
+
{% else %}
|
|
55
|
+
<h2 class="runwell-bundle-builder__title h1">{{ section.settings.heading }}</h2>
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
{%- assign fomo_mode = section.settings.fomo_mode | default: 'none' -%}
|
|
59
|
+
{%- if fomo_mode != 'none' -%}
|
|
60
|
+
{%- comment -%} Dynamic FOMO deadline computation {%- endcomment -%}
|
|
61
|
+
{%- assign now_epoch = 'now' | date: '%s' | plus: 0 -%}
|
|
62
|
+
{%- assign cycle_seconds = section.settings.fomo_cycle_days | times: 86400 -%}
|
|
63
|
+
{%- assign cycle_position = now_epoch | modulo: cycle_seconds -%}
|
|
64
|
+
{%- assign remaining_seconds = cycle_seconds | minus: cycle_position -%}
|
|
65
|
+
{%- assign remaining_days = remaining_seconds | divided_by: 86400 -%}
|
|
66
|
+
{%- assign end_epoch = now_epoch | plus: remaining_seconds -%}
|
|
67
|
+
{%- assign end_date = end_epoch | date: '%B %e' -%}
|
|
68
|
+
|
|
69
|
+
<div class="runwell-bundle-builder__sale-heading">
|
|
70
|
+
<span class="runwell-bundle-builder__sale-line"></span>
|
|
71
|
+
<span class="runwell-bundle-builder__sale-text">
|
|
72
|
+
{%- if fomo_mode == 'discount' or fomo_mode == 'both' -%}
|
|
73
|
+
{{ section.settings.sale_prefix }}: Ends {{ end_date }}
|
|
74
|
+
{%- else -%}
|
|
75
|
+
{{ section.settings.sale_prefix }}
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
</span>
|
|
78
|
+
<span class="runwell-bundle-builder__sale-line"></span>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="runwell-bundle-builder__scarcity">
|
|
82
|
+
<span class="runwell-bundle-builder__scarcity-dot"></span>
|
|
83
|
+
Only {{ section.settings.fomo_stock_count }} left in stock
|
|
84
|
+
</div>
|
|
85
|
+
{%- elsif section.settings.sale_prefix != blank -%}
|
|
86
|
+
<div class="runwell-bundle-builder__sale-heading">
|
|
87
|
+
<span class="runwell-bundle-builder__sale-line"></span>
|
|
88
|
+
<span class="runwell-bundle-builder__sale-text">{{ section.settings.sale_prefix }}</span>
|
|
89
|
+
<span class="runwell-bundle-builder__sale-line"></span>
|
|
90
|
+
</div>
|
|
91
|
+
{%- endif -%}
|
|
92
|
+
|
|
93
|
+
<div class="runwell-bundle-builder__options">
|
|
94
|
+
{%- assign has_popular = false -%}
|
|
95
|
+
{%- for block in section.blocks -%}
|
|
96
|
+
{%- if block.type == 'bundle_option' and block.settings.popular -%}
|
|
97
|
+
{%- assign has_popular = true -%}
|
|
98
|
+
{%- endif -%}
|
|
99
|
+
{%- endfor -%}
|
|
100
|
+
{% for block in section.blocks %}
|
|
101
|
+
{% if block.type != 'bundle_option' %}{% continue %}{% endif %}
|
|
102
|
+
{%- assign is_selected = false -%}
|
|
103
|
+
{%- if has_popular and block.settings.popular -%}
|
|
104
|
+
{%- assign is_selected = true -%}
|
|
105
|
+
{%- elsif has_popular == false and forloop.first -%}
|
|
106
|
+
{%- assign is_selected = true -%}
|
|
107
|
+
{%- endif -%}
|
|
108
|
+
<label class="runwell-bundle-builder__option {% if block.settings.popular %}runwell-bundle-builder__option--popular{% endif %} {% if is_selected %}runwell-bundle-builder__option--selected{% endif %}" {{ block.shopify_attributes }}>
|
|
109
|
+
{% if block.settings.popular %}
|
|
110
|
+
<span class="runwell-bundle-builder__popular-badge">MOST POPULAR</span>
|
|
111
|
+
{% endif %}
|
|
112
|
+
<input type="radio" name="bundle-{{ section.id }}" value="{{ block.id }}" {% if is_selected %}checked{% endif %}>
|
|
113
|
+
<div class="runwell-bundle-builder__option-radio"></div>
|
|
114
|
+
<div class="runwell-bundle-builder__option-content">
|
|
115
|
+
<div class="runwell-bundle-builder__option-header">
|
|
116
|
+
<span class="runwell-bundle-builder__option-title">{{ block.settings.title }}</span>
|
|
117
|
+
<div class="runwell-bundle-builder__option-pricing">
|
|
118
|
+
<span class="runwell-bundle-builder__option-price">${{ block.settings.price }}</span>
|
|
119
|
+
{% if block.settings.compare_price != blank %}
|
|
120
|
+
<span class="runwell-bundle-builder__option-compare">${{ block.settings.compare_price }}</span>
|
|
121
|
+
{% endif %}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="runwell-bundle-builder__option-badges">
|
|
125
|
+
{% if block.settings.savings != blank %}
|
|
126
|
+
<span class="runwell-bundle-builder__badge runwell-bundle-builder__badge--save">SAVE ${{ block.settings.savings }}</span>
|
|
127
|
+
{% endif %}
|
|
128
|
+
{% if block.settings.free_shipping %}
|
|
129
|
+
<span class="runwell-bundle-builder__badge runwell-bundle-builder__badge--shipping">FREE SHIPPING</span>
|
|
130
|
+
{% endif %}
|
|
131
|
+
</div>
|
|
132
|
+
{% if block.settings.free_gift_text != blank %}
|
|
133
|
+
<div class="runwell-bundle-builder__free-gift">
|
|
134
|
+
<span class="runwell-bundle-builder__gift-icon">🎁</span>
|
|
135
|
+
<span>{{ block.settings.free_gift_text }}</span>
|
|
136
|
+
</div>
|
|
137
|
+
{% endif %}
|
|
138
|
+
</div>
|
|
139
|
+
</label>
|
|
140
|
+
{% endfor %}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="runwell-bundle-builder__atc-wrap">
|
|
144
|
+
<button class="runwell-bundle-builder__atc button" type="button" data-atc>
|
|
145
|
+
<span class="runwell-bundle-builder__atc-text">ADD TO CART</span>
|
|
146
|
+
<span class="runwell-bundle-builder__atc-loading" style="display:none;">Adding...</span>
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{% if section.settings.show_trust_badges %}
|
|
151
|
+
<div class="runwell-bundle-builder__trust">
|
|
152
|
+
<span>✅ {{ section.settings.trust_1 }}</span>
|
|
153
|
+
<span>✅ {{ section.settings.trust_2 }}</span>
|
|
154
|
+
<span>✅ {{ section.settings.trust_3 }}</span>
|
|
155
|
+
</div>
|
|
156
|
+
{% endif %}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
/* Bundle option selection */
|
|
164
|
+
document.querySelectorAll('.runwell-bundle-builder__option').forEach(option => {
|
|
165
|
+
option.addEventListener('click', function() {
|
|
166
|
+
this.closest('.runwell-bundle-builder__options').querySelectorAll('.runwell-bundle-builder__option').forEach(o => o.classList.remove('runwell-bundle-builder__option--selected'));
|
|
167
|
+
this.classList.add('runwell-bundle-builder__option--selected');
|
|
168
|
+
this.querySelector('input').checked = true;
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/* Thumbnail slideshow */
|
|
173
|
+
document.querySelectorAll('[data-thumbnails]').forEach(container => {
|
|
174
|
+
container.querySelectorAll('[data-thumb]').forEach(thumb => {
|
|
175
|
+
thumb.addEventListener('click', function() {
|
|
176
|
+
const idx = this.dataset.thumb;
|
|
177
|
+
const section = this.closest('.runwell-bundle-builder');
|
|
178
|
+
section.querySelectorAll('.runwell-bundle-builder__slide').forEach(s => s.classList.remove('runwell-bundle-builder__slide--active'));
|
|
179
|
+
section.querySelectorAll('.runwell-bundle-builder__thumb').forEach(t => t.classList.remove('runwell-bundle-builder__thumb--active'));
|
|
180
|
+
section.querySelector('[data-slide="' + idx + '"]').classList.add('runwell-bundle-builder__slide--active');
|
|
181
|
+
this.classList.add('runwell-bundle-builder__thumb--active');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/* Add to Cart (Shopify Ajax API) */
|
|
187
|
+
document.querySelectorAll('[data-atc]').forEach(btn => {
|
|
188
|
+
btn.addEventListener('click', function() {
|
|
189
|
+
const section = this.closest('.runwell-bundle-builder');
|
|
190
|
+
const selected = section.querySelector('.runwell-bundle-builder__option--selected');
|
|
191
|
+
if (!selected) return;
|
|
192
|
+
|
|
193
|
+
const title = selected.querySelector('.runwell-bundle-builder__option-title');
|
|
194
|
+
const qtyMatch = title ? title.textContent.match(/^(\d+)x/) : null;
|
|
195
|
+
const qty = qtyMatch ? parseInt(qtyMatch[1]) : 1;
|
|
196
|
+
|
|
197
|
+
const textEl = this.querySelector('.runwell-bundle-builder__atc-text');
|
|
198
|
+
const loadEl = this.querySelector('.runwell-bundle-builder__atc-loading');
|
|
199
|
+
textEl.style.display = 'none';
|
|
200
|
+
loadEl.style.display = 'inline';
|
|
201
|
+
this.disabled = true;
|
|
202
|
+
|
|
203
|
+
fetch('/cart/add.js', {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({ items: [{ id: {{ section.settings.product.selected_or_first_available_variant.id | default: 0 }}, quantity: qty }] })
|
|
207
|
+
})
|
|
208
|
+
.then(r => r.json())
|
|
209
|
+
.then(() => {
|
|
210
|
+
textEl.textContent = 'ADDED!';
|
|
211
|
+
textEl.style.display = 'inline';
|
|
212
|
+
loadEl.style.display = 'none';
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
textEl.textContent = 'ADD TO CART';
|
|
215
|
+
this.disabled = false;
|
|
216
|
+
}, 2000);
|
|
217
|
+
})
|
|
218
|
+
.catch(() => {
|
|
219
|
+
textEl.textContent = 'ADD TO CART';
|
|
220
|
+
textEl.style.display = 'inline';
|
|
221
|
+
loadEl.style.display = 'none';
|
|
222
|
+
this.disabled = false;
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
{% schema %}
|
|
229
|
+
{
|
|
230
|
+
"name": "Runwell Bundle Builder",
|
|
231
|
+
"tag": "section",
|
|
232
|
+
"class": "section-runwell-bundle-builder",
|
|
233
|
+
"settings": [
|
|
234
|
+
{
|
|
235
|
+
"type": "product",
|
|
236
|
+
"id": "product",
|
|
237
|
+
"label": "Product"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"type": "text",
|
|
241
|
+
"id": "heading",
|
|
242
|
+
"label": "Heading (fallback)",
|
|
243
|
+
"default": "Build your bundle"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"type": "text",
|
|
247
|
+
"id": "sale_prefix",
|
|
248
|
+
"label": "Sale heading prefix",
|
|
249
|
+
"default": "Limited time offer"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"type": "select",
|
|
253
|
+
"id": "fomo_mode",
|
|
254
|
+
"label": "FOMO mode",
|
|
255
|
+
"options": [
|
|
256
|
+
{ "value": "discount", "label": "Discount deadline" },
|
|
257
|
+
{ "value": "scarcity", "label": "Stock scarcity" },
|
|
258
|
+
{ "value": "both", "label": "Both" },
|
|
259
|
+
{ "value": "none", "label": "None (static heading)" }
|
|
260
|
+
],
|
|
261
|
+
"default": "both"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"type": "range",
|
|
265
|
+
"id": "fomo_cycle_days",
|
|
266
|
+
"label": "FOMO cycle (days)",
|
|
267
|
+
"min": 7,
|
|
268
|
+
"max": 90,
|
|
269
|
+
"step": 1,
|
|
270
|
+
"default": 30,
|
|
271
|
+
"unit": "d"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"type": "range",
|
|
275
|
+
"id": "fomo_stock_count",
|
|
276
|
+
"label": "Stock scarcity count",
|
|
277
|
+
"min": 5,
|
|
278
|
+
"max": 100,
|
|
279
|
+
"step": 1,
|
|
280
|
+
"default": 47,
|
|
281
|
+
"unit": "qty"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"type": "checkbox",
|
|
285
|
+
"id": "show_rating",
|
|
286
|
+
"label": "Show star rating",
|
|
287
|
+
"default": true
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"type": "text",
|
|
291
|
+
"id": "rating_score",
|
|
292
|
+
"label": "Rating score",
|
|
293
|
+
"default": "4.8/5"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"type": "text",
|
|
297
|
+
"id": "rating_count",
|
|
298
|
+
"label": "Rating count",
|
|
299
|
+
"default": "2,400+"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"type": "checkbox",
|
|
303
|
+
"id": "show_trust_badges",
|
|
304
|
+
"label": "Show trust badges",
|
|
305
|
+
"default": true
|
|
306
|
+
},
|
|
307
|
+
{ "type": "text", "id": "trust_1", "label": "Trust badge 1", "default": "90-Day Guarantee" },
|
|
308
|
+
{ "type": "text", "id": "trust_2", "label": "Trust badge 2", "default": "Easy Returns" },
|
|
309
|
+
{ "type": "text", "id": "trust_3", "label": "Trust badge 3", "default": "Free Shipping" },
|
|
310
|
+
{
|
|
311
|
+
"type": "color_scheme",
|
|
312
|
+
"id": "color_scheme",
|
|
313
|
+
"label": "Color scheme",
|
|
314
|
+
"default": "scheme-1"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"type": "range",
|
|
318
|
+
"id": "padding_top",
|
|
319
|
+
"label": "Top padding",
|
|
320
|
+
"min": 0, "max": 100, "step": 4, "default": 40, "unit": "px"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"type": "range",
|
|
324
|
+
"id": "padding_bottom",
|
|
325
|
+
"label": "Bottom padding",
|
|
326
|
+
"min": 0, "max": 100, "step": 4, "default": 40, "unit": "px"
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
"blocks": [
|
|
330
|
+
{
|
|
331
|
+
"type": "gallery_image",
|
|
332
|
+
"name": "Gallery image",
|
|
333
|
+
"settings": [
|
|
334
|
+
{ "type": "image_picker", "id": "image", "label": "Image" },
|
|
335
|
+
{ "type": "text", "id": "alt", "label": "Alt text", "default": "Product image" }
|
|
336
|
+
]
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"type": "bundle_option",
|
|
340
|
+
"name": "Bundle option",
|
|
341
|
+
"settings": [
|
|
342
|
+
{ "type": "text", "id": "title", "label": "Title (e.g. \"1x Product\")", "default": "1x Product" },
|
|
343
|
+
{ "type": "text", "id": "price", "label": "Price", "default": "24.99" },
|
|
344
|
+
{ "type": "text", "id": "compare_price", "label": "Compare at price", "default": "49.99" },
|
|
345
|
+
{ "type": "text", "id": "savings", "label": "Savings amount", "default": "25.00" },
|
|
346
|
+
{ "type": "checkbox", "id": "free_shipping", "label": "Free shipping badge", "default": false },
|
|
347
|
+
{ "type": "checkbox", "id": "popular", "label": "Most popular (highlighted)", "default": false },
|
|
348
|
+
{ "type": "text", "id": "free_gift_text", "label": "Free gift text" }
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
"presets": [
|
|
353
|
+
{
|
|
354
|
+
"name": "Runwell Bundle Builder",
|
|
355
|
+
"blocks": [
|
|
356
|
+
{ "type": "bundle_option", "settings": { "title": "1x Product", "price": "24.99", "compare_price": "49.99", "savings": "25.00", "free_shipping": false, "popular": false } },
|
|
357
|
+
{ "type": "bundle_option", "settings": { "title": "2x Product", "price": "39.99", "compare_price": "99.98", "savings": "59.99", "free_shipping": true, "popular": true } },
|
|
358
|
+
{ "type": "bundle_option", "settings": { "title": "3x Product", "price": "59.99", "compare_price": "149.97", "savings": "89.98", "free_shipping": true, "popular": false, "free_gift_text": "+ Free gift with purchase" } }
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
{% endschema %}
|
|
364
|
+
|
|
365
|
+
<style>
|
|
366
|
+
.section-{{ section.id }}-padding {
|
|
367
|
+
padding-top: {{ section.settings.padding_top }}px;
|
|
368
|
+
padding-bottom: {{ section.settings.padding_bottom }}px;
|
|
369
|
+
}
|
|
370
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# cart-cross-sell
|
|
2
|
+
|
|
3
|
+
Cart drawer cross-sell suggestion. Picks the first available product not already in cart and renders a one-click Add card.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `snippets/runwell-cart-xsell.liquid`
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
|
|
11
|
+
In `snippets/cart-drawer.liquid` body:
|
|
12
|
+
|
|
13
|
+
```liquid
|
|
14
|
+
{% render 'runwell-cart-xsell' %}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Config
|
|
18
|
+
|
|
19
|
+
| Key | Default | Notes |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `eyebrow` | `Pairs well with` | Tag above the suggestion |
|
|
22
|
+
| `cta_label` | `Add` | Button label |
|
|
23
|
+
|
|
24
|
+
## Replaces
|
|
25
|
+
|
|
26
|
+
Rebuy, One Click Upsell (OCU) pre-purchase display layer.
|
|
27
|
+
|
|
28
|
+
## Future variants
|
|
29
|
+
|
|
30
|
+
- `tag-based`: filter to products tagged complementary to cart contents
|
|
31
|
+
- `metafield-curated`: per-product complementary set in `lushi.complements` metafield
|
|
32
|
+
- `recommendations-api`: Shopify `recommendations.json?intent=complementary`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cart-cross-sell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "conversion",
|
|
5
|
+
"description": "Cart drawer cross-sell card. Suggests one related product not yet in cart with one-click Add. Replaces Rebuy / OneClickUpsell pre-purchase upsell.",
|
|
6
|
+
"files": {
|
|
7
|
+
"snippets": ["snippets/runwell-cart-xsell.liquid"]
|
|
8
|
+
},
|
|
9
|
+
"config": {
|
|
10
|
+
"schema": {
|
|
11
|
+
"eyebrow": { "type": "string", "default": "Pairs well with" },
|
|
12
|
+
"cta_label": { "type": "string", "default": "Add" }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Runwell cart cross-sell. Surfaces the first available product not
|
|
3
|
+
already in cart. Replaces Rebuy/OneClickUpsell pre-purchase upsell
|
|
4
|
+
display. Render inside cart-drawer.liquid via:
|
|
5
|
+
{% render 'runwell-cart-xsell' %}
|
|
6
|
+
{%- endcomment -%}
|
|
7
|
+
|
|
8
|
+
{%- if cart.item_count > 0 -%}
|
|
9
|
+
{%- assign cart_handles = cart.items | map: 'handle' -%}
|
|
10
|
+
{%- assign suggestion = blank -%}
|
|
11
|
+
{%- for p in collections.all.products -%}
|
|
12
|
+
{%- unless cart_handles contains p.handle -%}
|
|
13
|
+
{%- if p.available -%}
|
|
14
|
+
{%- assign suggestion = p -%}
|
|
15
|
+
{%- break -%}
|
|
16
|
+
{%- endif -%}
|
|
17
|
+
{%- endunless -%}
|
|
18
|
+
{%- endfor -%}
|
|
19
|
+
{%- if suggestion != blank -%}
|
|
20
|
+
<div class="runwell-cart-xsell" data-runwell-xsell>
|
|
21
|
+
<p class="runwell-cart-xsell__eyebrow">{{config.eyebrow}}</p>
|
|
22
|
+
<div class="runwell-cart-xsell__row">
|
|
23
|
+
{%- if suggestion.featured_image -%}
|
|
24
|
+
<a href="{{ suggestion.url }}" class="runwell-cart-xsell__media" aria-hidden="true" tabindex="-1">
|
|
25
|
+
<img src="{{ suggestion.featured_image | image_url: width: 120 }}" alt="" width="60" height="60" loading="lazy">
|
|
26
|
+
</a>
|
|
27
|
+
{%- endif -%}
|
|
28
|
+
<div class="runwell-cart-xsell__body">
|
|
29
|
+
<a href="{{ suggestion.url }}" class="runwell-cart-xsell__title">{{ suggestion.title }}</a>
|
|
30
|
+
<span class="runwell-cart-xsell__price">{{ suggestion.price | money }}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<form action="{{ routes.cart_add_url }}" method="post" enctype="multipart/form-data" class="runwell-cart-xsell__form">
|
|
33
|
+
<input type="hidden" name="id" value="{{ suggestion.selected_or_first_available_variant.id }}">
|
|
34
|
+
<input type="hidden" name="quantity" value="1">
|
|
35
|
+
<button type="submit" class="runwell-cart-xsell__cta">{{config.cta_label}}</button>
|
|
36
|
+
</form>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{%- endif -%}
|
|
40
|
+
{%- endif -%}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# cart-freeship-progress
|
|
2
|
+
|
|
3
|
+
Cart drawer free-shipping progress bar. Renders dynamic remaining-to-free-ship message + a CSS progress fill that updates with cart total.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `snippets/runwell-cart-freeship.liquid`
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
|
|
11
|
+
In `snippets/cart-drawer.liquid` (or wherever your cart drawer body is):
|
|
12
|
+
|
|
13
|
+
```liquid
|
|
14
|
+
{% render 'runwell-cart-freeship' %}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
CSS is part of `_shared/css-tokens` + the styles already present in the merchant theme stylesheet (look for `.runwell-freeship` rules in your CSS).
|
|
18
|
+
|
|
19
|
+
## Config
|
|
20
|
+
|
|
21
|
+
| Key | Default | Notes |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `threshold_cents` | `7500` | Threshold in cents (USD `$75 = 7500`). Adjust for currency. |
|
|
24
|
+
| `away_text` | `away from free shipping.` | Text appended after the dynamic remaining amount |
|
|
25
|
+
| `unlocked_message` | `You unlocked free shipping. <strong>Glow on.</strong>` | HTML shown when cart total clears the threshold |
|
|
26
|
+
|
|
27
|
+
## Replaces
|
|
28
|
+
|
|
29
|
+
Bold Free Shipping Manager, similar app features.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cart-freeship-progress",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "conversion",
|
|
5
|
+
"description": "Cart drawer free-shipping progress bar. Shows remaining $$ to free ship + visual progress fill. Replaces Bold Free Shipping Manager and similar app features.",
|
|
6
|
+
"files": {
|
|
7
|
+
"snippets": ["snippets/runwell-cart-freeship.liquid"]
|
|
8
|
+
},
|
|
9
|
+
"config": {
|
|
10
|
+
"schema": {
|
|
11
|
+
"threshold_cents": { "type": "number", "default": 7500, "label": "Free shipping threshold (cents)" },
|
|
12
|
+
"away_text": { "type": "string", "default": "away from free shipping.", "label": "Text after remaining amount" },
|
|
13
|
+
"unlocked_message": { "type": "string", "default": "You unlocked free shipping. <strong>Glow on.</strong>", "label": "HTML shown when threshold met" }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Runwell cart free-shipping progress bar.
|
|
3
|
+
Renders inside cart-drawer.liquid via {% render 'runwell-cart-freeship' %}.
|
|
4
|
+
Replaces inline implementations and the freeship-bar feature in apps
|
|
5
|
+
like Vitals / Bold Free Shipping Manager.
|
|
6
|
+
{%- endcomment -%}
|
|
7
|
+
|
|
8
|
+
{%- assign runwell_freeship_threshold_cents = {{config.threshold_cents}} -%}
|
|
9
|
+
{%- assign runwell_remaining = runwell_freeship_threshold_cents | minus: cart.total_price -%}
|
|
10
|
+
<div class="runwell-freeship">
|
|
11
|
+
{%- if cart.item_count > 0 -%}
|
|
12
|
+
{%- if runwell_remaining > 0 -%}
|
|
13
|
+
<p class="runwell-freeship__msg">
|
|
14
|
+
You're <strong>{{ runwell_remaining | money }}</strong> {{config.away_text}}
|
|
15
|
+
</p>
|
|
16
|
+
{%- else -%}
|
|
17
|
+
<p class="runwell-freeship__msg runwell-freeship__msg--unlocked">
|
|
18
|
+
{{config.unlocked_message}}
|
|
19
|
+
</p>
|
|
20
|
+
{%- endif -%}
|
|
21
|
+
<div class="runwell-freeship__track" aria-hidden="true">
|
|
22
|
+
{%- assign pct = cart.total_price | times: 100.0 | divided_by: runwell_freeship_threshold_cents -%}
|
|
23
|
+
{%- if pct > 100 -%}{%- assign pct = 100 -%}{%- endif -%}
|
|
24
|
+
<div class="runwell-freeship__fill" style="width: {{ pct }}%"></div>
|
|
25
|
+
</div>
|
|
26
|
+
{%- endif -%}
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# cart-usps
|
|
2
|
+
|
|
3
|
+
Three brand promises listed at the bottom of the cart drawer. Reinforces trust at the moment of decision.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `snippets/runwell-cart-usps.liquid`
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
|
|
11
|
+
```liquid
|
|
12
|
+
{% render 'runwell-cart-usps' %}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Config
|
|
16
|
+
|
|
17
|
+
| Key | Default | Notes |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `icon` | `✓` | Bullet icon (HTML entity or emoji) |
|
|
20
|
+
| `usp_1` | `30-day satisfaction promise` | First USP |
|
|
21
|
+
| `usp_2` | `Editorially curated, not bulk-stocked` | Second USP |
|
|
22
|
+
| `usp_3` | `Every product paired with a journal post` | Third USP |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cart-usps",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "conversion",
|
|
5
|
+
"description": "Three brand promises listed at the bottom of the cart drawer. Native, no app.",
|
|
6
|
+
"files": {
|
|
7
|
+
"snippets": ["snippets/runwell-cart-usps.liquid"]
|
|
8
|
+
},
|
|
9
|
+
"config": {
|
|
10
|
+
"schema": {
|
|
11
|
+
"icon": { "type": "string", "default": "✓", "label": "Bullet icon (HTML entity or emoji)" },
|
|
12
|
+
"usp_1": { "type": "string", "default": "30-day satisfaction promise" },
|
|
13
|
+
"usp_2": { "type": "string", "default": "Editorially curated, not bulk-stocked" },
|
|
14
|
+
"usp_3": { "type": "string", "default": "Every product paired with a journal post" }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Runwell cart USPs. Three short bullets at the bottom of the cart
|
|
3
|
+
drawer reinforcing brand promises. Render via:
|
|
4
|
+
{% render 'runwell-cart-usps' %}
|
|
5
|
+
{%- endcomment -%}
|
|
6
|
+
|
|
7
|
+
<ul class="runwell-cart-usps" aria-label="Brand promises">
|
|
8
|
+
<li><span aria-hidden="true">{{config.icon}}</span> {{config.usp_1}}</li>
|
|
9
|
+
<li><span aria-hidden="true">{{config.icon}}</span> {{config.usp_2}}</li>
|
|
10
|
+
<li><span aria-hidden="true">{{config.icon}}</span> {{config.usp_3}}</li>
|
|
11
|
+
</ul>
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
height="1080"
|
|
29
29
|
loading="eager"
|
|
30
30
|
>
|
|
31
|
-
{%-
|
|
32
|
-
{%- comment -%} Fallback to bundled
|
|
31
|
+
{%- elsif section.settings.fallback_asset != blank -%}
|
|
32
|
+
{%- comment -%} Fallback to bundled brand image so the hero is never blank pre-launch {%- endcomment -%}
|
|
33
33
|
<img
|
|
34
34
|
class="runwell-video-hero__video"
|
|
35
|
-
src="{{
|
|
35
|
+
src="{{ section.settings.fallback_asset | asset_url }}"
|
|
36
36
|
alt="{{ section.settings.heading | escape }}"
|
|
37
37
|
width="1920"
|
|
38
38
|
height="1080"
|
|
@@ -109,6 +109,12 @@
|
|
|
109
109
|
"id": "poster_image",
|
|
110
110
|
"label": "Poster image (and fallback)"
|
|
111
111
|
},
|
|
112
|
+
{
|
|
113
|
+
"type": "text",
|
|
114
|
+
"id": "fallback_asset",
|
|
115
|
+
"label": "Fallback asset filename",
|
|
116
|
+
"info": "Bundled theme asset filename (e.g. lushi-hero-bg.jpg). Used when video_url and poster_image are blank."
|
|
117
|
+
},
|
|
112
118
|
{
|
|
113
119
|
"type": "range",
|
|
114
120
|
"id": "min_height",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# gift-with-purchase
|
|
2
|
+
|
|
3
|
+
Auto-add a gift product to the cart when total clears a threshold. Shows progress message before unlock and confirmation after. Combine with a Shopify 100%-off automatic discount on the gift SKU so the line lands free at checkout.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `snippets/runwell-gwp.liquid`. Cart-drawer snippet rendering progress + unlock state.
|
|
8
|
+
- `assets/runwell-gwp.js`. Auto-adder. Posts to `/cart/add.js` once when threshold is met and gift is not yet in cart.
|
|
9
|
+
|
|
10
|
+
## How to use
|
|
11
|
+
|
|
12
|
+
1. Run admin steps in `module.json`: create the gift product, create the 100% off automatic discount targeting it
|
|
13
|
+
2. Render the snippet in `snippets/cart-drawer.liquid`:
|
|
14
|
+
```liquid
|
|
15
|
+
{% render 'runwell-gwp' %}
|
|
16
|
+
```
|
|
17
|
+
3. Sync: `runwell-shopify sync`
|
|
18
|
+
|
|
19
|
+
## Behaviour
|
|
20
|
+
|
|
21
|
+
- Cart total below threshold: shows "Spend $X more for [unlocked_message]"
|
|
22
|
+
- Cart total above threshold AND gift not yet in cart: auto-adds gift, shows "[unlocked_message]"
|
|
23
|
+
- Cart total above threshold AND gift in cart: shows "[unlocked_message]" only
|
|
24
|
+
|
|
25
|
+
## Config
|
|
26
|
+
|
|
27
|
+
| Key | Default | Notes |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `threshold_cents` | `10000` | $100 default. Adjust per AOV. |
|
|
30
|
+
| `gift_handle` | `free-gift` | Product handle for the auto-added gift |
|
|
31
|
+
| `unlocked_message` | `You earned a free gift.` | Shown when threshold met |
|
|
32
|
+
| `locked_message_suffix` | `a free gift.` | Appended to "Spend $X more for..." |
|
|
33
|
+
|
|
34
|
+
## Replaces
|
|
35
|
+
|
|
36
|
+
Free Gifts BOGO Plus, Bold Free Gifts, Tada free gift apps.
|