@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,17 @@
1
+ # pdp-ingredients
2
+
3
+ Lushi ingredients. Migrated from Lushi (`sections/lushi-ingredients.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `pdp`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-ingredients.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-ingredients.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "pdp-ingredients",
3
+ "version": "0.1.0",
4
+ "category": "pdp",
5
+ "description": "Lushi ingredients module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-ingredients.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "eyebrow": {
14
+ "type": "string",
15
+ "default": "What's inside",
16
+ "label": "Eyebrow"
17
+ },
18
+ "heading": {
19
+ "type": "string",
20
+ "default": "The hero ingredients.",
21
+ "label": "Heading"
22
+ },
23
+ "lede": {
24
+ "type": "string",
25
+ "default": "We list the dose for every active. If we used a buzzword ingredient, we'll tell you why.",
26
+ "label": "Lede"
27
+ },
28
+ "background_color": {
29
+ "type": "string",
30
+ "default": "#EDE6D8",
31
+ "label": "Background color"
32
+ },
33
+ "text_color": {
34
+ "type": "string",
35
+ "default": "#0B3D38",
36
+ "label": "Text color"
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,139 @@
1
+ {%- comment -%}
2
+ Lushi ingredient grid. Drops below a PDP description (or anywhere) and
3
+ spotlights 3 to 6 hero ingredients with name, role, and dose. Reads
4
+ product.metafields.lushi.ingredients (list.metaobject) when set, with
5
+ manual blocks as the fallback.
6
+ {%- endcomment -%}
7
+
8
+ {%- assign ingredients_meta = product.metafields.lushi.ingredients.value -%}
9
+
10
+ <section
11
+ class="runwell-ingredients"
12
+ style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }};"
13
+ >
14
+ <div style="max-width: 1200px; margin: 0 auto; padding: clamp(3rem, 6vw, 5rem) 6vw;">
15
+ {%- if section.settings.eyebrow != blank -%}
16
+ <div style="font-family: var(--font-body-family); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.65; margin-bottom: 0.6rem;">
17
+ {{ section.settings.eyebrow }}
18
+ </div>
19
+ {%- endif -%}
20
+
21
+ {%- if section.settings.heading != blank -%}
22
+ <h2 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: clamp(1.8rem, 3.2vw, 2.6rem); line-height: 1.1; margin: 0 0 1rem 0; max-width: 22ch;">
23
+ {{ section.settings.heading }}
24
+ </h2>
25
+ {%- endif -%}
26
+
27
+ {%- if section.settings.lede != blank -%}
28
+ <p style="font-family: var(--font-body-family); font-size: 1rem; line-height: 1.65; max-width: 60ch; opacity: 0.85; margin: 0 0 2.5rem 0;">
29
+ {{ section.settings.lede }}
30
+ </p>
31
+ {%- endif -%}
32
+
33
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.6rem;">
34
+ {%- if ingredients_meta != blank -%}
35
+ {%- for ing in ingredients_meta -%}
36
+ <div style="border-top: 2px solid currentColor; padding-top: 1rem;">
37
+ {%- if ing.dose != blank -%}
38
+ <div style="font-family: var(--font-body-family); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.6; margin-bottom: 0.4rem;">
39
+ {{ ing.dose }}
40
+ </div>
41
+ {%- endif -%}
42
+ <h3 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.3rem; line-height: 1.2; margin: 0 0 0.5rem 0;">
43
+ {{ ing.name }}
44
+ </h3>
45
+ {%- if ing.role != blank -%}
46
+ <p style="font-family: var(--font-body-family); font-size: 0.92rem; line-height: 1.6; opacity: 0.85; margin: 0;">
47
+ {{ ing.role }}
48
+ </p>
49
+ {%- endif -%}
50
+ </div>
51
+ {%- endfor -%}
52
+ {%- else -%}
53
+ {%- for block in section.blocks -%}
54
+ <div {{ block.shopify_attributes }} style="border-top: 2px solid currentColor; padding-top: 1rem;">
55
+ {%- if block.settings.dose != blank -%}
56
+ <div style="font-family: var(--font-body-family); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.6; margin-bottom: 0.4rem;">
57
+ {{ block.settings.dose }}
58
+ </div>
59
+ {%- endif -%}
60
+ <h3 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.3rem; line-height: 1.2; margin: 0 0 0.5rem 0;">
61
+ {{ block.settings.name }}
62
+ </h3>
63
+ {%- if block.settings.role != blank -%}
64
+ <p style="font-family: var(--font-body-family); font-size: 0.92rem; line-height: 1.6; opacity: 0.85; margin: 0;">
65
+ {{ block.settings.role }}
66
+ </p>
67
+ {%- endif -%}
68
+ </div>
69
+ {%- endfor -%}
70
+ {%- endif -%}
71
+ </div>
72
+ </div>
73
+ </section>
74
+
75
+ {% schema %}
76
+ {
77
+ "name": "Lushi ingredients",
78
+ "tag": "section",
79
+ "class": "section-runwell-ingredients",
80
+ "settings": [
81
+ {
82
+ "type": "text",
83
+ "id": "eyebrow",
84
+ "label": "Eyebrow",
85
+ "default": "What's inside"
86
+ },
87
+ {
88
+ "type": "text",
89
+ "id": "heading",
90
+ "label": "Heading",
91
+ "default": "The hero ingredients."
92
+ },
93
+ {
94
+ "type": "textarea",
95
+ "id": "lede",
96
+ "label": "Lede",
97
+ "default": "We list the dose for every active. If we used a buzzword ingredient, we'll tell you why."
98
+ },
99
+ {
100
+ "type": "color",
101
+ "id": "background_color",
102
+ "label": "Background color",
103
+ "default": "#EDE6D8"
104
+ },
105
+ {
106
+ "type": "color",
107
+ "id": "text_color",
108
+ "label": "Text color",
109
+ "default": "#0B3D38"
110
+ },
111
+ {
112
+ "type": "paragraph",
113
+ "content": "If product.metafields.lushi.ingredients is set, those are used and the blocks below are ignored."
114
+ }
115
+ ],
116
+ "blocks": [
117
+ {
118
+ "type": "ingredient",
119
+ "name": "Ingredient",
120
+ "settings": [
121
+ { "type": "text", "id": "name", "label": "Name", "default": "Magnesium glycinate" },
122
+ { "type": "text", "id": "dose", "label": "Dose", "default": "200mg" },
123
+ { "type": "textarea", "id": "role", "label": "What it does" }
124
+ ]
125
+ }
126
+ ],
127
+ "max_blocks": 8,
128
+ "presets": [
129
+ {
130
+ "name": "Lushi ingredients",
131
+ "blocks": [
132
+ { "type": "ingredient", "settings": { "name": "Magnesium glycinate", "dose": "200mg", "role": "The most bioavailable form of magnesium. Supports sleep onset and muscle recovery." } },
133
+ { "type": "ingredient", "settings": { "name": "L-theanine", "dose": "100mg", "role": "Amino acid from green tea. Calms without sedating; pairs well with magnesium." } },
134
+ { "type": "ingredient", "settings": { "name": "Apigenin", "dose": "50mg", "role": "Found in chamomile. Mild anxiolytic effect; supports a quieter pre-sleep mind." } }
135
+ ]
136
+ }
137
+ ]
138
+ }
139
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # pdp-journal-link
2
+
3
+ Lushi PDP journal link. Migrated from Lushi (`sections/lushi-pdp-journal.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `pdp`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-pdp-journal.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-journal.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "pdp-journal-link",
3
+ "version": "0.1.0",
4
+ "category": "pdp",
5
+ "description": "Lushi PDP journal link module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-pdp-journal.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "eyebrow": {
14
+ "type": "string",
15
+ "default": "From the journal",
16
+ "label": "Eyebrow"
17
+ },
18
+ "article": {
19
+ "type": "string",
20
+ "label": "Linked article (override)"
21
+ },
22
+ "background_color": {
23
+ "type": "string",
24
+ "default": "#0B3D38",
25
+ "label": "Background color"
26
+ },
27
+ "text_color": {
28
+ "type": "string",
29
+ "default": "#EDE6D8",
30
+ "label": "Text color"
31
+ },
32
+ "fallback_title": {
33
+ "type": "string",
34
+ "default": "Read the full story.",
35
+ "label": "Fallback title"
36
+ },
37
+ "fallback_body": {
38
+ "type": "string",
39
+ "default": "Every product on Lushi pairs with a journal article. Browse the full archive to dig deeper.",
40
+ "label": "Fallback body"
41
+ },
42
+ "fallback_link_label": {
43
+ "type": "string",
44
+ "default": "Visit the journal",
45
+ "label": "Fallback link label"
46
+ },
47
+ "fallback_link_url": {
48
+ "type": "string",
49
+ "label": "Fallback link URL"
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,124 @@
1
+ {%- comment -%}
2
+ Lushi PDP journal link. Connects a product to the editorial article that
3
+ explains why it's on Lushi. Uses the product's metafield
4
+ `lushi.journal_article` if set, otherwise falls back to the linked
5
+ article picked in the section settings, otherwise the manual title.
6
+ {%- endcomment -%}
7
+
8
+ {%- assign linked_article = product.metafields.lushi.journal_article.value -%}
9
+ {%- if linked_article == blank -%}
10
+ {%- assign linked_article = section.settings.article -%}
11
+ {%- endif -%}
12
+
13
+ {%- if linked_article != blank or section.settings.fallback_title != blank -%}
14
+ <section
15
+ class="runwell-pdp-journal"
16
+ style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }};"
17
+ >
18
+ <div class="runwell-pdp-journal__inner" style="max-width: 1200px; margin: 0 auto; padding: clamp(3rem, 6vw, 5rem) 6vw; display: grid; grid-template-columns: 1fr; gap: 2rem;">
19
+ <div style="display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; align-items: center;">
20
+ <div>
21
+ {%- if section.settings.eyebrow != blank -%}
22
+ <div style="font-family: var(--font-body-family); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.65; margin-bottom: 0.6rem;">
23
+ {{ section.settings.eyebrow }}
24
+ </div>
25
+ {%- endif -%}
26
+
27
+ {%- if linked_article != blank -%}
28
+ <h2 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: clamp(1.8rem, 3.2vw, 2.6rem); line-height: 1.1; margin: 0 0 0.8rem 0; max-width: 22ch;">
29
+ {{ linked_article.title }}
30
+ </h2>
31
+ <p style="font-family: var(--font-body-family); font-size: 1rem; line-height: 1.65; max-width: 60ch; opacity: 0.85; margin: 0 0 1.4rem 0;">
32
+ {{ linked_article.excerpt_or_content | strip_html | truncate: 180 }}
33
+ </p>
34
+ <a href="{{ linked_article.url }}"
35
+ style="display: inline-block; padding: 0.85rem 1.4rem; border-radius: 4px; background: {{ section.settings.text_color }}; color: {{ section.settings.background_color }}; font-weight: 700; font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; text-decoration: none;">
36
+ Read the journal &rarr;
37
+ </a>
38
+ {%- else -%}
39
+ <h2 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: clamp(1.8rem, 3.2vw, 2.6rem); line-height: 1.1; margin: 0 0 0.8rem 0; max-width: 22ch;">
40
+ {{ section.settings.fallback_title }}
41
+ </h2>
42
+ {%- if section.settings.fallback_body != blank -%}
43
+ <p style="font-family: var(--font-body-family); font-size: 1rem; line-height: 1.65; max-width: 60ch; opacity: 0.85; margin: 0 0 1.4rem 0;">
44
+ {{ section.settings.fallback_body }}
45
+ </p>
46
+ {%- endif -%}
47
+ {%- if section.settings.fallback_link_url != blank -%}
48
+ <a href="{{ section.settings.fallback_link_url }}"
49
+ style="display: inline-block; padding: 0.85rem 1.4rem; border-radius: 4px; background: {{ section.settings.text_color }}; color: {{ section.settings.background_color }}; font-weight: 700; font-size: 0.85rem; letter-spacing: 0.04em; text-transform: uppercase; text-decoration: none;">
50
+ {{ section.settings.fallback_link_label | default: 'Read more' }} &rarr;
51
+ </a>
52
+ {%- endif -%}
53
+ {%- endif -%}
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </section>
58
+ {%- endif -%}
59
+
60
+ {% schema %}
61
+ {
62
+ "name": "Lushi PDP journal link",
63
+ "tag": "section",
64
+ "class": "section-runwell-pdp-journal",
65
+ "settings": [
66
+ {
67
+ "type": "text",
68
+ "id": "eyebrow",
69
+ "label": "Eyebrow",
70
+ "default": "From the journal"
71
+ },
72
+ {
73
+ "type": "article",
74
+ "id": "article",
75
+ "label": "Linked article (override)",
76
+ "info": "Optional. If set, overrides the product's lushi.journal_article metafield."
77
+ },
78
+ {
79
+ "type": "color",
80
+ "id": "background_color",
81
+ "label": "Background color",
82
+ "default": "#0B3D38"
83
+ },
84
+ {
85
+ "type": "color",
86
+ "id": "text_color",
87
+ "label": "Text color",
88
+ "default": "#EDE6D8"
89
+ },
90
+ {
91
+ "type": "header",
92
+ "content": "Fallback (when no article is set)"
93
+ },
94
+ {
95
+ "type": "text",
96
+ "id": "fallback_title",
97
+ "label": "Fallback title",
98
+ "default": "Read the full story."
99
+ },
100
+ {
101
+ "type": "textarea",
102
+ "id": "fallback_body",
103
+ "label": "Fallback body",
104
+ "default": "Every product on Lushi pairs with a journal article. Browse the full archive to dig deeper."
105
+ },
106
+ {
107
+ "type": "text",
108
+ "id": "fallback_link_label",
109
+ "label": "Fallback link label",
110
+ "default": "Visit the journal"
111
+ },
112
+ {
113
+ "type": "url",
114
+ "id": "fallback_link_url",
115
+ "label": "Fallback link URL"
116
+ }
117
+ ],
118
+ "presets": [
119
+ {
120
+ "name": "Lushi PDP journal link"
121
+ }
122
+ ]
123
+ }
124
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ # pdp-trust-checks
2
+
3
+ Lushi PDP trust. Migrated from Lushi (`sections/lushi-pdp-trust.liquid`) into the Runwell Shopify Toolkit.
4
+
5
+ Category: `pdp`
6
+
7
+ ## Files
8
+
9
+ - `sections/runwell-pdp-trust.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-trust.liquid`. The toolkit version uses `runwell-` class prefixes and pulls brand vars from the client config.
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "pdp-trust-checks",
3
+ "version": "0.1.0",
4
+ "category": "pdp",
5
+ "description": "Lushi PDP trust module migrated from Lushi.",
6
+ "files": {
7
+ "sections": [
8
+ "sections/runwell-pdp-trust.liquid"
9
+ ]
10
+ },
11
+ "config": {
12
+ "schema": {
13
+ "eyebrow": {
14
+ "type": "string",
15
+ "default": "How we picked this",
16
+ "label": "Eyebrow"
17
+ },
18
+ "heading": {
19
+ "type": "string",
20
+ "default": "Four checks, every product.",
21
+ "label": "Heading"
22
+ },
23
+ "lede": {
24
+ "type": "string",
25
+ "default": "We do not stock everything. Every product on Lushi has to clear these four checks before it gets a page.",
26
+ "label": "Lede"
27
+ },
28
+ "background_color": {
29
+ "type": "string",
30
+ "default": "#F5F0EE",
31
+ "label": "Background color"
32
+ },
33
+ "text_color": {
34
+ "type": "string",
35
+ "default": "#0B3D38",
36
+ "label": "Text color"
37
+ },
38
+ "link_label": {
39
+ "type": "string",
40
+ "default": "Read all our standards",
41
+ "label": "Link label"
42
+ },
43
+ "link_url": {
44
+ "type": "string",
45
+ "label": "Link URL"
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,141 @@
1
+ {%- comment -%}
2
+ Lushi PDP trust block. Shows the four-check standards on every PDP.
3
+ Builds buyer trust the way Goop's "What it is / what it does" panels do.
4
+ Drop into product.json after the main-product section.
5
+ {%- endcomment -%}
6
+
7
+ <section
8
+ class="runwell-pdp-trust"
9
+ style="background: {{ section.settings.background_color }}; color: {{ section.settings.text_color }};"
10
+ >
11
+ <div class="runwell-pdp-trust__inner" style="max-width: 1200px; margin: 0 auto; padding: clamp(3rem, 6vw, 5rem) 6vw;">
12
+ {%- if section.settings.eyebrow != blank -%}
13
+ <div style="font-family: var(--font-body-family); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.65; margin-bottom: 0.8rem;">
14
+ {{ section.settings.eyebrow }}
15
+ </div>
16
+ {%- endif -%}
17
+
18
+ {%- if section.settings.heading != blank -%}
19
+ <h2 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: clamp(1.8rem, 3.2vw, 2.6rem); line-height: 1.1; margin: 0 0 1rem 0; max-width: 22ch;">
20
+ {{ section.settings.heading }}
21
+ </h2>
22
+ {%- endif -%}
23
+
24
+ {%- if section.settings.lede != blank -%}
25
+ <p style="font-family: var(--font-body-family); font-size: 1rem; line-height: 1.65; max-width: 60ch; opacity: 0.85; margin: 0 0 2.5rem 0;">
26
+ {{ section.settings.lede }}
27
+ </p>
28
+ {%- endif -%}
29
+
30
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.6rem; margin-top: 2rem;">
31
+ {%- for block in section.blocks -%}
32
+ <div {{ block.shopify_attributes }} style="border-top: 2px solid currentColor; padding-top: 1rem; opacity: 0.92;">
33
+ <div style="font-family: var(--font-body-family); font-size: 0.75rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.6; margin-bottom: 0.4rem;">
34
+ {{ block.settings.eyebrow | default: forloop.index | prepend: 'Check ' }}
35
+ </div>
36
+ <h3 style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.25rem; line-height: 1.2; margin: 0 0 0.5rem 0;">
37
+ {{ block.settings.title }}
38
+ </h3>
39
+ <p style="font-family: var(--font-body-family); font-size: 0.92rem; line-height: 1.6; opacity: 0.85; margin: 0;">
40
+ {{ block.settings.body }}
41
+ </p>
42
+ </div>
43
+ {%- endfor -%}
44
+ </div>
45
+
46
+ {%- if section.settings.link_label != blank -%}
47
+ <div style="margin-top: 2.5rem;">
48
+ <a href="{{ section.settings.link_url | default: '/pages/standards' }}"
49
+ style="font-family: var(--font-body-family); font-weight: 700; font-size: 0.9rem; letter-spacing: 0.04em; text-transform: uppercase; text-decoration: underline; text-underline-offset: 4px; text-decoration-color: var(--runwell-blue);">
50
+ {{ section.settings.link_label }} &rarr;
51
+ </a>
52
+ </div>
53
+ {%- endif -%}
54
+ </div>
55
+ </section>
56
+
57
+ {% schema %}
58
+ {
59
+ "name": "Lushi PDP trust",
60
+ "tag": "section",
61
+ "class": "section-runwell-pdp-trust",
62
+ "settings": [
63
+ {
64
+ "type": "text",
65
+ "id": "eyebrow",
66
+ "label": "Eyebrow",
67
+ "default": "How we picked this"
68
+ },
69
+ {
70
+ "type": "text",
71
+ "id": "heading",
72
+ "label": "Heading",
73
+ "default": "Four checks, every product."
74
+ },
75
+ {
76
+ "type": "textarea",
77
+ "id": "lede",
78
+ "label": "Lede",
79
+ "default": "We do not stock everything. Every product on Lushi has to clear these four checks before it gets a page."
80
+ },
81
+ {
82
+ "type": "color",
83
+ "id": "background_color",
84
+ "label": "Background color",
85
+ "default": "#F5F0EE"
86
+ },
87
+ {
88
+ "type": "color",
89
+ "id": "text_color",
90
+ "label": "Text color",
91
+ "default": "#0B3D38"
92
+ },
93
+ {
94
+ "type": "text",
95
+ "id": "link_label",
96
+ "label": "Link label",
97
+ "default": "Read all our standards"
98
+ },
99
+ {
100
+ "type": "url",
101
+ "id": "link_url",
102
+ "label": "Link URL"
103
+ }
104
+ ],
105
+ "blocks": [
106
+ {
107
+ "type": "check",
108
+ "name": "Check",
109
+ "settings": [
110
+ {
111
+ "type": "text",
112
+ "id": "eyebrow",
113
+ "label": "Eyebrow",
114
+ "default": "Check 01"
115
+ },
116
+ {
117
+ "type": "text",
118
+ "id": "title",
119
+ "label": "Title"
120
+ },
121
+ {
122
+ "type": "textarea",
123
+ "id": "body",
124
+ "label": "Body"
125
+ }
126
+ ]
127
+ }
128
+ ],
129
+ "presets": [
130
+ {
131
+ "name": "Lushi PDP trust",
132
+ "blocks": [
133
+ { "type": "check", "settings": { "eyebrow": "Check 01", "title": "Ingredients we trust.", "body": "Whole-food forms over synthetic where the science holds. Synthetic where it works better, with a clear reason why." } },
134
+ { "type": "check", "settings": { "eyebrow": "Check 02", "title": "Doses that match research.", "body": "We check actual doses against published trials. No buzzword ingredients at one-tenth the studied dose." } },
135
+ { "type": "check", "settings": { "eyebrow": "Check 03", "title": "Brands that show their work.", "body": "Makers who name their facility, their testing, and their batches. No black-box white-label." } },
136
+ { "type": "check", "settings": { "eyebrow": "Check 04", "title": "Honest claims.", "body": "If it supports a thing, we say so. If it cures a thing, we don't list it." } }
137
+ ]
138
+ }
139
+ ]
140
+ }
141
+ {% endschema %}
@@ -0,0 +1,52 @@
1
+ # post-purchase-upsell
2
+
3
+ Native Shopify post-purchase upsell that renders on the order status page. Replaces the display layer of Reconvert, OneClickUpsell, and AfterSell. App-free; no monthly fees.
4
+
5
+ ## What it does
6
+
7
+ After a customer completes checkout, the order status page surfaces a "Complete the ritual" strip with 3 complementary products. Each card deep-links to its PDP with a discount code auto-applied via URL parameter, encouraging the second order before the customer leaves the page.
8
+
9
+ The complementary product picker uses a fallback chain:
10
+
11
+ 1. Shopify's `recommendations.json?intent=complementary` for the first ordered item
12
+ 2. Falls back to `intent=related`
13
+ 3. Falls back to a category-aware pick from `/products.json` (skincare paired with supplements, supplements paired with skincare)
14
+
15
+ ## What this is NOT
16
+
17
+ - Not a true one-click post-purchase add (those need Shopify Plus + Checkout UI Extensions)
18
+ - Not an app; this is a hand-written script + paste into Shopify admin
19
+ - Not a substitute for abandoned-cart email; pair with Shopify's built-in cart recovery emails
20
+
21
+ ## Config
22
+
23
+ | Key | Default | Notes |
24
+ |---|---|---|
25
+ | `discount_code` | `WELCOME15` | Must match a discount you create in Shopify admin |
26
+ | `discount_label` | `15% off your next order` | Shown in the lede text |
27
+ | `max_items` | `3` | Card count, capped at 6 |
28
+ | `heading` | `Complete the ritual.` | Section heading |
29
+ | `eyebrow` | `While you wait` | Eyebrow tag above heading |
30
+ | `lede` | `A few of our customers tend to add these next.` | First sentence of the lede paragraph |
31
+ | `cross_category_logic` | `true` | If true, prefer products from a different category than what was ordered |
32
+
33
+ ## Merchant admin steps (one-time)
34
+
35
+ 1. **Create the discount code.** See `admin/discount-setup.md` (rendered into `runwell-admin/post-purchase-upsell/discount-setup.md` after sync) for the exact Shopify admin clicks.
36
+ 2. **Paste the bootstrap into Order status page.** See `admin/order-status-paste.html` (rendered into `runwell-admin/post-purchase-upsell/order-status-paste.html` after sync). Settings, Checkout, Order status page, Additional scripts, paste, save.
37
+
38
+ ## Testing
39
+
40
+ 1. Place a real test order on the dev store (use a 100% off code if available)
41
+ 2. On the thank-you page, scroll past the order summary; upsell strip renders with 3 cards
42
+ 3. Click a card; confirm `?discount={code}` query param is in URL and applies at checkout
43
+
44
+ ## Replaces
45
+
46
+ - Reconvert (Display layer; full Reconvert also includes funnels we do not replicate)
47
+ - One Click Upsell (display layer)
48
+ - AfterSell (display layer)
49
+
50
+ ## Plan requirement
51
+
52
+ Works on **Shopify Basic and up**. Order status page Additional Scripts is available on every plan that has the order status page (i.e., everyone except headless-only setups).