@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.
Files changed (76) hide show
  1. package/README.md +87 -0
  2. package/bin/runwell-shopify +98 -0
  3. package/lib/add.js +66 -0
  4. package/lib/config-loader.js +47 -0
  5. package/lib/doctor.js +66 -0
  6. package/lib/list.js +31 -0
  7. package/lib/remove.js +37 -0
  8. package/lib/sync.js +107 -0
  9. package/lib/template.js +57 -0
  10. package/lib/validate.js +23 -0
  11. package/modules/comparison-table/README.md +17 -0
  12. package/modules/comparison-table/module.json +50 -0
  13. package/modules/comparison-table/sections/runwell-comparison-table.liquid +157 -0
  14. package/modules/delivery-estimate/README.md +17 -0
  15. package/modules/delivery-estimate/module.json +14 -0
  16. package/modules/delivery-estimate/snippets/runwell-delivery-estimate.liquid +39 -0
  17. package/modules/editorial-block/README.md +17 -0
  18. package/modules/editorial-block/module.json +40 -0
  19. package/modules/editorial-block/sections/runwell-editorial-block.liquid +155 -0
  20. package/modules/editorial-hero/README.md +17 -0
  21. package/modules/editorial-hero/module.json +61 -0
  22. package/modules/editorial-hero/sections/runwell-video-hero.liquid +151 -0
  23. package/modules/exit-intent/README.md +18 -0
  24. package/modules/exit-intent/assets/runwell-exit-intent.js +54 -0
  25. package/modules/exit-intent/module.json +33 -0
  26. package/modules/exit-intent/sections/runwell-exit-intent.liquid +48 -0
  27. package/modules/faq/README.md +17 -0
  28. package/modules/faq/module.json +35 -0
  29. package/modules/faq/sections/runwell-faq.liquid +66 -0
  30. package/modules/how-it-works/README.md +17 -0
  31. package/modules/how-it-works/module.json +57 -0
  32. package/modules/how-it-works/sections/runwell-how-it-works.liquid +99 -0
  33. package/modules/inventory-urgency/README.md +17 -0
  34. package/modules/inventory-urgency/module.json +14 -0
  35. package/modules/inventory-urgency/snippets/runwell-inventory-urgency.liquid +19 -0
  36. package/modules/pdp-ingredients/README.md +17 -0
  37. package/modules/pdp-ingredients/module.json +40 -0
  38. package/modules/pdp-ingredients/sections/runwell-ingredients.liquid +139 -0
  39. package/modules/pdp-journal-link/README.md +17 -0
  40. package/modules/pdp-journal-link/module.json +53 -0
  41. package/modules/pdp-journal-link/sections/runwell-pdp-journal.liquid +124 -0
  42. package/modules/pdp-trust-checks/README.md +17 -0
  43. package/modules/pdp-trust-checks/module.json +49 -0
  44. package/modules/pdp-trust-checks/sections/runwell-pdp-trust.liquid +141 -0
  45. package/modules/post-purchase-upsell/README.md +52 -0
  46. package/modules/post-purchase-upsell/admin/discount-setup.md +25 -0
  47. package/modules/post-purchase-upsell/admin/order-status-paste.html +31 -0
  48. package/modules/post-purchase-upsell/assets/runwell-thank-you.css +119 -0
  49. package/modules/post-purchase-upsell/assets/runwell-thank-you.js +162 -0
  50. package/modules/post-purchase-upsell/module.json +44 -0
  51. package/modules/press-bar/README.md +17 -0
  52. package/modules/press-bar/module.json +30 -0
  53. package/modules/press-bar/sections/runwell-press-bar.liquid +119 -0
  54. package/modules/recently-viewed/README.md +18 -0
  55. package/modules/recently-viewed/assets/runwell-recently-viewed.js +57 -0
  56. package/modules/recently-viewed/module.json +33 -0
  57. package/modules/recently-viewed/sections/runwell-recently-viewed.liquid +38 -0
  58. package/modules/reviews/README.md +17 -0
  59. package/modules/reviews/module.json +20 -0
  60. package/modules/reviews/sections/runwell-pdp-reviews.liquid +93 -0
  61. package/modules/risk-reversal/README.md +17 -0
  62. package/modules/risk-reversal/module.json +49 -0
  63. package/modules/risk-reversal/sections/runwell-risk-reversal.liquid +94 -0
  64. package/modules/shipping-bar/README.md +17 -0
  65. package/modules/shipping-bar/module.json +45 -0
  66. package/modules/shipping-bar/sections/runwell-shipping-bar.liquid +95 -0
  67. package/modules/sticky-atc/README.md +17 -0
  68. package/modules/sticky-atc/module.json +14 -0
  69. package/modules/sticky-atc/sections/runwell-pdp-sticky.liquid +78 -0
  70. package/modules/testimonials/README.md +17 -0
  71. package/modules/testimonials/module.json +35 -0
  72. package/modules/testimonials/sections/runwell-testimonials.liquid +87 -0
  73. package/modules/trust-badges/README.md +17 -0
  74. package/modules/trust-badges/module.json +25 -0
  75. package/modules/trust-badges/sections/runwell-trust-badges.liquid +93 -0
  76. package/package.json +45 -0
@@ -0,0 +1,38 @@
1
+ {%- comment -%}
2
+ Lushi recently-viewed products. Reads from localStorage (populated on
3
+ PDP visits) and renders up to 4 cards. Native replacement for the
4
+ recently-viewed feature in Vitals / similar apps.
5
+ {%- endcomment -%}
6
+
7
+ <section
8
+ class="runwell-rv"
9
+ data-runwell-recently-viewed
10
+ style="display: none; background: {{ section.settings.background_color | default: '#FFFFFF' }};">
11
+ <div class="runwell-rv__inner">
12
+ {%- if section.settings.eyebrow != blank -%}
13
+ <p class="runwell-rv__eyebrow">{{ section.settings.eyebrow }}</p>
14
+ {%- endif -%}
15
+ {%- if section.settings.heading != blank -%}
16
+ <h2 class="runwell-rv__heading">{{ section.settings.heading }}</h2>
17
+ {%- endif -%}
18
+ <div class="runwell-rv__grid" data-runwell-recently-viewed-grid></div>
19
+ </div>
20
+ </section>
21
+
22
+ <script src="{{ 'runwell-recently-viewed.js' | asset_url }}" defer="defer"></script>
23
+
24
+ {% schema %}
25
+ {
26
+ "name": "Lushi recently viewed",
27
+ "tag": "section",
28
+ "class": "section-runwell-rv",
29
+ "settings": [
30
+ { "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "Picking up where you left off" },
31
+ { "type": "text", "id": "heading", "label": "Heading", "default": "Recently viewed." },
32
+ { "type": "color", "id": "background_color", "label": "Background", "default": "#FFFFFF" }
33
+ ],
34
+ "presets": [
35
+ { "name": "Lushi recently viewed" }
36
+ ]
37
+ }
38
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # reviews
2
+
3
+ Lushi PDP reviews. Migrated from Lushi (`sections/lushi-pdp-reviews.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `social-proof`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-pdp-reviews.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-pdp-reviews.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "reviews",
3
+ "version": "0.1.0",
4
+ "category": "social-proof",
5
+ "description": "Lushi PDP reviews module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-pdp-reviews.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "heading": {
14
+ "type": "string",
15
+ "default": "What customers wrote in.",
16
+ "label": "Heading"
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,93 @@
1
+ {%- comment -%}
2
+ Lushi native PDP reviews. Replaces Stamped / Loox display layer.
3
+ Reads from product metafield `lushi.reviews` (json type) where each
4
+ entry is { name, location, rating, body, date, verified }.
5
+ Falls back to a "no reviews yet" state with a CTA to email a review.
6
+ {%- endcomment -%}
7
+
8
+ {%- if product != blank -%}
9
+ {%- assign reviews_json = product.metafields.lushi.reviews.value -%}
10
+ <section class="runwell-reviews" data-runwell-reviews>
11
+ <div class="runwell-reviews__inner">
12
+ <header class="runwell-reviews__header">
13
+ <h2 class="runwell-reviews__heading">{{ section.settings.heading | default: 'What customers wrote in.' }}</h2>
14
+ {%- if reviews_json != blank and reviews_json.size > 0 -%}
15
+ {%- assign sum = 0 -%}
16
+ {%- for r in reviews_json -%}
17
+ {%- assign sum = sum | plus: r.rating -%}
18
+ {%- endfor -%}
19
+ {%- assign avg = sum | divided_by: reviews_json.size | times: 1.0 -%}
20
+ {%- assign avg_int = sum | divided_by: reviews_json.size -%}
21
+ <div class="runwell-reviews__summary">
22
+ <span class="runwell-reviews__stars" aria-label="{{ avg_int }} out of 5">
23
+ {%- for i in (1..5) -%}{%- if i <= avg_int -%}★{%- else -%}☆{%- endif -%}{%- endfor -%}
24
+ </span>
25
+ <span class="runwell-reviews__count">{{ reviews_json.size }} review{% if reviews_json.size > 1 %}s{% endif %}</span>
26
+ </div>
27
+ {%- endif -%}
28
+ </header>
29
+
30
+ {%- if reviews_json != blank and reviews_json.size > 0 -%}
31
+ <ul class="runwell-reviews__list">
32
+ {%- for r in reviews_json -%}
33
+ <li class="runwell-reviews__item">
34
+ <div class="runwell-reviews__row">
35
+ <span class="runwell-reviews__stars" aria-label="{{ r.rating }} out of 5">
36
+ {%- for i in (1..5) -%}{%- if i <= r.rating -%}★{%- else -%}☆{%- endif -%}{%- endfor -%}
37
+ </span>
38
+ {%- if r.verified -%}<span class="runwell-reviews__verified">Verified buyer</span>{%- endif -%}
39
+ </div>
40
+ {%- if r.body -%}<p class="runwell-reviews__body">{{ r.body }}</p>{%- endif -%}
41
+ <p class="runwell-reviews__meta">
42
+ <strong>{{ r.name }}</strong>{% if r.location %}, {{ r.location }}{% endif %}{% if r.date %} &middot; {{ r.date }}{% endif %}
43
+ </p>
44
+ </li>
45
+ {%- endfor -%}
46
+ </ul>
47
+ {%- else -%}
48
+ <div class="runwell-reviews__empty">
49
+ <p>No reviews on this one yet. If you have tried it, write us at <a href="mailto:hello@lushi.co?subject=Review for {{ product.title | escape }}">hello@lushi.co</a> and we will publish thoughtful reviews on the page.</p>
50
+ </div>
51
+ {%- endif -%}
52
+
53
+ <details class="runwell-reviews__write">
54
+ <summary>Write a review</summary>
55
+ {%- form 'contact', class: 'runwell-reviews__form' -%}
56
+ <input type="hidden" name="contact[product]" value="{{ product.title }}">
57
+ <input type="hidden" name="contact[product_handle]" value="{{ product.handle }}">
58
+ <input type="hidden" name="contact[type]" value="review">
59
+ <label>Your name<input type="text" name="contact[name]" required></label>
60
+ <label>Email<input type="email" name="contact[email]" required></label>
61
+ <label>Rating
62
+ <select name="contact[rating]" required>
63
+ <option value="5">5 stars</option>
64
+ <option value="4">4 stars</option>
65
+ <option value="3">3 stars</option>
66
+ <option value="2">2 stars</option>
67
+ <option value="1">1 star</option>
68
+ </select>
69
+ </label>
70
+ <label>Your review<textarea name="contact[body]" rows="4" required></textarea></label>
71
+ <button type="submit">Submit</button>
72
+ {%- if form.posted_successfully? -%}
73
+ <p class="runwell-reviews__success">Thanks. We read every review before publishing.</p>
74
+ {%- endif -%}
75
+ {%- endform -%}
76
+ </details>
77
+ </div>
78
+ </section>
79
+ {%- endif -%}
80
+
81
+ {% schema %}
82
+ {
83
+ "name": "Lushi PDP reviews",
84
+ "tag": "section",
85
+ "class": "section-runwell-pdp-reviews",
86
+ "settings": [
87
+ { "type": "text", "id": "heading", "label": "Heading", "default": "What customers wrote in." }
88
+ ],
89
+ "presets": [
90
+ { "name": "Lushi PDP reviews" }
91
+ ]
92
+ }
93
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # risk-reversal
2
+
3
+ Lushi risk reversal. Migrated from Lushi (`sections/lushi-risk-reversal.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `conversion`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-risk-reversal.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-risk-reversal.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "risk-reversal",
3
+ "version": "0.1.0",
4
+ "category": "conversion",
5
+ "description": "Lushi risk reversal module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-risk-reversal.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "icon": {
14
+ "type": "string",
15
+ "default": "↺",
16
+ "label": "Icon (emoji or single char)"
17
+ },
18
+ "heading": {
19
+ "type": "string",
20
+ "default": "30-day satisfaction promise.",
21
+ "label": "Heading"
22
+ },
23
+ "body": {
24
+ "type": "string",
25
+ "default": "Try it. If it does not earn a place on your counter, write us within 30 days for a full refund. We do not make returns hard.",
26
+ "label": "Body"
27
+ },
28
+ "link_label": {
29
+ "type": "string",
30
+ "default": "Read our return policy",
31
+ "label": "Link label"
32
+ },
33
+ "link_url": {
34
+ "type": "string",
35
+ "label": "Link URL"
36
+ },
37
+ "background_color": {
38
+ "type": "string",
39
+ "default": "#EDE6D8",
40
+ "label": "Background color"
41
+ },
42
+ "text_color": {
43
+ "type": "string",
44
+ "default": "#0B3D38",
45
+ "label": "Text color"
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,94 @@
1
+ {%- comment -%}
2
+ Lushi risk-reversal block. Small, calm reassurance that drops directly
3
+ under the PDP buy form (or anywhere the editor wants reassurance).
4
+ Per CRO §5.3 (foundational trust signal under add-to-cart).
5
+ {%- endcomment -%}
6
+
7
+ <section
8
+ class="runwell-risk-reversal"
9
+ style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }};"
10
+ >
11
+ <div style="max-width: 1200px; margin: 0 auto; padding: clamp(1.4rem, 3vw, 2.2rem) 6vw;">
12
+ <div style="display: flex; align-items: flex-start; gap: 1rem;">
13
+ {%- if section.settings.icon != blank -%}
14
+ <div style="flex: 0 0 auto; font-size: 1.6rem; line-height: 1; opacity: 0.7;">
15
+ {{ section.settings.icon }}
16
+ </div>
17
+ {%- endif -%}
18
+ <div style="flex: 1 1 auto;">
19
+ {%- if section.settings.heading != blank -%}
20
+ <h3 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.15rem; line-height: 1.2; margin: 0 0 0.3rem 0;">
21
+ {{ section.settings.heading }}
22
+ </h3>
23
+ {%- endif -%}
24
+ {%- if section.settings.body != blank -%}
25
+ <p style="font-family: var(--font-body-family); font-size: 0.92rem; line-height: 1.55; margin: 0; opacity: 0.85;">
26
+ {{ section.settings.body }}
27
+ </p>
28
+ {%- endif -%}
29
+ {%- if section.settings.link_label != blank -%}
30
+ <a href="{{ section.settings.link_url | default: '/policies/refund-policy' }}"
31
+ style="display: inline-block; margin-top: 0.5rem; font-family: var(--font-body-family); font-weight: 700; font-size: 0.82rem; letter-spacing: 0.04em; text-decoration: underline; text-underline-offset: 4px; text-decoration-color: var(--runwell-blue);">
32
+ {{ section.settings.link_label }} &rarr;
33
+ </a>
34
+ {%- endif -%}
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </section>
39
+
40
+ {% schema %}
41
+ {
42
+ "name": "Lushi risk reversal",
43
+ "tag": "section",
44
+ "class": "section-runwell-risk-reversal",
45
+ "settings": [
46
+ {
47
+ "type": "text",
48
+ "id": "icon",
49
+ "label": "Icon (emoji or single char)",
50
+ "default": "↺"
51
+ },
52
+ {
53
+ "type": "text",
54
+ "id": "heading",
55
+ "label": "Heading",
56
+ "default": "30-day satisfaction promise."
57
+ },
58
+ {
59
+ "type": "textarea",
60
+ "id": "body",
61
+ "label": "Body",
62
+ "default": "Try it. If it does not earn a place on your counter, write us within 30 days for a full refund. We do not make returns hard."
63
+ },
64
+ {
65
+ "type": "text",
66
+ "id": "link_label",
67
+ "label": "Link label",
68
+ "default": "Read our return policy"
69
+ },
70
+ {
71
+ "type": "url",
72
+ "id": "link_url",
73
+ "label": "Link URL"
74
+ },
75
+ {
76
+ "type": "color",
77
+ "id": "background_color",
78
+ "label": "Background color",
79
+ "default": "#EDE6D8"
80
+ },
81
+ {
82
+ "type": "color",
83
+ "id": "text_color",
84
+ "label": "Text color",
85
+ "default": "#0B3D38"
86
+ }
87
+ ],
88
+ "presets": [
89
+ {
90
+ "name": "Lushi risk reversal"
91
+ }
92
+ ]
93
+ }
94
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # shipping-bar
2
+
3
+ Lushi shipping bar. Migrated from Lushi (`sections/lushi-shipping-bar.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `conversion`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-shipping-bar.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-shipping-bar.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "shipping-bar",
3
+ "version": "0.1.0",
4
+ "category": "conversion",
5
+ "description": "Lushi shipping bar module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-shipping-bar.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "threshold_cents": {
14
+ "type": "number",
15
+ "default": 7500,
16
+ "label": "Free shipping threshold (in cents)"
17
+ },
18
+ "message_below": {
19
+ "type": "string",
20
+ "default": "Add [remaining] more for free shipping.",
21
+ "label": "Message when below threshold"
22
+ },
23
+ "message_qualified": {
24
+ "type": "string",
25
+ "default": "You qualified for free shipping. Glow on.",
26
+ "label": "Message when qualified"
27
+ },
28
+ "message_default": {
29
+ "type": "string",
30
+ "default": "Free shipping on US orders over $75.",
31
+ "label": "Default message (no threshold set)"
32
+ },
33
+ "background_color": {
34
+ "type": "string",
35
+ "default": "#0B3D38",
36
+ "label": "Background color"
37
+ },
38
+ "text_color": {
39
+ "type": "string",
40
+ "default": "#EDE6D8",
41
+ "label": "Text color"
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,95 @@
1
+ {%- comment -%}
2
+ Lushi free-shipping announcement bar. Sits at the very top of every
3
+ page. Reads cart total and shows a progress message + bar.
4
+ {%- endcomment -%}
5
+
6
+ {%- liquid
7
+ assign threshold = section.settings.threshold_cents | times: 1
8
+ assign current = cart.total_price | times: 1
9
+ if threshold > 0
10
+ assign remaining = threshold | minus: current
11
+ assign progress = current | times: 100.0 | divided_by: threshold
12
+ if progress > 100
13
+ assign progress = 100
14
+ endif
15
+ endif
16
+ -%}
17
+
18
+ <aside
19
+ class="runwell-shipping-bar"
20
+ data-section-id="{{ section.id }}"
21
+ style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }}; padding: 0.55rem 1.5rem; font-family: var(--font-body-family); font-size: 0.82rem; letter-spacing: 0.04em; text-align: center;"
22
+ >
23
+ <div style="max-width: 1400px; margin: 0 auto;">
24
+ {%- if threshold > 0 and remaining > 0 -%}
25
+ {%- assign remaining_money = remaining | money_without_trailing_zeros -%}
26
+ <span>
27
+ {{ section.settings.message_below
28
+ | replace: '[remaining]', remaining_money }}
29
+ </span>
30
+ <div style="margin-top: 0.4rem; height: 2px; background: rgba(255,255,255,0.18); border-radius: 1px; overflow: hidden;">
31
+ <div style="height: 100%; width: {{ progress | round }}%; background: currentColor; transition: width 0.3s ease;"></div>
32
+ </div>
33
+ {%- elsif threshold > 0 -%}
34
+ <span>{{ section.settings.message_qualified }}</span>
35
+ {%- else -%}
36
+ <span>{{ section.settings.message_default }}</span>
37
+ {%- endif -%}
38
+ </div>
39
+ </aside>
40
+
41
+ {% schema %}
42
+ {
43
+ "name": "Lushi shipping bar",
44
+ "tag": "aside",
45
+ "class": "section-runwell-shipping-bar",
46
+ "enabled_on": {
47
+ "groups": ["header"]
48
+ },
49
+ "settings": [
50
+ {
51
+ "type": "number",
52
+ "id": "threshold_cents",
53
+ "label": "Free shipping threshold (in cents)",
54
+ "info": "Set to 7500 for $75. Set to 0 to always show the default message.",
55
+ "default": 7500
56
+ },
57
+ {
58
+ "type": "text",
59
+ "id": "message_below",
60
+ "label": "Message when below threshold",
61
+ "info": "Use [remaining] as a placeholder for the amount left to qualify.",
62
+ "default": "Add [remaining] more for free shipping."
63
+ },
64
+ {
65
+ "type": "text",
66
+ "id": "message_qualified",
67
+ "label": "Message when qualified",
68
+ "default": "You qualified for free shipping. Glow on."
69
+ },
70
+ {
71
+ "type": "text",
72
+ "id": "message_default",
73
+ "label": "Default message (no threshold set)",
74
+ "default": "Free shipping on US orders over $75."
75
+ },
76
+ {
77
+ "type": "color",
78
+ "id": "background_color",
79
+ "label": "Background color",
80
+ "default": "#0B3D38"
81
+ },
82
+ {
83
+ "type": "color",
84
+ "id": "text_color",
85
+ "label": "Text color",
86
+ "default": "#EDE6D8"
87
+ }
88
+ ],
89
+ "presets": [
90
+ {
91
+ "name": "Lushi shipping bar"
92
+ }
93
+ ]
94
+ }
95
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # sticky-atc
2
+
3
+ Lushi sticky ATC. Migrated from Lushi (`sections/lushi-pdp-sticky.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `conversion`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-pdp-sticky.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-pdp-sticky.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "sticky-atc",
3
+ "version": "0.1.0",
4
+ "category": "conversion",
5
+ "description": "Lushi sticky ATC module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-pdp-sticky.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {}
13
+ }
14
+ }
@@ -0,0 +1,78 @@
1
+ {%- comment -%}
2
+ Lushi sticky add-to-cart for PDP. Slides up from the bottom on scroll
3
+ past the main buy button, hides again when buy area returns to view.
4
+ Mobile-first; on desktop reveals only after a meaningful scroll depth.
5
+ {%- endcomment -%}
6
+
7
+ {%- if template == 'product' and product != blank -%}
8
+ <div class="runwell-sticky-atc" data-runwell-sticky aria-hidden="true">
9
+ <div class="runwell-sticky-atc__inner">
10
+ <div class="runwell-sticky-atc__product">
11
+ {%- if product.featured_image -%}
12
+ <img
13
+ src="{{ product.featured_image | image_url: width: 80 }}"
14
+ alt="{{ product.title | escape }}"
15
+ width="40"
16
+ height="40"
17
+ loading="lazy"
18
+ class="runwell-sticky-atc__thumb">
19
+ {%- endif -%}
20
+ <div class="runwell-sticky-atc__meta">
21
+ <span class="runwell-sticky-atc__title">{{ product.title }}</span>
22
+ <span class="runwell-sticky-atc__price">{{ product.price | money }}</span>
23
+ </div>
24
+ </div>
25
+ <form
26
+ action="{{ routes.cart_add_url }}"
27
+ method="post"
28
+ enctype="multipart/form-data"
29
+ class="runwell-sticky-atc__form">
30
+ <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
31
+ <input type="hidden" name="quantity" value="1">
32
+ <button
33
+ type="submit"
34
+ class="runwell-sticky-atc__cta"
35
+ {% if product.selected_or_first_available_variant.available == false %}disabled{% endif %}>
36
+ {%- if product.selected_or_first_available_variant.available -%}
37
+ Add to cart
38
+ {%- else -%}
39
+ Sold out
40
+ {%- endif -%}
41
+ </button>
42
+ </form>
43
+ </div>
44
+ </div>
45
+
46
+ <script>
47
+ (function () {
48
+ var bar = document.querySelector('[data-runwell-sticky]');
49
+ if (!bar) return;
50
+ var trigger = document.querySelector('product-form, .product-form, [name="add"]') ||
51
+ document.querySelector('.product__info-container');
52
+ if (!trigger) return;
53
+
54
+ var observer = new IntersectionObserver(function (entries) {
55
+ entries.forEach(function (e) {
56
+ if (e.isIntersecting) {
57
+ bar.setAttribute('aria-hidden', 'true');
58
+ } else if (e.boundingClientRect.top < 0) {
59
+ bar.setAttribute('aria-hidden', 'false');
60
+ }
61
+ });
62
+ }, { threshold: 0 });
63
+ observer.observe(trigger);
64
+ })();
65
+ </script>
66
+ {%- endif -%}
67
+
68
+ {% schema %}
69
+ {
70
+ "name": "Lushi sticky ATC",
71
+ "tag": "section",
72
+ "class": "section-runwell-sticky-atc",
73
+ "settings": [],
74
+ "presets": [
75
+ { "name": "Lushi sticky ATC" }
76
+ ]
77
+ }
78
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # testimonials
2
+
3
+ Lushi testimonials. Migrated from Lushi (`sections/lushi-testimonials.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `social-proof`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-testimonials.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-testimonials.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "testimonials",
3
+ "version": "0.1.0",
4
+ "category": "social-proof",
5
+ "description": "Lushi testimonials module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-testimonials.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "eyebrow": {
14
+ "type": "string",
15
+ "default": "From the inbox",
16
+ "label": "Eyebrow"
17
+ },
18
+ "heading": {
19
+ "type": "string",
20
+ "default": "What customers are writing in.",
21
+ "label": "Heading"
22
+ },
23
+ "background_color": {
24
+ "type": "string",
25
+ "default": "#F5F0EE",
26
+ "label": "Background"
27
+ },
28
+ "text_color": {
29
+ "type": "string",
30
+ "default": "#0B3D38",
31
+ "label": "Text"
32
+ }
33
+ }
34
+ }
35
+ }