@runwell/shopify-toolkit 0.2.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.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# bundle-builder
|
|
2
|
+
|
|
3
|
+
PDP section for build-your-own-bundle merchandising. Slideshow gallery + thumbnail nav + radio bundle picker (1x / 2x / 3x with savings, free-ship and free-gift badges) + sticky ATC + FOMO countdown + scarcity. Migrated from Lusha's bundle-selector.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `sections/runwell-bundle-builder.liquid`. Section template (392 lines).
|
|
8
|
+
- `assets/runwell-bundle-builder.css`. ~370 lines of styling, brand-aware via `var(--runwell-*)`.
|
|
9
|
+
|
|
10
|
+
## Behaviour
|
|
11
|
+
|
|
12
|
+
- **Gallery**: configurable via section blocks of type `gallery_image`. If no images set, falls back to the product's featured image, then to a brand-coloured placeholder.
|
|
13
|
+
- **Thumbnails**: clickable navigation between gallery images.
|
|
14
|
+
- **Bundle options**: configurable via section blocks of type `bundle_option`. Each one shows: title, price, compare-at price, savings badge, free-ship badge, "MOST POPULAR" highlight, free-gift text.
|
|
15
|
+
- **FOMO mode**: `discount` (countdown to sale end), `scarcity` (low-stock count), `both`, or `none`. The deadline rotates on a configurable cycle so it never expires for a returning visitor.
|
|
16
|
+
- **Trust badges**: 3 configurable promise lines below the ATC.
|
|
17
|
+
- **ATC**: posts to `/cart/add.js` with quantity parsed from the title (`"3x Product"` adds qty 3).
|
|
18
|
+
|
|
19
|
+
## How to use
|
|
20
|
+
|
|
21
|
+
1. Sync: `runwell-shopify sync`
|
|
22
|
+
2. In any product template (or a custom landing page), add the section "Runwell Bundle Builder"
|
|
23
|
+
3. Inside the section, add `gallery_image` blocks for product photography
|
|
24
|
+
4. Add `bundle_option` blocks for each bundle tier (typically 3 with the middle marked "popular")
|
|
25
|
+
5. Configure FOMO mode if desired
|
|
26
|
+
6. Style further via the brand's `--runwell-*` CSS custom properties
|
|
27
|
+
|
|
28
|
+
## Lineage
|
|
29
|
+
|
|
30
|
+
Migrated from `guabrasha-store/sections/bundle-selector.liquid` (Lusha gua sha brand). Class prefix renamed from `bundle-selector` to `runwell-bundle-builder`. Lusha-specific hardcoded gallery filenames replaced with a placeholder pattern. Lusha-specific copy ("Lusha Sculpting Brush", "Spring Sculpting Sale") replaced with generic defaults that the merchant overrides via section settings.
|
|
31
|
+
|
|
32
|
+
## Replaces
|
|
33
|
+
|
|
34
|
+
Bundle Builder, BYOB Bundles by Pickystory, Bundler. The toolkit version is display + ATC only; if you need true bundle-as-SKU pricing the merchant should pair this with a Shopify Functions discount.
|
|
35
|
+
|
|
36
|
+
## Future variants
|
|
37
|
+
|
|
38
|
+
- `step-by-step` (single-product, multi-step BYOB walkthrough)
|
|
39
|
+
- `mix-and-match` (any 3 from a curated set)
|
|
40
|
+
- `subscription-bundle` (bundle with subscription price option)
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
.runwell-bundle-builder__grid {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: 1fr 1fr;
|
|
4
|
+
gap: 2.4rem;
|
|
5
|
+
align-items: start;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Slideshow Gallery */
|
|
9
|
+
.runwell-bundle-builder__slideshow {
|
|
10
|
+
position: relative;
|
|
11
|
+
border-radius: 12px;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
background: #E8D5C4;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.runwell-bundle-builder__slide {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.runwell-bundle-builder__slide--active {
|
|
21
|
+
display: block;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.runwell-bundle-builder__img {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: auto;
|
|
27
|
+
display: block;
|
|
28
|
+
aspect-ratio: 1 / 1;
|
|
29
|
+
object-fit: cover;
|
|
30
|
+
max-height: calc(100vh - 260px);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Thumbnail Strip - ALWAYS visible including mobile */
|
|
34
|
+
.runwell-bundle-builder__thumbnails {
|
|
35
|
+
display: flex;
|
|
36
|
+
gap: 8px;
|
|
37
|
+
margin-top: 12px;
|
|
38
|
+
overflow-x: auto;
|
|
39
|
+
scrollbar-width: thin;
|
|
40
|
+
scrollbar-color: #EADFD4 transparent;
|
|
41
|
+
padding-bottom: 4px;
|
|
42
|
+
-webkit-overflow-scrolling: touch;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.runwell-bundle-builder__thumbnails::-webkit-scrollbar {
|
|
46
|
+
height: 4px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.runwell-bundle-builder__thumbnails::-webkit-scrollbar-thumb {
|
|
50
|
+
background: #EADFD4;
|
|
51
|
+
border-radius: 2px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.runwell-bundle-builder__thumb {
|
|
55
|
+
flex: 0 0 72px;
|
|
56
|
+
height: 72px;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
border: 2px solid transparent;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
padding: 0;
|
|
62
|
+
background: #E8D5C4;
|
|
63
|
+
transition: border-color 0.2s, opacity 0.2s;
|
|
64
|
+
opacity: 0.7;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.runwell-bundle-builder__thumb--active {
|
|
68
|
+
border-color: #3F5B4C;
|
|
69
|
+
opacity: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.runwell-bundle-builder__thumb:hover {
|
|
73
|
+
opacity: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.runwell-bundle-builder__thumb img {
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 100%;
|
|
79
|
+
object-fit: cover;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Rating */
|
|
83
|
+
.runwell-bundle-builder__rating {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
margin-bottom: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.runwell-bundle-builder__stars {
|
|
91
|
+
color: #E8B931;
|
|
92
|
+
font-size: 1.6rem;
|
|
93
|
+
letter-spacing: 1px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.runwell-bundle-builder__rating-text {
|
|
97
|
+
font-size: 1.4rem;
|
|
98
|
+
font-weight: 600;
|
|
99
|
+
font-style: italic;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Title */
|
|
103
|
+
.runwell-bundle-builder__title {
|
|
104
|
+
margin-top: 0.4rem;
|
|
105
|
+
margin-bottom: 1rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Sale heading */
|
|
109
|
+
.runwell-bundle-builder__sale-heading {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 1.2rem;
|
|
113
|
+
margin-bottom: 1.2rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.runwell-bundle-builder__sale-line {
|
|
117
|
+
flex: 1;
|
|
118
|
+
height: 1px;
|
|
119
|
+
background: #2A2622;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.runwell-bundle-builder__sale-text {
|
|
123
|
+
font-weight: 700;
|
|
124
|
+
font-size: 1.5rem;
|
|
125
|
+
white-space: nowrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Scarcity indicator */
|
|
129
|
+
.runwell-bundle-builder__scarcity {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
gap: 6px;
|
|
134
|
+
font-size: 1.3rem;
|
|
135
|
+
font-weight: 500;
|
|
136
|
+
color: #b45309;
|
|
137
|
+
margin-bottom: 1rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.runwell-bundle-builder__scarcity-dot {
|
|
141
|
+
width: 8px;
|
|
142
|
+
height: 8px;
|
|
143
|
+
border-radius: 50%;
|
|
144
|
+
background: #b45309;
|
|
145
|
+
animation: scarcity-pulse 2s ease-in-out infinite;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@keyframes scarcity-pulse {
|
|
149
|
+
0%, 100% { opacity: 1; }
|
|
150
|
+
50% { opacity: 0.4; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Options */
|
|
154
|
+
.runwell-bundle-builder__options {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
gap: 10px;
|
|
158
|
+
margin-bottom: 1.2rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.runwell-bundle-builder__option {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: flex-start;
|
|
164
|
+
gap: 12px;
|
|
165
|
+
padding: 14px 18px;
|
|
166
|
+
border: 2px solid #EADFD4;
|
|
167
|
+
border-radius: 10px;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
170
|
+
position: relative;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.runwell-bundle-builder__option:hover {
|
|
174
|
+
border-color: #3F5B4C;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.runwell-bundle-builder__option--selected {
|
|
178
|
+
border-color: #2A2622;
|
|
179
|
+
box-shadow: 0 0 0 1px #2A2622;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.runwell-bundle-builder__option--popular {
|
|
183
|
+
border-color: #3F5B4C;
|
|
184
|
+
position: relative;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.runwell-bundle-builder__popular-badge {
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: -10px;
|
|
190
|
+
right: 16px;
|
|
191
|
+
background: #3F5B4C;
|
|
192
|
+
color: #fff;
|
|
193
|
+
font-size: 1.0rem;
|
|
194
|
+
font-weight: 700;
|
|
195
|
+
padding: 2px 10px;
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
letter-spacing: 0.06em;
|
|
198
|
+
text-transform: uppercase;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.runwell-bundle-builder__option input {
|
|
202
|
+
position: absolute;
|
|
203
|
+
opacity: 0;
|
|
204
|
+
pointer-events: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.runwell-bundle-builder__option-radio {
|
|
208
|
+
width: 22px;
|
|
209
|
+
height: 22px;
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
border: 2px solid #EADFD4;
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
margin-top: 2px;
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
transition: border-color 0.2s;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.runwell-bundle-builder__option--selected .runwell-bundle-builder__option-radio {
|
|
221
|
+
border-color: #2A2622;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.runwell-bundle-builder__option--selected .runwell-bundle-builder__option-radio::after {
|
|
225
|
+
content: '';
|
|
226
|
+
width: 12px;
|
|
227
|
+
height: 12px;
|
|
228
|
+
border-radius: 50%;
|
|
229
|
+
background: #2A2622;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.runwell-bundle-builder__option-content {
|
|
233
|
+
flex: 1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.runwell-bundle-builder__option-header {
|
|
237
|
+
display: flex;
|
|
238
|
+
justify-content: space-between;
|
|
239
|
+
align-items: baseline;
|
|
240
|
+
gap: 12px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.runwell-bundle-builder__option-title {
|
|
244
|
+
font-weight: 700;
|
|
245
|
+
font-size: 1.5rem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.runwell-bundle-builder__option-pricing {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: baseline;
|
|
251
|
+
gap: 8px;
|
|
252
|
+
flex-shrink: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.runwell-bundle-builder__option-price {
|
|
256
|
+
font-weight: 700;
|
|
257
|
+
font-size: 1.8rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.runwell-bundle-builder__option-compare {
|
|
261
|
+
font-size: 1.3rem;
|
|
262
|
+
text-decoration: line-through;
|
|
263
|
+
opacity: 0.5;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.runwell-bundle-builder__option-badges {
|
|
267
|
+
display: flex;
|
|
268
|
+
gap: 6px;
|
|
269
|
+
margin-top: 6px;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.runwell-bundle-builder__badge {
|
|
274
|
+
font-size: 1.1rem;
|
|
275
|
+
font-weight: 700;
|
|
276
|
+
padding: 2px 8px;
|
|
277
|
+
border-radius: 4px;
|
|
278
|
+
text-transform: uppercase;
|
|
279
|
+
letter-spacing: 0.03em;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.runwell-bundle-builder__badge--save {
|
|
283
|
+
background: #3F5B4C;
|
|
284
|
+
color: #fff;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.runwell-bundle-builder__badge--shipping {
|
|
288
|
+
color: #3F5B4C;
|
|
289
|
+
font-weight: 600;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.runwell-bundle-builder__free-gift {
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 6px;
|
|
296
|
+
margin-top: 8px;
|
|
297
|
+
padding: 8px 12px;
|
|
298
|
+
background: rgba(63, 91, 76, 0.06);
|
|
299
|
+
border-radius: 6px;
|
|
300
|
+
font-size: 1.3rem;
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.runwell-bundle-builder__gift-icon {
|
|
305
|
+
font-size: 1.6rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* ATC Button */
|
|
309
|
+
.runwell-bundle-builder__atc {
|
|
310
|
+
width: 100%;
|
|
311
|
+
padding: 1.6rem;
|
|
312
|
+
font-size: 1.6rem;
|
|
313
|
+
font-weight: 700;
|
|
314
|
+
letter-spacing: 0.06em;
|
|
315
|
+
text-transform: uppercase;
|
|
316
|
+
background: #2A2622;
|
|
317
|
+
color: #fff;
|
|
318
|
+
border: none;
|
|
319
|
+
border-radius: 8px;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
transition: background 0.2s;
|
|
322
|
+
margin-bottom: 1.2rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.runwell-bundle-builder__atc:hover {
|
|
326
|
+
background: #3F5B4C;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Trust badges */
|
|
330
|
+
.runwell-bundle-builder__trust {
|
|
331
|
+
display: flex;
|
|
332
|
+
justify-content: center;
|
|
333
|
+
gap: 2rem;
|
|
334
|
+
font-size: 1.3rem;
|
|
335
|
+
opacity: 0.7;
|
|
336
|
+
flex-wrap: wrap;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Sticky ATC on mobile */
|
|
340
|
+
@media screen and (max-width: 989px) {
|
|
341
|
+
.runwell-bundle-builder__grid {
|
|
342
|
+
grid-template-columns: 1fr;
|
|
343
|
+
gap: 2rem;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.runwell-bundle-builder__atc-wrap {
|
|
347
|
+
position: sticky;
|
|
348
|
+
bottom: 0;
|
|
349
|
+
z-index: 10;
|
|
350
|
+
background: #fff;
|
|
351
|
+
padding: 12px 0 max(12px, env(safe-area-inset-bottom));
|
|
352
|
+
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
|
|
353
|
+
margin: 0 -1.5rem;
|
|
354
|
+
padding-left: 1.5rem;
|
|
355
|
+
padding-right: 1.5rem;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@media screen and (max-width: 749px) {
|
|
360
|
+
.runwell-bundle-builder__option-header {
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 4px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.runwell-bundle-builder__trust {
|
|
366
|
+
flex-direction: column;
|
|
367
|
+
align-items: center;
|
|
368
|
+
gap: 0.6rem;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Runwell additions: placeholder states when gallery is empty */
|
|
373
|
+
.runwell-bundle-builder__img--placeholder,
|
|
374
|
+
.runwell-bundle-builder__thumb-img--placeholder {
|
|
375
|
+
width: 100%;
|
|
376
|
+
height: 100%;
|
|
377
|
+
min-height: 320px;
|
|
378
|
+
background: linear-gradient(135deg, var(--runwell-oat, #F5F0EE), var(--runwell-cream, #EDE6D8));
|
|
379
|
+
display: block;
|
|
380
|
+
}
|
|
381
|
+
.runwell-bundle-builder__thumb-img--placeholder {
|
|
382
|
+
min-height: 0;
|
|
383
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bundle-builder",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "catalog",
|
|
5
|
+
"description": "Build-your-own-bundle PDP section. Slideshow gallery + thumbnail nav + radio bundle picker (1x / 2x / 3x with savings, free-ship and free-gift badges) + sticky ATC + FOMO countdown + scarcity. Migrated from Lusha (guabrasha-store/sections/bundle-selector.liquid). Replaces BYOB-style bundle apps.",
|
|
6
|
+
"files": {
|
|
7
|
+
"sections": ["sections/runwell-bundle-builder.liquid"],
|
|
8
|
+
"assets": ["assets/runwell-bundle-builder.css"]
|
|
9
|
+
},
|
|
10
|
+
"config": {
|
|
11
|
+
"schema": {
|
|
12
|
+
"heading": { "type": "string", "default": "Build your bundle" },
|
|
13
|
+
"sale_prefix": { "type": "string", "default": "Limited time offer" },
|
|
14
|
+
"show_rating": { "type": "boolean", "default": true },
|
|
15
|
+
"rating_score": { "type": "string", "default": "4.8/5" },
|
|
16
|
+
"rating_count": { "type": "string", "default": "2,400+" },
|
|
17
|
+
"show_trust_badges": { "type": "boolean", "default": true },
|
|
18
|
+
"trust_1": { "type": "string", "default": "90-Day Guarantee" },
|
|
19
|
+
"trust_2": { "type": "string", "default": "Easy Returns" },
|
|
20
|
+
"trust_3": { "type": "string", "default": "Free Shipping" },
|
|
21
|
+
"fomo_mode": { "type": "string", "default": "both", "enum": ["discount", "scarcity", "both", "none"] },
|
|
22
|
+
"fomo_cycle_days": { "type": "number", "default": 30 },
|
|
23
|
+
"fomo_stock_count": { "type": "number", "default": 47 }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -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>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runwell/shopify-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Reusable Shopify theme modules from Runwell. Replaces typically app-driven features (reviews, wishlist, urgency, FAQ, post-purchase upsell, exit popups, free-ship progress, sticky ATC, testimonials, badges, bundles) with native Liquid + JS + CSS that ship across multiple client themes via a config-driven sync CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|