@runwell/shopify-toolkit 0.1.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/README.md +87 -0
- package/bin/runwell-shopify +98 -0
- package/lib/add.js +66 -0
- package/lib/config-loader.js +47 -0
- package/lib/doctor.js +66 -0
- package/lib/list.js +31 -0
- package/lib/remove.js +37 -0
- package/lib/sync.js +107 -0
- package/lib/template.js +57 -0
- package/lib/validate.js +23 -0
- package/modules/comparison-table/README.md +17 -0
- package/modules/comparison-table/module.json +50 -0
- package/modules/comparison-table/sections/runwell-comparison-table.liquid +157 -0
- package/modules/delivery-estimate/README.md +17 -0
- package/modules/delivery-estimate/module.json +14 -0
- package/modules/delivery-estimate/snippets/runwell-delivery-estimate.liquid +39 -0
- package/modules/editorial-block/README.md +17 -0
- package/modules/editorial-block/module.json +40 -0
- package/modules/editorial-block/sections/runwell-editorial-block.liquid +155 -0
- package/modules/editorial-hero/README.md +17 -0
- package/modules/editorial-hero/module.json +61 -0
- package/modules/editorial-hero/sections/runwell-video-hero.liquid +151 -0
- package/modules/exit-intent/README.md +18 -0
- package/modules/exit-intent/assets/runwell-exit-intent.js +54 -0
- package/modules/exit-intent/module.json +33 -0
- package/modules/exit-intent/sections/runwell-exit-intent.liquid +48 -0
- package/modules/faq/README.md +17 -0
- package/modules/faq/module.json +35 -0
- package/modules/faq/sections/runwell-faq.liquid +66 -0
- package/modules/how-it-works/README.md +17 -0
- package/modules/how-it-works/module.json +57 -0
- package/modules/how-it-works/sections/runwell-how-it-works.liquid +99 -0
- package/modules/inventory-urgency/README.md +17 -0
- package/modules/inventory-urgency/module.json +14 -0
- package/modules/inventory-urgency/snippets/runwell-inventory-urgency.liquid +19 -0
- package/modules/pdp-ingredients/README.md +17 -0
- package/modules/pdp-ingredients/module.json +40 -0
- package/modules/pdp-ingredients/sections/runwell-ingredients.liquid +139 -0
- package/modules/pdp-journal-link/README.md +17 -0
- package/modules/pdp-journal-link/module.json +53 -0
- package/modules/pdp-journal-link/sections/runwell-pdp-journal.liquid +124 -0
- package/modules/pdp-trust-checks/README.md +17 -0
- package/modules/pdp-trust-checks/module.json +49 -0
- package/modules/pdp-trust-checks/sections/runwell-pdp-trust.liquid +141 -0
- package/modules/post-purchase-upsell/README.md +52 -0
- package/modules/post-purchase-upsell/admin/discount-setup.md +25 -0
- package/modules/post-purchase-upsell/admin/order-status-paste.html +31 -0
- package/modules/post-purchase-upsell/assets/runwell-thank-you.css +119 -0
- package/modules/post-purchase-upsell/assets/runwell-thank-you.js +162 -0
- package/modules/post-purchase-upsell/module.json +44 -0
- package/modules/press-bar/README.md +17 -0
- package/modules/press-bar/module.json +30 -0
- package/modules/press-bar/sections/runwell-press-bar.liquid +119 -0
- package/modules/recently-viewed/README.md +18 -0
- package/modules/recently-viewed/assets/runwell-recently-viewed.js +57 -0
- package/modules/recently-viewed/module.json +33 -0
- package/modules/recently-viewed/sections/runwell-recently-viewed.liquid +38 -0
- package/modules/reviews/README.md +17 -0
- package/modules/reviews/module.json +20 -0
- package/modules/reviews/sections/runwell-pdp-reviews.liquid +93 -0
- package/modules/risk-reversal/README.md +17 -0
- package/modules/risk-reversal/module.json +49 -0
- package/modules/risk-reversal/sections/runwell-risk-reversal.liquid +94 -0
- package/modules/shipping-bar/README.md +17 -0
- package/modules/shipping-bar/module.json +45 -0
- package/modules/shipping-bar/sections/runwell-shipping-bar.liquid +95 -0
- package/modules/sticky-atc/README.md +17 -0
- package/modules/sticky-atc/module.json +14 -0
- package/modules/sticky-atc/sections/runwell-pdp-sticky.liquid +78 -0
- package/modules/testimonials/README.md +17 -0
- package/modules/testimonials/module.json +35 -0
- package/modules/testimonials/sections/runwell-testimonials.liquid +87 -0
- package/modules/trust-badges/README.md +17 -0
- package/modules/trust-badges/module.json +25 -0
- package/modules/trust-badges/sections/runwell-trust-badges.liquid +93 -0
- package/package.json +45 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Lushi v2 video hero. Full-bleed background video with editorial copy
|
|
3
|
+
bottom-left. Mirrors the v1 lushi.co Hero pattern but pivots messaging
|
|
4
|
+
to general health and wellness.
|
|
5
|
+
{%- endcomment -%}
|
|
6
|
+
|
|
7
|
+
<section class="runwell-video-hero" style="min-height: {{ section.settings.min_height | default: 100 }}vh;">
|
|
8
|
+
{%- if section.settings.video_url != blank -%}
|
|
9
|
+
<video
|
|
10
|
+
class="runwell-video-hero__video"
|
|
11
|
+
autoplay
|
|
12
|
+
loop
|
|
13
|
+
muted
|
|
14
|
+
playsinline
|
|
15
|
+
preload="auto"
|
|
16
|
+
{%- if section.settings.poster_image != blank -%}
|
|
17
|
+
poster="{{ section.settings.poster_image | image_url: width: 1920 }}"
|
|
18
|
+
{%- endif -%}
|
|
19
|
+
>
|
|
20
|
+
<source src="{{ section.settings.video_url }}" type="video/mp4">
|
|
21
|
+
</video>
|
|
22
|
+
{%- elsif section.settings.poster_image != blank -%}
|
|
23
|
+
<img
|
|
24
|
+
class="runwell-video-hero__video"
|
|
25
|
+
src="{{ section.settings.poster_image | image_url: width: 1920 }}"
|
|
26
|
+
alt="{{ section.settings.heading | escape }}"
|
|
27
|
+
width="1920"
|
|
28
|
+
height="1080"
|
|
29
|
+
loading="eager"
|
|
30
|
+
>
|
|
31
|
+
{%- else -%}
|
|
32
|
+
{%- comment -%} Fallback to bundled v1 imagery so the hero is never blank pre-launch {%- endcomment -%}
|
|
33
|
+
<img
|
|
34
|
+
class="runwell-video-hero__video"
|
|
35
|
+
src="{{ 'runwell-hero-bg.jpg' | asset_url }}"
|
|
36
|
+
alt="{{ section.settings.heading | escape }}"
|
|
37
|
+
width="1920"
|
|
38
|
+
height="1080"
|
|
39
|
+
loading="eager"
|
|
40
|
+
>
|
|
41
|
+
{%- endif -%}
|
|
42
|
+
|
|
43
|
+
<div class="runwell-video-hero__overlay"></div>
|
|
44
|
+
|
|
45
|
+
<div class="runwell-video-hero__content">
|
|
46
|
+
{%- if section.settings.eyebrow != blank -%}
|
|
47
|
+
<div style="font-size: 0.78rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.85; margin-bottom: 1rem;">
|
|
48
|
+
{{ section.settings.eyebrow }}
|
|
49
|
+
</div>
|
|
50
|
+
{%- endif -%}
|
|
51
|
+
|
|
52
|
+
{%- if section.settings.heading != blank -%}
|
|
53
|
+
<h1 class="runwell-video-hero__title">{{ section.settings.heading }}</h1>
|
|
54
|
+
{%- endif -%}
|
|
55
|
+
|
|
56
|
+
{%- if section.settings.subheading != blank -%}
|
|
57
|
+
<p class="runwell-video-hero__subtitle">{{ section.settings.subheading }}</p>
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
|
|
60
|
+
{%- if section.settings.button_label_primary != blank or section.settings.button_label_secondary != blank -%}
|
|
61
|
+
<div class="runwell-video-hero__cta-row">
|
|
62
|
+
{%- if section.settings.button_label_primary != blank -%}
|
|
63
|
+
<a href="{{ section.settings.button_link_primary | default: '#' }}" class="runwell-video-hero__cta runwell-video-hero__cta--primary">
|
|
64
|
+
{{ section.settings.button_label_primary }}
|
|
65
|
+
</a>
|
|
66
|
+
{%- endif -%}
|
|
67
|
+
{%- if section.settings.button_label_secondary != blank -%}
|
|
68
|
+
<a href="{{ section.settings.button_link_secondary | default: '#' }}" class="runwell-video-hero__cta runwell-video-hero__cta--secondary">
|
|
69
|
+
{{ section.settings.button_label_secondary }}
|
|
70
|
+
</a>
|
|
71
|
+
{%- endif -%}
|
|
72
|
+
</div>
|
|
73
|
+
{%- endif -%}
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
{% schema %}
|
|
78
|
+
{
|
|
79
|
+
"name": "Lushi video hero",
|
|
80
|
+
"tag": "section",
|
|
81
|
+
"class": "section-runwell-video-hero",
|
|
82
|
+
"settings": [
|
|
83
|
+
{
|
|
84
|
+
"type": "text",
|
|
85
|
+
"id": "eyebrow",
|
|
86
|
+
"label": "Eyebrow text",
|
|
87
|
+
"default": "Wellness, curated"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "text",
|
|
91
|
+
"id": "heading",
|
|
92
|
+
"label": "Headline",
|
|
93
|
+
"default": "Glow from within."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "textarea",
|
|
97
|
+
"id": "subheading",
|
|
98
|
+
"label": "Subheading",
|
|
99
|
+
"default": "Lushi is a modern wellness brand offering curated supplements, skincare, and rituals for everyday health."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"type": "url",
|
|
103
|
+
"id": "video_url",
|
|
104
|
+
"label": "Background video URL (mp4)",
|
|
105
|
+
"info": "Use a Vimeo or CDN-hosted mp4. Falls back to poster image if blank."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "image_picker",
|
|
109
|
+
"id": "poster_image",
|
|
110
|
+
"label": "Poster image (and fallback)"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"type": "range",
|
|
114
|
+
"id": "min_height",
|
|
115
|
+
"label": "Hero min-height (vh)",
|
|
116
|
+
"min": 60,
|
|
117
|
+
"max": 100,
|
|
118
|
+
"step": 5,
|
|
119
|
+
"default": 100,
|
|
120
|
+
"unit": "vh"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "text",
|
|
124
|
+
"id": "button_label_primary",
|
|
125
|
+
"label": "Primary button label",
|
|
126
|
+
"default": "Shop wellness"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "url",
|
|
130
|
+
"id": "button_link_primary",
|
|
131
|
+
"label": "Primary button link"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"type": "text",
|
|
135
|
+
"id": "button_label_secondary",
|
|
136
|
+
"label": "Secondary button label",
|
|
137
|
+
"default": "Read the journal"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "url",
|
|
141
|
+
"id": "button_link_secondary",
|
|
142
|
+
"label": "Secondary button link"
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"presets": [
|
|
146
|
+
{
|
|
147
|
+
"name": "Lushi video hero"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
{% endschema %}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# exit-intent
|
|
2
|
+
|
|
3
|
+
Lushi exit popup. Migrated from Lushi (`sections/lushi-exit-intent.liquid`) into the Runwell Shopify Toolkit.
|
|
4
|
+
|
|
5
|
+
Category: `conversion`
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- `sections/runwell-exit-intent.liquid`
|
|
10
|
+
- `assets/runwell-exit-intent.js`
|
|
11
|
+
|
|
12
|
+
## Config
|
|
13
|
+
|
|
14
|
+
See `module.json` config schema. Defaults match the original Lushi defaults; override per client in `runwell.config.json`.
|
|
15
|
+
|
|
16
|
+
## Lineage
|
|
17
|
+
|
|
18
|
+
This module was extracted from the Lushi build (Capital V) and generalised. The original lives at `lushi-shopify/sections/lushi-exit-intent.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* Lushi exit-intent popup. Triggers on mouseleave to top of viewport
|
|
2
|
+
on desktop, after 30s of activity on mobile. Suppressed for 30 days
|
|
3
|
+
after dismiss or sign-up. */
|
|
4
|
+
(function () {
|
|
5
|
+
if (typeof window === 'undefined') return;
|
|
6
|
+
var KEY = 'lushi_exit_seen';
|
|
7
|
+
var DAYS = 30;
|
|
8
|
+
var modal = document.querySelector('[data-runwell-exit-intent]');
|
|
9
|
+
if (!modal) return;
|
|
10
|
+
|
|
11
|
+
function seenRecently() {
|
|
12
|
+
try {
|
|
13
|
+
var v = localStorage.getItem(KEY);
|
|
14
|
+
if (!v) return false;
|
|
15
|
+
var ts = parseInt(v, 10);
|
|
16
|
+
if (isNaN(ts)) return false;
|
|
17
|
+
return (Date.now() - ts) < (DAYS * 24 * 60 * 60 * 1000);
|
|
18
|
+
} catch (e) { return false; }
|
|
19
|
+
}
|
|
20
|
+
function markSeen() {
|
|
21
|
+
try { localStorage.setItem(KEY, String(Date.now())); } catch (e) {}
|
|
22
|
+
}
|
|
23
|
+
function open() {
|
|
24
|
+
if (seenRecently()) return;
|
|
25
|
+
modal.setAttribute('aria-hidden', 'false');
|
|
26
|
+
modal.classList.add('is-open');
|
|
27
|
+
document.body.classList.add('runwell-exit-open');
|
|
28
|
+
markSeen();
|
|
29
|
+
}
|
|
30
|
+
function close() {
|
|
31
|
+
modal.setAttribute('aria-hidden', 'true');
|
|
32
|
+
modal.classList.remove('is-open');
|
|
33
|
+
document.body.classList.remove('runwell-exit-open');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (seenRecently()) return;
|
|
37
|
+
|
|
38
|
+
// Desktop: mouseleave top
|
|
39
|
+
document.addEventListener('mouseout', function (e) {
|
|
40
|
+
if (e.relatedTarget === null && e.clientY <= 0) open();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Mobile fallback: 30s timer
|
|
44
|
+
if (window.matchMedia('(max-width: 749px)').matches) {
|
|
45
|
+
setTimeout(open, 30000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
modal.querySelectorAll('[data-runwell-exit-close]').forEach(function (el) {
|
|
49
|
+
el.addEventListener('click', close);
|
|
50
|
+
});
|
|
51
|
+
document.addEventListener('keydown', function (e) {
|
|
52
|
+
if (e.key === 'Escape' && modal.classList.contains('is-open')) close();
|
|
53
|
+
});
|
|
54
|
+
})();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "exit-intent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "conversion",
|
|
5
|
+
"description": "Lushi exit popup module migrated from Lushi.",
|
|
6
|
+
"files": {
|
|
7
|
+
"sections": [
|
|
8
|
+
"sections/runwell-exit-intent.liquid"
|
|
9
|
+
],
|
|
10
|
+
"assets": [
|
|
11
|
+
"assets/runwell-exit-intent.js"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"config": {
|
|
15
|
+
"schema": {
|
|
16
|
+
"eyebrow": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"default": "One last thing",
|
|
19
|
+
"label": "Eyebrow"
|
|
20
|
+
},
|
|
21
|
+
"heading": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"default": "Read before you buy.",
|
|
24
|
+
"label": "Heading"
|
|
25
|
+
},
|
|
26
|
+
"lede": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"default": "Join the journal and get the founder's take on what we picked, why we picked it, and which products to skip altogether.",
|
|
29
|
+
"label": "Lede"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Lushi exit-intent newsletter popup. Native replacement for Privy /
|
|
3
|
+
Justuno / Uno popups. Triggers on mouseleave at top of viewport
|
|
4
|
+
(desktop) or after 30 seconds (mobile fallback). Suppresses with a
|
|
5
|
+
localStorage cookie for 30 days after dismiss or sign-up.
|
|
6
|
+
{%- endcomment -%}
|
|
7
|
+
|
|
8
|
+
<div class="runwell-exit" data-runwell-exit-intent aria-hidden="true" role="dialog" aria-labelledby="runwell-exit-title">
|
|
9
|
+
<div class="runwell-exit__backdrop" data-runwell-exit-close></div>
|
|
10
|
+
<div class="runwell-exit__panel">
|
|
11
|
+
<button class="runwell-exit__close" type="button" data-runwell-exit-close aria-label="Close">×</button>
|
|
12
|
+
{%- if section.settings.eyebrow != blank -%}
|
|
13
|
+
<p class="runwell-exit__eyebrow">{{ section.settings.eyebrow }}</p>
|
|
14
|
+
{%- endif -%}
|
|
15
|
+
<h2 id="runwell-exit-title" class="runwell-exit__heading">{{ section.settings.heading }}</h2>
|
|
16
|
+
{%- if section.settings.lede != blank -%}
|
|
17
|
+
<p class="runwell-exit__lede">{{ section.settings.lede }}</p>
|
|
18
|
+
{%- endif -%}
|
|
19
|
+
{%- form 'customer', class: 'runwell-exit__form' -%}
|
|
20
|
+
<input type="hidden" name="contact[tags]" value="newsletter, exit-intent">
|
|
21
|
+
<label class="visually-hidden" for="runwell-exit-email">Email</label>
|
|
22
|
+
<input id="runwell-exit-email" type="email" name="contact[email]" placeholder="you@email.com" required>
|
|
23
|
+
<button type="submit" class="runwell-exit__cta">Join the list</button>
|
|
24
|
+
{%- if form.posted_successfully? -%}
|
|
25
|
+
<p class="runwell-exit__success">Thanks. Check your inbox for our welcome edit.</p>
|
|
26
|
+
{%- endif -%}
|
|
27
|
+
{%- endform -%}
|
|
28
|
+
<p class="runwell-exit__fineprint">No spam. Unsubscribe anytime.</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<script src="{{ 'runwell-exit-intent.js' | asset_url }}" defer="defer"></script>
|
|
33
|
+
|
|
34
|
+
{% schema %}
|
|
35
|
+
{
|
|
36
|
+
"name": "Lushi exit popup",
|
|
37
|
+
"tag": "section",
|
|
38
|
+
"class": "section-runwell-exit",
|
|
39
|
+
"settings": [
|
|
40
|
+
{ "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "One last thing" },
|
|
41
|
+
{ "type": "text", "id": "heading", "label": "Heading", "default": "Read before you buy." },
|
|
42
|
+
{ "type": "textarea", "id": "lede", "label": "Lede", "default": "Join the journal and get the founder's take on what we picked, why we picked it, and which products to skip altogether." }
|
|
43
|
+
],
|
|
44
|
+
"presets": [
|
|
45
|
+
{ "name": "Lushi exit popup" }
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
{% endschema %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# faq
|
|
2
|
+
|
|
3
|
+
Lushi FAQ. Migrated from Lushi (`sections/lushi-faq.liquid`) into the Runwell Shopify Toolkit.
|
|
4
|
+
|
|
5
|
+
Category: `storefront`
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- `sections/runwell-faq.liquid`
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
See `module.json` config schema. Defaults match the original Lushi defaults; override per client in `runwell.config.json`.
|
|
14
|
+
|
|
15
|
+
## Lineage
|
|
16
|
+
|
|
17
|
+
This module was extracted from the Lushi build (Capital V) and generalised. The original lives at `lushi-shopify/sections/lushi-faq.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "faq",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "storefront",
|
|
5
|
+
"description": "Lushi FAQ module migrated from Lushi.",
|
|
6
|
+
"files": {
|
|
7
|
+
"sections": [
|
|
8
|
+
"sections/runwell-faq.liquid"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"config": {
|
|
12
|
+
"schema": {
|
|
13
|
+
"eyebrow": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "Questions",
|
|
16
|
+
"label": "Eyebrow"
|
|
17
|
+
},
|
|
18
|
+
"heading": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "Things people ask.",
|
|
21
|
+
"label": "Heading"
|
|
22
|
+
},
|
|
23
|
+
"background_color": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"default": "#FFFFFF",
|
|
26
|
+
"label": "Background"
|
|
27
|
+
},
|
|
28
|
+
"text_color": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"default": "#0B3D38",
|
|
31
|
+
"label": "Text"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Lushi FAQ accordion. Native replacement for FAQ widgets in Vitals,
|
|
3
|
+
HelpCenter, etc. Uses native HTML <details> elements so it works
|
|
4
|
+
without any JS.
|
|
5
|
+
{%- endcomment -%}
|
|
6
|
+
|
|
7
|
+
<section
|
|
8
|
+
class="runwell-faq"
|
|
9
|
+
style="background: {{ section.settings.background_color | default: '#FFFFFF' }}; color: {{ section.settings.text_color | default: '#0B3D38' }};">
|
|
10
|
+
<div class="runwell-faq__inner">
|
|
11
|
+
{%- if section.settings.eyebrow != blank -%}
|
|
12
|
+
<p class="runwell-faq__eyebrow">{{ section.settings.eyebrow }}</p>
|
|
13
|
+
{%- endif -%}
|
|
14
|
+
{%- if section.settings.heading != blank -%}
|
|
15
|
+
<h2 class="runwell-faq__heading">{{ section.settings.heading }}</h2>
|
|
16
|
+
{%- endif -%}
|
|
17
|
+
<div class="runwell-faq__list">
|
|
18
|
+
{%- for block in section.blocks -%}
|
|
19
|
+
<details class="runwell-faq__item" {{ block.shopify_attributes }}>
|
|
20
|
+
<summary class="runwell-faq__q">
|
|
21
|
+
<span>{{ block.settings.question }}</span>
|
|
22
|
+
<span class="runwell-faq__icon" aria-hidden="true">+</span>
|
|
23
|
+
</summary>
|
|
24
|
+
<div class="runwell-faq__a">{{ block.settings.answer }}</div>
|
|
25
|
+
</details>
|
|
26
|
+
{%- endfor -%}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</section>
|
|
30
|
+
|
|
31
|
+
{% schema %}
|
|
32
|
+
{
|
|
33
|
+
"name": "Lushi FAQ",
|
|
34
|
+
"tag": "section",
|
|
35
|
+
"class": "section-runwell-faq",
|
|
36
|
+
"settings": [
|
|
37
|
+
{ "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "Questions" },
|
|
38
|
+
{ "type": "text", "id": "heading", "label": "Heading", "default": "Things people ask." },
|
|
39
|
+
{ "type": "color", "id": "background_color", "label": "Background", "default": "#FFFFFF" },
|
|
40
|
+
{ "type": "color", "id": "text_color", "label": "Text", "default": "#0B3D38" }
|
|
41
|
+
],
|
|
42
|
+
"blocks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "qa",
|
|
45
|
+
"name": "Question",
|
|
46
|
+
"settings": [
|
|
47
|
+
{ "type": "text", "id": "question", "label": "Question" },
|
|
48
|
+
{ "type": "richtext", "id": "answer", "label": "Answer" }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"max_blocks": 12,
|
|
53
|
+
"presets": [
|
|
54
|
+
{
|
|
55
|
+
"name": "Lushi FAQ",
|
|
56
|
+
"blocks": [
|
|
57
|
+
{ "type": "qa", "settings": { "question": "Are these supplements third-party tested?", "answer": "<p>Every product we list ships with a published Certificate of Analysis from the supplier. We confirm the lot numbers match what the bottle claims before any product gets a page on Lushi.</p>" } },
|
|
58
|
+
{ "type": "qa", "settings": { "question": "How fast do orders ship?", "answer": "<p>Most orders leave the warehouse within one to two business days. Free standard shipping on US orders over $75; flat $7 below. International rates calculated at checkout.</p>" } },
|
|
59
|
+
{ "type": "qa", "settings": { "question": "What is the return policy?", "answer": "<p>If a product does not earn a place on your counter, write to us within 30 days for a full refund. We do not make returns hard.</p>" } },
|
|
60
|
+
{ "type": "qa", "settings": { "question": "Do these products treat or cure conditions?", "answer": "<p>No. Statements about supplements and skincare on Lushi have not been evaluated by the FDA and are not intended to diagnose, treat, cure, or prevent any disease. Consult your physician before starting any new regimen.</p>" } },
|
|
61
|
+
{ "type": "qa", "settings": { "question": "How does the journal work?", "answer": "<p>Every product is paired with a journal post explaining how it works, who it is for, and what the research actually says. Editorial, not advertorial.</p>" } }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
{% endschema %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# how-it-works
|
|
2
|
+
|
|
3
|
+
Lushi how it works. Migrated from Lushi (`sections/lushi-how-it-works.liquid`) into the Runwell Shopify Toolkit.
|
|
4
|
+
|
|
5
|
+
Category: `storefront`
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- `sections/runwell-how-it-works.liquid`
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
See `module.json` config schema. Defaults match the original Lushi defaults; override per client in `runwell.config.json`.
|
|
14
|
+
|
|
15
|
+
## Lineage
|
|
16
|
+
|
|
17
|
+
This module was extracted from the Lushi build (Capital V) and generalised. The original lives at `lushi-shopify/sections/lushi-how-it-works.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "how-it-works",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "storefront",
|
|
5
|
+
"description": "Lushi how it works module migrated from Lushi.",
|
|
6
|
+
"files": {
|
|
7
|
+
"sections": [
|
|
8
|
+
"sections/runwell-how-it-works.liquid"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"config": {
|
|
12
|
+
"schema": {
|
|
13
|
+
"eyebrow": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "How Lushi works",
|
|
16
|
+
"label": "Eyebrow"
|
|
17
|
+
},
|
|
18
|
+
"heading": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "Curated, written, shipped.",
|
|
21
|
+
"label": "Heading"
|
|
22
|
+
},
|
|
23
|
+
"lede": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"default": "Three simple steps from the brands we trust to your counter. No filler in between.",
|
|
26
|
+
"label": "Lede"
|
|
27
|
+
},
|
|
28
|
+
"image": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"label": "Side image (uploaded)"
|
|
31
|
+
},
|
|
32
|
+
"asset_filename": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"label": "Side image (bundled asset)"
|
|
35
|
+
},
|
|
36
|
+
"background_color": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"default": "#EDE6D8",
|
|
39
|
+
"label": "Background"
|
|
40
|
+
},
|
|
41
|
+
"text_color": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"default": "#0B3D38",
|
|
44
|
+
"label": "Text"
|
|
45
|
+
},
|
|
46
|
+
"cta_label": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"default": "Read our standards",
|
|
49
|
+
"label": "CTA label"
|
|
50
|
+
},
|
|
51
|
+
"cta_link": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"label": "CTA link"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Lushi "How it works" section. Side-image left + content right (or
|
|
3
|
+
reversible). Eyebrow + headline + lede + 3 numbered service cards.
|
|
4
|
+
Mirrors v1 .conciergeSection / "Nationwide Access to Concierge Care"
|
|
5
|
+
but pivoted to wellness curation flow.
|
|
6
|
+
{%- endcomment -%}
|
|
7
|
+
|
|
8
|
+
<section
|
|
9
|
+
class="runwell-how"
|
|
10
|
+
style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }};"
|
|
11
|
+
>
|
|
12
|
+
<div class="runwell-how__inner">
|
|
13
|
+
<div class="runwell-how__media">
|
|
14
|
+
{%- if section.settings.image != blank -%}
|
|
15
|
+
<img src="{{ section.settings.image | image_url: width: 1200 }}"
|
|
16
|
+
alt="{{ section.settings.heading | escape }}"
|
|
17
|
+
width="1200" height="1500" loading="lazy">
|
|
18
|
+
{%- elsif section.settings.asset_filename != blank -%}
|
|
19
|
+
<img src="{{ section.settings.asset_filename | asset_url }}"
|
|
20
|
+
alt="{{ section.settings.heading | escape }}"
|
|
21
|
+
width="1200" height="1500" loading="lazy">
|
|
22
|
+
{%- endif -%}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="runwell-how__content">
|
|
26
|
+
{%- if section.settings.eyebrow != blank -%}
|
|
27
|
+
<div class="runwell-how__eyebrow">{{ section.settings.eyebrow }}</div>
|
|
28
|
+
{%- endif -%}
|
|
29
|
+
{%- if section.settings.heading != blank -%}
|
|
30
|
+
<h2 class="runwell-how__heading">{{ section.settings.heading }}</h2>
|
|
31
|
+
{%- endif -%}
|
|
32
|
+
{%- if section.settings.lede != blank -%}
|
|
33
|
+
<p class="runwell-how__lede">{{ section.settings.lede }}</p>
|
|
34
|
+
{%- endif -%}
|
|
35
|
+
|
|
36
|
+
<div class="runwell-how__cards">
|
|
37
|
+
{%- for block in section.blocks -%}
|
|
38
|
+
<div {{ block.shopify_attributes }} class="runwell-how__card">
|
|
39
|
+
<div class="runwell-how__card-num">{{ forloop.index | prepend: '0' | slice: -2, 2 }}</div>
|
|
40
|
+
<div class="runwell-how__card-body">
|
|
41
|
+
{%- if block.settings.title != blank -%}
|
|
42
|
+
<h3 class="runwell-how__card-title">{{ block.settings.title }}</h3>
|
|
43
|
+
{%- endif -%}
|
|
44
|
+
{%- if block.settings.body != blank -%}
|
|
45
|
+
<p class="runwell-how__card-text">{{ block.settings.body }}</p>
|
|
46
|
+
{%- endif -%}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
{%- endfor -%}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{%- if section.settings.cta_label != blank -%}
|
|
53
|
+
<a href="{{ section.settings.cta_link | default: '#' }}" class="runwell-how__cta">
|
|
54
|
+
{{ section.settings.cta_label }} →
|
|
55
|
+
</a>
|
|
56
|
+
{%- endif -%}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
{% schema %}
|
|
62
|
+
{
|
|
63
|
+
"name": "Lushi how it works",
|
|
64
|
+
"tag": "section",
|
|
65
|
+
"class": "section-runwell-how",
|
|
66
|
+
"settings": [
|
|
67
|
+
{ "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "How Lushi works" },
|
|
68
|
+
{ "type": "text", "id": "heading", "label": "Heading", "default": "Curated, written, shipped." },
|
|
69
|
+
{ "type": "textarea", "id": "lede", "label": "Lede", "default": "Three simple steps from the brands we trust to your counter. No filler in between." },
|
|
70
|
+
{ "type": "image_picker", "id": "image", "label": "Side image (uploaded)" },
|
|
71
|
+
{ "type": "text", "id": "asset_filename", "label": "Side image (bundled asset)", "info": "e.g. runwell-about-2.jpg" },
|
|
72
|
+
{ "type": "color", "id": "background_color", "label": "Background", "default": "#EDE6D8" },
|
|
73
|
+
{ "type": "color", "id": "text_color", "label": "Text", "default": "#0B3D38" },
|
|
74
|
+
{ "type": "text", "id": "cta_label", "label": "CTA label", "default": "Read our standards" },
|
|
75
|
+
{ "type": "url", "id": "cta_link", "label": "CTA link" }
|
|
76
|
+
],
|
|
77
|
+
"blocks": [
|
|
78
|
+
{
|
|
79
|
+
"type": "step",
|
|
80
|
+
"name": "Step",
|
|
81
|
+
"settings": [
|
|
82
|
+
{ "type": "text", "id": "title", "label": "Title" },
|
|
83
|
+
{ "type": "textarea", "id": "body", "label": "Body" }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"max_blocks": 4,
|
|
88
|
+
"presets": [
|
|
89
|
+
{
|
|
90
|
+
"name": "Lushi how it works",
|
|
91
|
+
"blocks": [
|
|
92
|
+
{ "type": "step", "settings": { "title": "We curate.", "body": "Every product on Lushi is hand-picked from small wellness brands and clears our four-check standards before it gets a page." } },
|
|
93
|
+
{ "type": "step", "settings": { "title": "We write.", "body": "Each product pairs with a journal post: how it works, who it's for, and what the research actually says. Editorial, not advertorial." } },
|
|
94
|
+
{ "type": "step", "settings": { "title": "We ship.", "body": "Suppliers ship direct from their facility. Free shipping over $75; 30-day no-hassle returns." } }
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
{% endschema %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# inventory-urgency
|
|
2
|
+
|
|
3
|
+
inventory-urgency. Migrated from Lushi (`snippets/lushi-inventory-urgency.liquid`) into the Runwell Shopify Toolkit.
|
|
4
|
+
|
|
5
|
+
Category: `pdp`
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
- `snippets/runwell-inventory-urgency.liquid`
|
|
10
|
+
|
|
11
|
+
## Config
|
|
12
|
+
|
|
13
|
+
See `module.json` config schema. Defaults match the original Lushi defaults; override per client in `runwell.config.json`.
|
|
14
|
+
|
|
15
|
+
## Lineage
|
|
16
|
+
|
|
17
|
+
This module was extracted from the Lushi build (Capital V) and generalised. The original lives at `lushi-shopify/snippets/lushi-inventory-urgency.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "inventory-urgency",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"category": "pdp",
|
|
5
|
+
"description": "inventory-urgency module migrated from Lushi.",
|
|
6
|
+
"files": {
|
|
7
|
+
"snippets": [
|
|
8
|
+
"snippets/runwell-inventory-urgency.liquid"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"config": {
|
|
12
|
+
"schema": {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Lushi inventory urgency. Shows "Only N left" near ATC when stock is
|
|
3
|
+
low. Replaces the urgency widget from Vitals / similar apps.
|
|
4
|
+
Threshold and display are conservative: only fires when inventory is
|
|
5
|
+
tracked AND visible on the PDP product object.
|
|
6
|
+
{%- endcomment -%}
|
|
7
|
+
|
|
8
|
+
{%- assign threshold = 10 -%}
|
|
9
|
+
{%- assign variant = product.selected_or_first_available_variant -%}
|
|
10
|
+
{%- if variant.inventory_management == 'shopify' and variant.inventory_policy == 'deny' and variant.inventory_quantity > 0 and variant.inventory_quantity <= threshold -%}
|
|
11
|
+
<div class="runwell-urgency" role="status" aria-live="polite">
|
|
12
|
+
<span class="runwell-urgency__dot" aria-hidden="true"></span>
|
|
13
|
+
<span class="runwell-urgency__msg">
|
|
14
|
+
Only <strong>{{ variant.inventory_quantity }}</strong>
|
|
15
|
+
{%- if variant.inventory_quantity == 1 -%} unit left{%- else -%} units left{%- endif -%}
|
|
16
|
+
in stock.
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
{%- endif -%}
|