@runwell/shopify-toolkit 0.21.0 → 0.24.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 (29) hide show
  1. package/lib/init.js +13 -2
  2. package/modules/INDEX.md +3 -3
  3. package/modules/runwell-bundle-system/admin-metafields.json +15 -3
  4. package/modules/runwell-bundle-system/assets/runwell-bundle-quantity-builder.css +383 -0
  5. package/modules/runwell-bundle-system/assets/runwell-bundle-system.css +246 -0
  6. package/modules/runwell-bundle-system/assets/runwell-bundle-system.js +359 -0
  7. package/modules/runwell-bundle-system/module.json +18 -4
  8. package/modules/runwell-bundle-system/qa/v1.5-mobile-qa-checklist.md +103 -0
  9. package/modules/runwell-bundle-system/sections/runwell-bundle-cart-xsell.liquid +2 -1
  10. package/modules/runwell-bundle-system/sections/runwell-bundle-collection.liquid +20 -8
  11. package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-banner.liquid +2 -1
  12. package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-pairs-with.liquid +2 -1
  13. package/modules/runwell-bundle-system/sections/runwell-bundle-pdp.liquid +15 -1
  14. package/modules/runwell-bundle-system/sections/runwell-bundle-quantity-builder.liquid +318 -0
  15. package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-accordion.liquid +84 -0
  16. package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-grid.liquid +72 -0
  17. package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-radio.liquid +77 -0
  18. package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker.liquid +71 -0
  19. package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-summary.liquid +39 -0
  20. package/modules/runwell-bundle-system/snippets/runwell-bundle-card.liquid +15 -2
  21. package/modules/runwell-bundle-system/snippets/runwell-bundle-cart-xsell.liquid +85 -0
  22. package/modules/runwell-bundle-system/snippets/runwell-bundle-data.liquid +8 -0
  23. package/modules/scratch-popup/README.md +88 -0
  24. package/modules/scratch-popup/SPEC.md +120 -0
  25. package/modules/scratch-popup/assets/runwell-scratch-popup.css +315 -0
  26. package/modules/scratch-popup/assets/runwell-scratch-popup.js +367 -0
  27. package/modules/scratch-popup/module.json +128 -0
  28. package/modules/scratch-popup/sections/runwell-scratch-popup.liquid +184 -0
  29. package/package.json +1 -1
@@ -0,0 +1,128 @@
1
+ {
2
+ "name": "scratch-popup",
3
+ "version": "0.1.0",
4
+ "status": "ready",
5
+ "category": "conversion",
6
+ "description": "Mystery-discount scratch popup. Email capture + foil-card scratch-to-reveal that unlocks a unique discount code per visitor. KH-validated alternative to spin-the-wheel.",
7
+ "files": {
8
+ "sections": [
9
+ "sections/runwell-scratch-popup.liquid"
10
+ ],
11
+ "assets": [
12
+ "assets/runwell-scratch-popup.js",
13
+ "assets/runwell-scratch-popup.css"
14
+ ]
15
+ },
16
+ "depends_on": {
17
+ "scopes": [
18
+ "write_discounts",
19
+ "read_discounts",
20
+ "write_customers",
21
+ "read_customers"
22
+ ]
23
+ },
24
+ "config": {
25
+ "schema": {
26
+ "enabled": {
27
+ "type": "boolean",
28
+ "default": false,
29
+ "label": "Enable scratch popup"
30
+ },
31
+ "triggers.first_visit_delay_sec": {
32
+ "type": "number",
33
+ "default": 8,
34
+ "label": "First-visit delay (seconds)"
35
+ },
36
+ "triggers.exit_intent": {
37
+ "type": "boolean",
38
+ "default": true,
39
+ "label": "Trigger on exit intent"
40
+ },
41
+ "triggers.manual_cta_selector": {
42
+ "type": "string",
43
+ "default": null,
44
+ "label": "Manual CTA selector (optional)"
45
+ },
46
+ "email_gate": {
47
+ "type": "boolean",
48
+ "default": true,
49
+ "label": "Require email before scratch"
50
+ },
51
+ "scratch_threshold_pct": {
52
+ "type": "number",
53
+ "default": 60,
54
+ "label": "Foil-erase percentage before auto-reveal"
55
+ },
56
+ "tiers": {
57
+ "type": "array",
58
+ "default": [
59
+ { "code_prefix": "MYSTERY10", "percentage": 10, "probability": 0.60 },
60
+ { "code_prefix": "MYSTERY15", "percentage": 15, "probability": 0.30 },
61
+ { "code_prefix": "MYSTERY20", "percentage": 20, "probability": 0.10 }
62
+ ],
63
+ "label": "Discount tiers + probability"
64
+ },
65
+ "expiry_days": {
66
+ "type": "number",
67
+ "default": 30,
68
+ "label": "Discount-code expiry"
69
+ },
70
+ "copy.heading": {
71
+ "type": "string",
72
+ "default": "Scratch to reveal your discount",
73
+ "label": "Modal heading"
74
+ },
75
+ "copy.subheading": {
76
+ "type": "string",
77
+ "default": "One scratch, one code, just for you.",
78
+ "label": "Modal subheading"
79
+ },
80
+ "copy.email_placeholder": {
81
+ "type": "string",
82
+ "default": "you@email.com",
83
+ "label": "Email field placeholder"
84
+ },
85
+ "copy.email_cta": {
86
+ "type": "string",
87
+ "default": "Continue",
88
+ "label": "Email CTA"
89
+ },
90
+ "copy.reveal_cta": {
91
+ "type": "string",
92
+ "default": "Shop now",
93
+ "label": "Post-reveal CTA"
94
+ },
95
+ "copy.post_reveal_subheading": {
96
+ "type": "string",
97
+ "default": "Code copied. Also sent to your inbox.",
98
+ "label": "Post-reveal subheading"
99
+ },
100
+ "design.foil_color": {
101
+ "type": "color",
102
+ "default": "#C8B89A",
103
+ "label": "Foil color"
104
+ },
105
+ "design.foil_texture_url": {
106
+ "type": "string",
107
+ "default": null,
108
+ "label": "Optional foil texture image URL"
109
+ },
110
+ "design.accent_color": {
111
+ "type": "color",
112
+ "default": "#5B7A3E",
113
+ "label": "Accent color"
114
+ },
115
+ "design.card_radius_px": {
116
+ "type": "number",
117
+ "default": 16,
118
+ "label": "Card corner radius (px)"
119
+ },
120
+ "klaviyo_list_id": {
121
+ "type": "string",
122
+ "default": null,
123
+ "label": "Klaviyo list ID (optional)"
124
+ }
125
+ }
126
+ },
127
+ "spec": "SPEC.md"
128
+ }
@@ -0,0 +1,184 @@
1
+ {%- comment -%}
2
+ Runwell scratch popup. Mystery-discount email capture with foil
3
+ scratch-to-reveal mechanic. KH-validated alternative to spin-the-wheel.
4
+
5
+ Discount codes are pre-created on the store via
6
+ `infrastructure/scripts/shopify/shopify-discount-create.sh`. Tier codes here
7
+ must match the codes created on the store side. Default codes:
8
+ MYSTERY10 (10% off, once-per-customer)
9
+ MYSTERY15 (15% off, once-per-customer)
10
+ MYSTERY20 (20% off, once-per-customer)
11
+ {%- endcomment -%}
12
+ {{ 'runwell-scratch-popup.css' | asset_url | stylesheet_tag }}
13
+
14
+ <div
15
+ class="runwell-scratch"
16
+ data-runwell-scratch
17
+ data-tier1-code="{{ section.settings.tier1_code }}"
18
+ data-tier1-pct="{{ section.settings.tier1_pct }}"
19
+ data-tier1-prob="{{ section.settings.tier1_prob }}"
20
+ data-tier2-code="{{ section.settings.tier2_code }}"
21
+ data-tier2-pct="{{ section.settings.tier2_pct }}"
22
+ data-tier2-prob="{{ section.settings.tier2_prob }}"
23
+ data-tier3-code="{{ section.settings.tier3_code }}"
24
+ data-tier3-pct="{{ section.settings.tier3_pct }}"
25
+ data-tier3-prob="{{ section.settings.tier3_prob }}"
26
+ data-trigger-delay-sec="{{ section.settings.trigger_delay_sec }}"
27
+ data-exit-intent="{{ section.settings.exit_intent }}"
28
+ data-scratch-threshold-pct="{{ section.settings.scratch_threshold_pct }}"
29
+ style="--runwell-scratch-foil: {{ section.settings.foil_color }}; --runwell-scratch-accent: {{ section.settings.accent_color }};"
30
+ aria-hidden="true"
31
+ role="dialog"
32
+ aria-labelledby="runwell-scratch-title"
33
+ >
34
+ <div class="runwell-scratch__backdrop" data-runwell-scratch-close></div>
35
+
36
+ <div class="runwell-scratch__panel">
37
+ <button class="runwell-scratch__close" type="button" data-runwell-scratch-close aria-label="Close">&times;</button>
38
+
39
+ {%- if section.settings.eyebrow != blank -%}
40
+ <p class="runwell-scratch__eyebrow">{{ section.settings.eyebrow }}</p>
41
+ {%- endif -%}
42
+
43
+ <h2 id="runwell-scratch-title" class="runwell-scratch__heading">{{ section.settings.heading }}</h2>
44
+
45
+ {%- if section.settings.subheading != blank -%}
46
+ <p class="runwell-scratch__subheading">{{ section.settings.subheading }}</p>
47
+ {%- endif -%}
48
+
49
+ {%- comment -%}
50
+ Flow (corrected 2026-05-11 after live-vendor research):
51
+ 1. SCRATCH - foil card visible immediately; visitor scratches it
52
+ 2. EMAIL - card stays as trophy; "Won X% off! Email me my code" form appears
53
+ 3. REVEALED - code shown with Copy + Shop now (auto-applies via cart URL)
54
+
55
+ The scratch is the dopamine hook that earns the email. Showing the email
56
+ field first kills the mechanic. Pattern matches Knowband, Poptin,
57
+ AppSetup, CI Scratch, Triggerbee, Winify, Superpop.
58
+ {%- endcomment -%}
59
+
60
+ {%- comment -%} STEP 1 (default visible): scratch surface {%- endcomment -%}
61
+ <div class="runwell-scratch__step runwell-scratch__step--scratch" data-runwell-scratch-step="scratch">
62
+ <div class="runwell-scratch__card">
63
+ <div class="runwell-scratch__reveal" data-runwell-scratch-reveal>
64
+ <p class="runwell-scratch__reveal-eyebrow">You won</p>
65
+ <p class="runwell-scratch__reveal-pct" data-runwell-scratch-pct>--%</p>
66
+ <p class="runwell-scratch__reveal-fineprint">off your order</p>
67
+ </div>
68
+ <canvas class="runwell-scratch__canvas" data-runwell-scratch-canvas></canvas>
69
+ <button
70
+ type="button"
71
+ class="runwell-scratch__a11y-reveal"
72
+ data-runwell-scratch-a11y-reveal
73
+ aria-label="Reveal discount (accessibility)"
74
+ >
75
+ Or tap to reveal
76
+ </button>
77
+ </div>
78
+ <p class="runwell-scratch__scratch-prompt">{{ section.settings.scratch_prompt }}</p>
79
+ </div>
80
+
81
+ {%- comment -%} STEP 2: email capture (after scratch reveals the discount amount) {%- endcomment -%}
82
+ <div class="runwell-scratch__step runwell-scratch__step--email" data-runwell-scratch-step="email" hidden>
83
+ <div class="runwell-scratch__card runwell-scratch__card--trophy" data-runwell-scratch-trophy>
84
+ <p class="runwell-scratch__reveal-eyebrow">You won</p>
85
+ <p class="runwell-scratch__reveal-pct" data-runwell-scratch-pct-trophy>--%</p>
86
+ <p class="runwell-scratch__reveal-fineprint">off your order</p>
87
+ </div>
88
+ <p class="runwell-scratch__email-prompt">{{ section.settings.email_prompt }}</p>
89
+ <div class="runwell-scratch__form" data-runwell-scratch-form>
90
+ <input type="hidden" data-runwell-scratch-tags value="newsletter, scratch-popup">
91
+ <label class="visually-hidden" for="runwell-scratch-email">Email</label>
92
+ <input
93
+ id="runwell-scratch-email"
94
+ type="email"
95
+ name="contact[email]"
96
+ placeholder="{{ section.settings.email_placeholder }}"
97
+ required
98
+ data-runwell-scratch-email
99
+ >
100
+ <button
101
+ type="button"
102
+ class="runwell-scratch__cta runwell-scratch__cta--email"
103
+ data-runwell-scratch-email-submit
104
+ >
105
+ {{ section.settings.email_cta_text }}
106
+ </button>
107
+ </div>
108
+ <p class="runwell-scratch__fineprint">No spam. One use per customer.</p>
109
+ </div>
110
+
111
+ {%- comment -%} STEP 3: code revealed + auto-apply CTA {%- endcomment -%}
112
+ <div class="runwell-scratch__step runwell-scratch__step--revealed" data-runwell-scratch-step="revealed" hidden>
113
+ <div class="runwell-scratch__card runwell-scratch__card--trophy">
114
+ <p class="runwell-scratch__reveal-eyebrow">You won</p>
115
+ <p class="runwell-scratch__reveal-pct" data-runwell-scratch-pct-final>--%</p>
116
+ <p class="runwell-scratch__reveal-fineprint">off your order</p>
117
+ </div>
118
+ <p class="runwell-scratch__revealed-heading">{{ section.settings.revealed_heading }}</p>
119
+ <div class="runwell-scratch__code-display">
120
+ <span data-runwell-scratch-code-display>------</span>
121
+ <button type="button" class="runwell-scratch__copy" data-runwell-scratch-copy aria-label="Copy code">Copy</button>
122
+ </div>
123
+ <a
124
+ href="{{ section.settings.reveal_cta_url | default: '/collections/all' }}"
125
+ class="runwell-scratch__cta runwell-scratch__cta--reveal"
126
+ data-runwell-scratch-shop
127
+ >
128
+ {{ section.settings.reveal_cta_text }}
129
+ </a>
130
+ <p class="runwell-scratch__fineprint">{{ section.settings.revealed_subheading }}</p>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <script src="{{ 'runwell-scratch-popup.js' | asset_url }}" defer="defer"></script>
136
+
137
+ {% schema %}
138
+ {
139
+ "name": "Runwell scratch popup",
140
+ "tag": "section",
141
+ "class": "section-runwell-scratch",
142
+ "settings": [
143
+ { "type": "header", "content": "Copy" },
144
+ { "type": "text", "id": "eyebrow", "label": "Eyebrow", "default": "A little surprise" },
145
+ { "type": "text", "id": "heading", "label": "Heading", "default": "Scratch to reveal your discount" },
146
+ { "type": "textarea", "id": "subheading", "label": "Subheading", "default": "Scratch the foil card below to see how much you saved." },
147
+ { "type": "text", "id": "scratch_prompt", "label": "Scratch prompt (under the card)", "default": "Drag or tap to scratch" },
148
+ { "type": "text", "id": "email_prompt", "label": "Email prompt (after scratch)", "default": "Where should we send your code?" },
149
+ { "type": "text", "id": "email_placeholder", "label": "Email placeholder", "default": "you@email.com" },
150
+ { "type": "text", "id": "email_cta_text", "label": "Email CTA", "default": "Email me my code" },
151
+ { "type": "text", "id": "revealed_heading", "label": "Revealed heading", "default": "Here is your code." },
152
+ { "type": "textarea", "id": "revealed_subheading", "label": "Revealed fineprint", "default": "Copied and sent to your inbox. One use per customer. Auto-applies at checkout." },
153
+ { "type": "text", "id": "reveal_cta_text", "label": "Revealed CTA", "default": "Shop now" },
154
+ { "type": "url", "id": "reveal_cta_url", "label": "Revealed CTA URL (discount auto-appends)" },
155
+
156
+ { "type": "header", "content": "Tier 1 (most likely)" },
157
+ { "type": "text", "id": "tier1_code", "label": "Code (must exist as Shopify discount)", "default": "MYSTERY10" },
158
+ { "type": "range", "id": "tier1_pct", "label": "Percentage off", "min": 1, "max": 50, "step": 1, "default": 10 },
159
+ { "type": "range", "id": "tier1_prob", "label": "Probability (%)", "min": 0, "max": 100, "step": 5, "default": 60 },
160
+
161
+ { "type": "header", "content": "Tier 2 (mid)" },
162
+ { "type": "text", "id": "tier2_code", "label": "Code", "default": "MYSTERY15" },
163
+ { "type": "range", "id": "tier2_pct", "label": "Percentage off", "min": 1, "max": 50, "step": 1, "default": 15 },
164
+ { "type": "range", "id": "tier2_prob", "label": "Probability (%)", "min": 0, "max": 100, "step": 5, "default": 30 },
165
+
166
+ { "type": "header", "content": "Tier 3 (rare)" },
167
+ { "type": "text", "id": "tier3_code", "label": "Code", "default": "MYSTERY20" },
168
+ { "type": "range", "id": "tier3_pct", "label": "Percentage off", "min": 1, "max": 50, "step": 1, "default": 20 },
169
+ { "type": "range", "id": "tier3_prob", "label": "Probability (%)", "min": 0, "max": 100, "step": 5, "default": 10 },
170
+
171
+ { "type": "header", "content": "Triggers" },
172
+ { "type": "range", "id": "trigger_delay_sec", "label": "First-visit delay (seconds)", "min": 0, "max": 60, "step": 1, "default": 8 },
173
+ { "type": "checkbox", "id": "exit_intent", "label": "Also trigger on exit intent", "default": true },
174
+ { "type": "range", "id": "scratch_threshold_pct", "label": "Foil-erase % before auto-reveal", "min": 30, "max": 90, "step": 5, "default": 60 },
175
+
176
+ { "type": "header", "content": "Design" },
177
+ { "type": "color", "id": "foil_color", "label": "Foil color", "default": "#C8B89A" },
178
+ { "type": "color", "id": "accent_color", "label": "Accent color", "default": "#5B7A3E" }
179
+ ],
180
+ "presets": [
181
+ { "name": "Runwell scratch popup" }
182
+ ]
183
+ }
184
+ {% endschema %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runwell/shopify-toolkit",
3
- "version": "0.21.0",
3
+ "version": "0.24.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",