@runwell/shopify-toolkit 0.18.0 → 0.23.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/bin/runwell-shopify +10 -1
- package/lib/init.js +13 -2
- package/lib/list.js +22 -9
- package/lib/qa-bundles.js +117 -0
- package/lib/qa.js +147 -13
- package/modules/INDEX.md +14 -5
- package/modules/bundle-builder/README.md +6 -1
- package/modules/bundle-builder/module.json +5 -1
- package/modules/cart-cross-sell/snippets/runwell-cart-xsell.liquid +16 -0
- package/modules/runwell-bundle-system/README.md +35 -0
- package/modules/runwell-bundle-system/admin-metafields.json +55 -0
- package/modules/runwell-bundle-system/assets/runwell-bundle-system.css +1107 -0
- package/modules/runwell-bundle-system/assets/runwell-bundle-system.js +444 -0
- package/modules/runwell-bundle-system/module.json +137 -0
- package/modules/runwell-bundle-system/qa/mobile-checklist.md +105 -0
- package/modules/runwell-bundle-system/qa/v1.5-mobile-qa-checklist.md +103 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-cart-xsell.liquid +59 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-collection.liquid +121 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-home-stacks.liquid +77 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-banner.liquid +50 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp-pairs-with.liquid +72 -0
- package/modules/runwell-bundle-system/sections/runwell-bundle-pdp.liquid +117 -0
- package/modules/runwell-bundle-system/settings.json +25 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-accordion.liquid +84 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-grid.liquid +72 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker-radio.liquid +77 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-picker.liquid +71 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-byob-summary.liquid +39 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-card.liquid +70 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-cross-supplier.liquid +18 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-data.liquid +75 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-fomo.liquid +32 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-free-gift.liquid +34 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-multi-product.liquid +86 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-pricing.liquid +30 -0
- package/modules/runwell-bundle-system/snippets/runwell-bundle-quantity-tiers.liquid +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-pdp.liquid.
|
|
3
|
+
Bundle PDP section. Used on templates/product.bundle.json (auto-assigned
|
|
4
|
+
to products with the runwell.bundle_mode metafield set). Routes to a
|
|
5
|
+
mode-specific snippet based on bundle_mode.
|
|
6
|
+
|
|
7
|
+
See _clients/capital-v/lushi/specs/bundle-system/spec.md sections 3, 5.
|
|
8
|
+
{%- endcomment -%}
|
|
9
|
+
|
|
10
|
+
{%- comment -%} v2: render runwell-subscription-picker here when bundle_subscription_enabled. Per BS-13 the subscription_* metafields ship in v1 inactive so v2 wires without a schema migration. {%- endcomment -%}
|
|
11
|
+
|
|
12
|
+
{% render 'runwell-bundle-data', product: product %}
|
|
13
|
+
|
|
14
|
+
<section
|
|
15
|
+
class="runwell-bundle-system runwell-bundle-system--mode-{{ bundle_mode | replace: '_', '-' }} color-{{ section.settings.color_scheme }}"
|
|
16
|
+
style="padding-top: {{ section.settings.padding_top }}px; padding-bottom: {{ section.settings.padding_bottom }}px;"
|
|
17
|
+
>
|
|
18
|
+
<div class="runwell-bundle-system__inner page-width">
|
|
19
|
+
<div class="runwell-bundle-system__gallery">
|
|
20
|
+
{%- if product.featured_image -%}
|
|
21
|
+
{{ product.featured_image | image_url: width: 800 | image_tag:
|
|
22
|
+
loading: 'eager',
|
|
23
|
+
width: product.featured_image.width,
|
|
24
|
+
height: product.featured_image.height,
|
|
25
|
+
sizes: '(min-width: 1024px) 50vw, 100vw',
|
|
26
|
+
class: 'runwell-bundle-system__gallery-image'
|
|
27
|
+
}}
|
|
28
|
+
{%- endif -%}
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="runwell-bundle-system__details">
|
|
32
|
+
{%- if section.settings.sale_prefix != blank -%}
|
|
33
|
+
<p class="runwell-bundle-system__sale-prefix">{{ section.settings.sale_prefix }}</p>
|
|
34
|
+
{%- endif -%}
|
|
35
|
+
|
|
36
|
+
<h1 class="runwell-bundle-system__title">{{ product.title }}</h1>
|
|
37
|
+
|
|
38
|
+
{%- if product.description != blank -%}
|
|
39
|
+
<div class="runwell-bundle-system__description rte">{{ product.description }}</div>
|
|
40
|
+
{%- endif -%}
|
|
41
|
+
|
|
42
|
+
{%- if bundle_mode == 'quantity_tiers' -%}
|
|
43
|
+
{% render 'runwell-bundle-quantity-tiers',
|
|
44
|
+
product: product,
|
|
45
|
+
bundle_mode: bundle_mode,
|
|
46
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
47
|
+
bundle_quantity_tiers: bundle_quantity_tiers,
|
|
48
|
+
section: section
|
|
49
|
+
%}
|
|
50
|
+
{%- elsif bundle_mode == 'multi_product' or bundle_mode == 'mix_match' -%}
|
|
51
|
+
{% render 'runwell-bundle-multi-product',
|
|
52
|
+
product: product,
|
|
53
|
+
bundle_mode: bundle_mode,
|
|
54
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
55
|
+
bundle_components: bundle_components,
|
|
56
|
+
bundle_subtotal: bundle_subtotal,
|
|
57
|
+
bundle_price: bundle_price,
|
|
58
|
+
bundle_savings_amount: bundle_savings_amount,
|
|
59
|
+
bundle_savings_pct: bundle_savings_pct
|
|
60
|
+
%}
|
|
61
|
+
{%- elsif bundle_mode == 'byob' -%}
|
|
62
|
+
{% render 'runwell-bundle-byob-picker',
|
|
63
|
+
product: product,
|
|
64
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
65
|
+
bundle_pricing_value: bundle_pricing_value,
|
|
66
|
+
bundle_byob_candidates: bundle_byob_candidates,
|
|
67
|
+
bundle_byob_min_picks: bundle_byob_min_picks,
|
|
68
|
+
bundle_byob_max_picks: bundle_byob_max_picks,
|
|
69
|
+
bundle_byob_layout: bundle_byob_layout,
|
|
70
|
+
bundle_byob_categories: bundle_byob_categories,
|
|
71
|
+
bundle_byob_required_handles: bundle_byob_required_handles
|
|
72
|
+
%}
|
|
73
|
+
{%- endif -%}
|
|
74
|
+
|
|
75
|
+
{%- if bundle_fomo_mode != 'none' and bundle_fomo_mode != blank -%}
|
|
76
|
+
{% render 'runwell-bundle-fomo',
|
|
77
|
+
bundle_fomo_mode: bundle_fomo_mode,
|
|
78
|
+
bundle_fomo_cycle_days: bundle_fomo_cycle_days,
|
|
79
|
+
bundle_fomo_stock_count: bundle_fomo_stock_count
|
|
80
|
+
%}
|
|
81
|
+
{%- endif -%}
|
|
82
|
+
|
|
83
|
+
{%- if bundle_free_gift_enabled -%}
|
|
84
|
+
{% render 'runwell-bundle-free-gift',
|
|
85
|
+
bundle_free_gift_handle: bundle_free_gift_handle
|
|
86
|
+
%}
|
|
87
|
+
{%- endif -%}
|
|
88
|
+
|
|
89
|
+
{%- if bundle_cross_supplier and bundle_supplier_count > 1 -%}
|
|
90
|
+
{% render 'runwell-bundle-cross-supplier',
|
|
91
|
+
bundle_supplier_count: bundle_supplier_count
|
|
92
|
+
%}
|
|
93
|
+
{%- endif -%}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
{{ 'runwell-bundle-system.css' | asset_url | stylesheet_tag }}
|
|
99
|
+
<script src="{{ 'runwell-bundle-system.js' | asset_url }}" defer="defer"></script>
|
|
100
|
+
|
|
101
|
+
{% schema %}
|
|
102
|
+
{
|
|
103
|
+
"name": "Bundle PDP",
|
|
104
|
+
"tag": "section",
|
|
105
|
+
"class": "section-runwell-bundle-pdp",
|
|
106
|
+
"settings": [
|
|
107
|
+
{ "type": "text", "id": "sale_prefix", "label": "Sale prefix (above title)", "default": "Limited time offer" },
|
|
108
|
+
{ "type": "color_scheme", "id": "color_scheme", "label": "Color scheme", "default": "scheme-1" },
|
|
109
|
+
{ "type": "range", "id": "padding_top", "label": "Top padding", "min": 0, "max": 100, "step": 4, "default": 40, "unit": "px" },
|
|
110
|
+
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "min": 0, "max": 100, "step": 4, "default": 40, "unit": "px" }
|
|
111
|
+
],
|
|
112
|
+
"presets": [
|
|
113
|
+
{ "name": "Bundle PDP", "category": "Runwell" }
|
|
114
|
+
],
|
|
115
|
+
"templates": ["product.bundle"]
|
|
116
|
+
}
|
|
117
|
+
{% endschema %}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Theme customizer settings patch. runwell-shopify sync appends this group to tenant config/settings_schema.json. Site-wide defaults; section-level overrides live in each section's schema.",
|
|
3
|
+
"spec_ref": "_clients/capital-v/lushi/specs/bundle-system/spec.md#10-settings-schema-for-theme-customizer",
|
|
4
|
+
"name": "Bundle system",
|
|
5
|
+
"settings": [
|
|
6
|
+
{ "type": "header", "content": "Display surfaces" },
|
|
7
|
+
{ "type": "checkbox", "id": "bundle_system__surface_1_collection_page_enabled", "label": "/bundles collection page", "default": true },
|
|
8
|
+
{ "type": "checkbox", "id": "bundle_system__surface_2_pdp_pairs_with_enabled", "label": "PDP 'Pairs well with' widget", "default": true },
|
|
9
|
+
{ "type": "checkbox", "id": "bundle_system__surface_3_home_stacks_enabled", "label": "Home page curated stacks", "default": true },
|
|
10
|
+
{ "type": "checkbox", "id": "bundle_system__surface_4_cart_drawer_xsell_enabled", "label": "Cart drawer bundle cross-sell", "default": true },
|
|
11
|
+
{ "type": "checkbox", "id": "bundle_system__surface_5_pdp_banner_enabled", "label": "PDP bundle banner", "default": true },
|
|
12
|
+
{ "type": "checkbox", "id": "bundle_system__surface_6_collection_filter_enabled", "label": "Collection page bundles filter", "default": false },
|
|
13
|
+
{ "type": "header", "content": "Default copy" },
|
|
14
|
+
{ "type": "text", "id": "bundle_system__surface_2_eyebrow", "label": "Surface 2 eyebrow", "default": "Pairs well with" },
|
|
15
|
+
{ "type": "text", "id": "bundle_system__surface_2_heading", "label": "Surface 2 heading", "default": "Complete the routine" },
|
|
16
|
+
{ "type": "text", "id": "bundle_system__surface_3_eyebrow", "label": "Surface 3 eyebrow", "default": "Editor's picks" },
|
|
17
|
+
{ "type": "text", "id": "bundle_system__surface_3_heading", "label": "Surface 3 heading", "default": "Curated stacks" },
|
|
18
|
+
{ "type": "text", "id": "bundle_system__surface_4_eyebrow", "label": "Surface 4 eyebrow", "default": "Complete the stack" },
|
|
19
|
+
{ "type": "text", "id": "bundle_system__surface_4_cta", "label": "Surface 4 CTA", "default": "Add bundle and save" },
|
|
20
|
+
{ "type": "text", "id": "bundle_system__surface_5_copy_template", "label": "Surface 5 copy template", "default": "Save {savings_pct}% when bundled" },
|
|
21
|
+
{ "type": "text", "id": "bundle_system__cross_supplier_disclosure", "label": "Cross-supplier disclosure", "default": "This bundle ships in {n} packages." },
|
|
22
|
+
{ "type": "header", "content": "Layout" },
|
|
23
|
+
{ "type": "range", "id": "bundle_system__home_strip_position", "label": "Home strip position (1-10)", "min": 1, "max": 10, "step": 1, "default": 3 }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-byob-picker-accordion.liquid.
|
|
3
|
+
Mode C, accordion layout. Candidates grouped by bundle_byob_categories.
|
|
4
|
+
One category open at a time. Total picks across all categories
|
|
5
|
+
enforced via JS.
|
|
6
|
+
|
|
7
|
+
Inputs:
|
|
8
|
+
bundle_byob_candidates, bundle_byob_min_picks, bundle_byob_max_picks,
|
|
9
|
+
bundle_byob_categories ([{label, handles}]),
|
|
10
|
+
bundle_byob_required_handles, bundle_pricing_model, bundle_pricing_value.
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
|
|
13
|
+
{%- if bundle_byob_categories and bundle_byob_categories.size > 0 -%}
|
|
14
|
+
{%- assign required_handles = '' -%}
|
|
15
|
+
{%- if bundle_byob_required_handles -%}
|
|
16
|
+
{%- capture required_handles -%}
|
|
17
|
+
{%- for r in bundle_byob_required_handles -%}{{ r.handle }},{%- endfor -%}
|
|
18
|
+
{%- endcapture -%}
|
|
19
|
+
{%- endif -%}
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class="runwell-bundle-system__byob-picker runwell-bundle-system__byob-picker--accordion"
|
|
23
|
+
data-runwell-byob-picker
|
|
24
|
+
data-byob-layout="accordion"
|
|
25
|
+
data-byob-min="{{ bundle_byob_min_picks }}"
|
|
26
|
+
data-byob-max="{{ bundle_byob_max_picks }}"
|
|
27
|
+
data-byob-pricing-model="{{ bundle_pricing_model }}"
|
|
28
|
+
data-byob-pricing-value="{{ bundle_pricing_value | json | escape }}"
|
|
29
|
+
>
|
|
30
|
+
{%- for category in bundle_byob_categories -%}
|
|
31
|
+
<details class="runwell-bundle-system__byob-accordion" {% if forloop.first %}open{% endif %}>
|
|
32
|
+
<summary class="runwell-bundle-system__byob-accordion-summary">
|
|
33
|
+
<span class="runwell-bundle-system__byob-accordion-label">{{ category.label }}</span>
|
|
34
|
+
<span class="runwell-bundle-system__byob-accordion-count" data-runwell-byob-cat-count>0</span>
|
|
35
|
+
</summary>
|
|
36
|
+
<div class="runwell-bundle-system__byob-accordion-body">
|
|
37
|
+
{%- for handle in category.handles -%}
|
|
38
|
+
{%- assign candidate = all_products[handle] -%}
|
|
39
|
+
{%- if candidate != blank -%}
|
|
40
|
+
{%- assign is_required = false -%}
|
|
41
|
+
{%- if required_handles contains handle -%}{%- assign is_required = true -%}{%- endif -%}
|
|
42
|
+
<label
|
|
43
|
+
class="runwell-bundle-system__byob-candidate{% if is_required %} runwell-bundle-system__byob-candidate--required{% endif %}"
|
|
44
|
+
data-handle="{{ candidate.handle }}"
|
|
45
|
+
data-price="{{ candidate.price }}"
|
|
46
|
+
data-variant-id="{{ candidate.selected_or_first_available_variant.id }}"
|
|
47
|
+
data-category="{{ category.label | escape }}"
|
|
48
|
+
>
|
|
49
|
+
<input
|
|
50
|
+
type="checkbox"
|
|
51
|
+
class="runwell-bundle-system__byob-input"
|
|
52
|
+
name="byob-pick"
|
|
53
|
+
value="{{ candidate.handle }}"
|
|
54
|
+
{% if is_required %}checked disabled{% endif %}
|
|
55
|
+
{% unless candidate.available %}disabled{% endunless %}
|
|
56
|
+
>
|
|
57
|
+
<span class="runwell-bundle-system__byob-candidate-media">
|
|
58
|
+
{%- if candidate.featured_image -%}
|
|
59
|
+
{{ candidate.featured_image | image_url: width: 240 | image_tag:
|
|
60
|
+
width: 120,
|
|
61
|
+
height: 120,
|
|
62
|
+
loading: 'lazy',
|
|
63
|
+
class: 'runwell-bundle-system__byob-candidate-thumb',
|
|
64
|
+
alt: candidate.title
|
|
65
|
+
}}
|
|
66
|
+
{%- endif -%}
|
|
67
|
+
</span>
|
|
68
|
+
<span class="runwell-bundle-system__byob-candidate-body">
|
|
69
|
+
<span class="runwell-bundle-system__byob-candidate-title">{{ candidate.title }}</span>
|
|
70
|
+
<span class="runwell-bundle-system__byob-candidate-price">{{ candidate.price | money }}</span>
|
|
71
|
+
{%- if is_required -%}
|
|
72
|
+
<span class="runwell-bundle-system__byob-candidate-badge">Always included</span>
|
|
73
|
+
{%- endif -%}
|
|
74
|
+
</span>
|
|
75
|
+
</label>
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
{%- endfor -%}
|
|
78
|
+
</div>
|
|
79
|
+
</details>
|
|
80
|
+
{%- endfor -%}
|
|
81
|
+
</div>
|
|
82
|
+
{%- else -%}
|
|
83
|
+
<p class="runwell-bundle-system__empty">Accordion layout requires bundle_byob_categories. Falling back to grid layout via runwell-bundle-byob-picker.liquid.</p>
|
|
84
|
+
{%- endif -%}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-byob-picker-grid.liquid.
|
|
3
|
+
Mode C, default layout. Flat checkbox grid of all candidates. Min/max
|
|
4
|
+
picks enforced via JS.
|
|
5
|
+
|
|
6
|
+
Inputs:
|
|
7
|
+
bundle_byob_candidates, bundle_byob_min_picks, bundle_byob_max_picks,
|
|
8
|
+
bundle_byob_required_handles, bundle_pricing_model, bundle_pricing_value.
|
|
9
|
+
{%- endcomment -%}
|
|
10
|
+
|
|
11
|
+
{%- if bundle_byob_candidates and bundle_byob_candidates.size > 0 -%}
|
|
12
|
+
{%- assign required_handles = '' -%}
|
|
13
|
+
{%- if bundle_byob_required_handles -%}
|
|
14
|
+
{%- capture required_handles -%}
|
|
15
|
+
{%- for r in bundle_byob_required_handles -%}{{ r.handle }},{%- endfor -%}
|
|
16
|
+
{%- endcapture -%}
|
|
17
|
+
{%- endif -%}
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
class="runwell-bundle-system__byob-picker runwell-bundle-system__byob-picker--grid"
|
|
21
|
+
data-runwell-byob-picker
|
|
22
|
+
data-byob-layout="grid"
|
|
23
|
+
data-byob-min="{{ bundle_byob_min_picks }}"
|
|
24
|
+
data-byob-max="{{ bundle_byob_max_picks }}"
|
|
25
|
+
data-byob-pricing-model="{{ bundle_pricing_model }}"
|
|
26
|
+
data-byob-pricing-value="{{ bundle_pricing_value | json | escape }}"
|
|
27
|
+
>
|
|
28
|
+
{%- for candidate in bundle_byob_candidates -%}
|
|
29
|
+
{%- assign is_required = false -%}
|
|
30
|
+
{%- if required_handles contains candidate.handle -%}{%- assign is_required = true -%}{%- endif -%}
|
|
31
|
+
{%- assign is_available = candidate.available | default: true -%}
|
|
32
|
+
|
|
33
|
+
<label
|
|
34
|
+
class="runwell-bundle-system__byob-candidate{% if is_required %} runwell-bundle-system__byob-candidate--required{% endif %}{% unless is_available %} runwell-bundle-system__byob-candidate--unavailable{% endunless %}"
|
|
35
|
+
data-handle="{{ candidate.handle }}"
|
|
36
|
+
data-price="{{ candidate.price }}"
|
|
37
|
+
data-variant-id="{{ candidate.selected_or_first_available_variant.id }}"
|
|
38
|
+
>
|
|
39
|
+
<input
|
|
40
|
+
type="checkbox"
|
|
41
|
+
class="runwell-bundle-system__byob-input"
|
|
42
|
+
name="byob-pick"
|
|
43
|
+
value="{{ candidate.handle }}"
|
|
44
|
+
{% if is_required %}checked disabled{% endif %}
|
|
45
|
+
{% unless is_available %}disabled{% endunless %}
|
|
46
|
+
>
|
|
47
|
+
<span class="runwell-bundle-system__byob-candidate-media">
|
|
48
|
+
{%- if candidate.featured_image -%}
|
|
49
|
+
{{ candidate.featured_image | image_url: width: 240 | image_tag:
|
|
50
|
+
width: 120,
|
|
51
|
+
height: 120,
|
|
52
|
+
loading: 'lazy',
|
|
53
|
+
class: 'runwell-bundle-system__byob-candidate-thumb',
|
|
54
|
+
alt: candidate.title
|
|
55
|
+
}}
|
|
56
|
+
{%- endif -%}
|
|
57
|
+
</span>
|
|
58
|
+
<span class="runwell-bundle-system__byob-candidate-body">
|
|
59
|
+
<span class="runwell-bundle-system__byob-candidate-title">{{ candidate.title }}</span>
|
|
60
|
+
<span class="runwell-bundle-system__byob-candidate-price">{{ candidate.price | money }}</span>
|
|
61
|
+
{%- if is_required -%}
|
|
62
|
+
<span class="runwell-bundle-system__byob-candidate-badge">Always included</span>
|
|
63
|
+
{%- elsif is_available == false -%}
|
|
64
|
+
<span class="runwell-bundle-system__byob-candidate-badge">Sold out</span>
|
|
65
|
+
{%- endif -%}
|
|
66
|
+
</span>
|
|
67
|
+
</label>
|
|
68
|
+
{%- endfor -%}
|
|
69
|
+
</div>
|
|
70
|
+
{%- else -%}
|
|
71
|
+
<p class="runwell-bundle-system__empty">No BYOB candidates configured for this bundle.</p>
|
|
72
|
+
{%- endif -%}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-byob-picker-radio.liquid.
|
|
3
|
+
Mode C, one-pick-per-category layout. Total picks equals category count.
|
|
4
|
+
Each category renders as a radio group; total picks across categories
|
|
5
|
+
is the category count exactly.
|
|
6
|
+
|
|
7
|
+
Inputs:
|
|
8
|
+
bundle_byob_candidates, bundle_byob_categories ([{label, handles}]),
|
|
9
|
+
bundle_byob_required_handles, bundle_pricing_model, bundle_pricing_value.
|
|
10
|
+
{%- endcomment -%}
|
|
11
|
+
|
|
12
|
+
{%- if bundle_byob_categories and bundle_byob_categories.size > 0 -%}
|
|
13
|
+
{%- assign required_handles = '' -%}
|
|
14
|
+
{%- if bundle_byob_required_handles -%}
|
|
15
|
+
{%- capture required_handles -%}
|
|
16
|
+
{%- for r in bundle_byob_required_handles -%}{{ r.handle }},{%- endfor -%}
|
|
17
|
+
{%- endcapture -%}
|
|
18
|
+
{%- endif -%}
|
|
19
|
+
{%- assign cat_count = bundle_byob_categories.size -%}
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class="runwell-bundle-system__byob-picker runwell-bundle-system__byob-picker--radio"
|
|
23
|
+
data-runwell-byob-picker
|
|
24
|
+
data-byob-layout="radio"
|
|
25
|
+
data-byob-min="{{ cat_count }}"
|
|
26
|
+
data-byob-max="{{ cat_count }}"
|
|
27
|
+
data-byob-pricing-model="{{ bundle_pricing_model }}"
|
|
28
|
+
data-byob-pricing-value="{{ bundle_pricing_value | json | escape }}"
|
|
29
|
+
>
|
|
30
|
+
{%- for category in bundle_byob_categories -%}
|
|
31
|
+
{%- assign cat_idx = forloop.index -%}
|
|
32
|
+
<fieldset class="runwell-bundle-system__byob-radio-group">
|
|
33
|
+
<legend class="runwell-bundle-system__byob-radio-legend">{{ category.label }}</legend>
|
|
34
|
+
{%- for handle in category.handles -%}
|
|
35
|
+
{%- assign candidate = all_products[handle] -%}
|
|
36
|
+
{%- if candidate != blank -%}
|
|
37
|
+
{%- assign is_required = false -%}
|
|
38
|
+
{%- if required_handles contains handle -%}{%- assign is_required = true -%}{%- endif -%}
|
|
39
|
+
<label
|
|
40
|
+
class="runwell-bundle-system__byob-candidate runwell-bundle-system__byob-candidate--radio{% if is_required %} runwell-bundle-system__byob-candidate--required{% endif %}"
|
|
41
|
+
data-handle="{{ candidate.handle }}"
|
|
42
|
+
data-price="{{ candidate.price }}"
|
|
43
|
+
data-variant-id="{{ candidate.selected_or_first_available_variant.id }}"
|
|
44
|
+
data-category="{{ category.label | escape }}"
|
|
45
|
+
>
|
|
46
|
+
<input
|
|
47
|
+
type="radio"
|
|
48
|
+
class="runwell-bundle-system__byob-input"
|
|
49
|
+
name="byob-pick-cat-{{ cat_idx }}"
|
|
50
|
+
value="{{ candidate.handle }}"
|
|
51
|
+
{% if is_required or forloop.first %}checked{% endif %}
|
|
52
|
+
{% unless candidate.available %}disabled{% endunless %}
|
|
53
|
+
>
|
|
54
|
+
<span class="runwell-bundle-system__byob-candidate-media">
|
|
55
|
+
{%- if candidate.featured_image -%}
|
|
56
|
+
{{ candidate.featured_image | image_url: width: 240 | image_tag:
|
|
57
|
+
width: 80,
|
|
58
|
+
height: 80,
|
|
59
|
+
loading: 'lazy',
|
|
60
|
+
class: 'runwell-bundle-system__byob-candidate-thumb',
|
|
61
|
+
alt: candidate.title
|
|
62
|
+
}}
|
|
63
|
+
{%- endif -%}
|
|
64
|
+
</span>
|
|
65
|
+
<span class="runwell-bundle-system__byob-candidate-body">
|
|
66
|
+
<span class="runwell-bundle-system__byob-candidate-title">{{ candidate.title }}</span>
|
|
67
|
+
<span class="runwell-bundle-system__byob-candidate-price">{{ candidate.price | money }}</span>
|
|
68
|
+
</span>
|
|
69
|
+
</label>
|
|
70
|
+
{%- endif -%}
|
|
71
|
+
{%- endfor -%}
|
|
72
|
+
</fieldset>
|
|
73
|
+
{%- endfor -%}
|
|
74
|
+
</div>
|
|
75
|
+
{%- else -%}
|
|
76
|
+
<p class="runwell-bundle-system__empty">Radio layout requires bundle_byob_categories.</p>
|
|
77
|
+
{%- endif -%}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-byob-picker.liquid.
|
|
3
|
+
Mode C router. Reads bundle_byob_layout and delegates to one of
|
|
4
|
+
three layout snippets:
|
|
5
|
+
- grid (default)
|
|
6
|
+
- accordion
|
|
7
|
+
- radio (one pick per category)
|
|
8
|
+
|
|
9
|
+
Inputs (all forwarded to the layout snippet):
|
|
10
|
+
product, bundle_pricing_model, bundle_pricing_value,
|
|
11
|
+
bundle_byob_candidates, bundle_byob_min_picks, bundle_byob_max_picks,
|
|
12
|
+
bundle_byob_layout, bundle_byob_categories, bundle_byob_required_handles.
|
|
13
|
+
|
|
14
|
+
Fallback: if layout == 'radio' but no categories are set, falls back
|
|
15
|
+
to grid and emits a console warning client side.
|
|
16
|
+
{%- endcomment -%}
|
|
17
|
+
|
|
18
|
+
{%- assign layout = bundle_byob_layout | default: 'grid' -%}
|
|
19
|
+
{%- if layout == 'radio' and bundle_byob_categories == blank -%}
|
|
20
|
+
{%- assign layout = 'grid' -%}
|
|
21
|
+
{%- endif -%}
|
|
22
|
+
|
|
23
|
+
{% render 'runwell-bundle-byob-summary',
|
|
24
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
25
|
+
bundle_pricing_value: bundle_pricing_value,
|
|
26
|
+
bundle_byob_min_picks: bundle_byob_min_picks,
|
|
27
|
+
bundle_byob_max_picks: bundle_byob_max_picks
|
|
28
|
+
%}
|
|
29
|
+
|
|
30
|
+
{%- form 'product', product, class: 'runwell-bundle-system__form runwell-bundle-system__byob-form', novalidate: 'novalidate' -%}
|
|
31
|
+
{%- if layout == 'grid' -%}
|
|
32
|
+
{% render 'runwell-bundle-byob-picker-grid',
|
|
33
|
+
bundle_byob_candidates: bundle_byob_candidates,
|
|
34
|
+
bundle_byob_min_picks: bundle_byob_min_picks,
|
|
35
|
+
bundle_byob_max_picks: bundle_byob_max_picks,
|
|
36
|
+
bundle_byob_required_handles: bundle_byob_required_handles,
|
|
37
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
38
|
+
bundle_pricing_value: bundle_pricing_value
|
|
39
|
+
%}
|
|
40
|
+
{%- elsif layout == 'accordion' -%}
|
|
41
|
+
{% render 'runwell-bundle-byob-picker-accordion',
|
|
42
|
+
bundle_byob_candidates: bundle_byob_candidates,
|
|
43
|
+
bundle_byob_min_picks: bundle_byob_min_picks,
|
|
44
|
+
bundle_byob_max_picks: bundle_byob_max_picks,
|
|
45
|
+
bundle_byob_categories: bundle_byob_categories,
|
|
46
|
+
bundle_byob_required_handles: bundle_byob_required_handles,
|
|
47
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
48
|
+
bundle_pricing_value: bundle_pricing_value
|
|
49
|
+
%}
|
|
50
|
+
{%- elsif layout == 'radio' -%}
|
|
51
|
+
{% render 'runwell-bundle-byob-picker-radio',
|
|
52
|
+
bundle_byob_candidates: bundle_byob_candidates,
|
|
53
|
+
bundle_byob_categories: bundle_byob_categories,
|
|
54
|
+
bundle_byob_required_handles: bundle_byob_required_handles,
|
|
55
|
+
bundle_pricing_model: bundle_pricing_model,
|
|
56
|
+
bundle_pricing_value: bundle_pricing_value
|
|
57
|
+
%}
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
|
|
60
|
+
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
|
|
61
|
+
|
|
62
|
+
<button
|
|
63
|
+
type="submit"
|
|
64
|
+
name="add"
|
|
65
|
+
class="runwell-bundle-system__atc button button--primary button--full-width"
|
|
66
|
+
data-runwell-byob-atc
|
|
67
|
+
disabled
|
|
68
|
+
>
|
|
69
|
+
<span class="runwell-bundle-system__atc-label">Add to cart</span>
|
|
70
|
+
</button>
|
|
71
|
+
{%- endform -%}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-byob-summary.liquid.
|
|
3
|
+
Live counter + price for Mode C BYOB. Numbers update from JS on every
|
|
4
|
+
selection change. Static fallback on first render shows zeros.
|
|
5
|
+
|
|
6
|
+
Inputs:
|
|
7
|
+
bundle_pricing_model, bundle_pricing_value,
|
|
8
|
+
bundle_byob_min_picks, bundle_byob_max_picks.
|
|
9
|
+
{%- endcomment -%}
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
class="runwell-bundle-system__byob-summary"
|
|
13
|
+
data-runwell-byob-summary
|
|
14
|
+
data-byob-min="{{ bundle_byob_min_picks }}"
|
|
15
|
+
data-byob-max="{{ bundle_byob_max_picks }}"
|
|
16
|
+
data-byob-pricing-model="{{ bundle_pricing_model }}"
|
|
17
|
+
data-byob-pricing-value="{{ bundle_pricing_value | json | escape }}"
|
|
18
|
+
>
|
|
19
|
+
<p class="runwell-bundle-system__byob-counter" data-runwell-byob-counter>
|
|
20
|
+
<span data-runwell-byob-selected>0</span>
|
|
21
|
+
<span class="runwell-bundle-system__byob-counter-of">of</span>
|
|
22
|
+
<span data-runwell-byob-target>{{ bundle_byob_max_picks }}</span>
|
|
23
|
+
<span class="runwell-bundle-system__byob-counter-label">selected</span>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<div class="runwell-bundle-system__byob-pricing">
|
|
27
|
+
<span class="runwell-bundle-system__byob-pricing-subtotal" data-runwell-byob-subtotal hidden>
|
|
28
|
+
<s data-runwell-byob-subtotal-amount>{{ 0 | money }}</s>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="runwell-bundle-system__byob-pricing-current" data-runwell-byob-total>{{ 0 | money }}</span>
|
|
31
|
+
<span class="runwell-bundle-system__byob-pricing-badge" data-runwell-byob-savings hidden>
|
|
32
|
+
Save <span data-runwell-byob-savings-amount></span>
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<p class="runwell-bundle-system__byob-helper" data-runwell-byob-helper>
|
|
37
|
+
Pick at least <strong>{{ bundle_byob_min_picks }}</strong>{% if bundle_byob_max_picks != bundle_byob_min_picks %}, up to <strong>{{ bundle_byob_max_picks }}</strong>{% endif %} to continue.
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-card.liquid.
|
|
3
|
+
Shared bundle card UI used by Surfaces 1, 2, 3, 4, 6.
|
|
4
|
+
|
|
5
|
+
Inputs:
|
|
6
|
+
bundle_product (the bundle product to render; required)
|
|
7
|
+
variant ('compact' | 'default' | 'hero'; default 'default')
|
|
8
|
+
|
|
9
|
+
Renders a self-contained card linking to /products/<bundle_handle>.
|
|
10
|
+
Loads the bundle's metafields locally via runwell-bundle-data so the
|
|
11
|
+
card has access to savings, supplier count, and component count.
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
|
|
14
|
+
{%- if bundle_product != blank -%}
|
|
15
|
+
{%- assign card_variant = variant | default: 'default' -%}
|
|
16
|
+
{%- assign product = bundle_product -%}
|
|
17
|
+
{% render 'runwell-bundle-data', product: bundle_product %}
|
|
18
|
+
{%- assign component_count = 0 -%}
|
|
19
|
+
{%- if bundle_components and bundle_components.size > 0 -%}
|
|
20
|
+
{%- assign component_count = bundle_components.size -%}
|
|
21
|
+
{%- endif -%}
|
|
22
|
+
|
|
23
|
+
<a
|
|
24
|
+
href="{{ bundle_product.url }}"
|
|
25
|
+
class="runwell-bundle-card runwell-bundle-card--{{ card_variant }}"
|
|
26
|
+
data-bundle-handle="{{ bundle_product.handle }}"
|
|
27
|
+
>
|
|
28
|
+
<div class="runwell-bundle-card__media">
|
|
29
|
+
{%- if bundle_product.featured_image -%}
|
|
30
|
+
{{ bundle_product.featured_image | image_url: width: 600 | image_tag:
|
|
31
|
+
width: 300,
|
|
32
|
+
height: 300,
|
|
33
|
+
loading: 'lazy',
|
|
34
|
+
class: 'runwell-bundle-card__image',
|
|
35
|
+
alt: bundle_product.title,
|
|
36
|
+
sizes: '(min-width: 1024px) 25vw, 50vw'
|
|
37
|
+
}}
|
|
38
|
+
{%- endif -%}
|
|
39
|
+
|
|
40
|
+
{%- if bundle_savings_pct > 0 and card_variant != 'compact' -%}
|
|
41
|
+
<span class="runwell-bundle-card__badge">Save {{ bundle_savings_pct }}%</span>
|
|
42
|
+
{%- endif -%}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="runwell-bundle-card__body">
|
|
46
|
+
<h3 class="runwell-bundle-card__title">{{ bundle_product.title }}</h3>
|
|
47
|
+
|
|
48
|
+
{%- if component_count > 0 and card_variant != 'compact' -%}
|
|
49
|
+
<p class="runwell-bundle-card__meta">
|
|
50
|
+
{{ component_count }}{% if component_count == 1 %} item{% else %} items{% endif %}
|
|
51
|
+
{%- if bundle_cross_supplier %} · ships in {{ bundle_supplier_count }} packages{%- endif -%}
|
|
52
|
+
</p>
|
|
53
|
+
{%- endif -%}
|
|
54
|
+
|
|
55
|
+
<div class="runwell-bundle-card__price-row">
|
|
56
|
+
<span class="runwell-bundle-card__price">{{ bundle_price | money }}</span>
|
|
57
|
+
{%- if bundle_subtotal > bundle_price -%}
|
|
58
|
+
<span class="runwell-bundle-card__strikethrough">{{ bundle_subtotal | money }}</span>
|
|
59
|
+
{%- endif -%}
|
|
60
|
+
{%- if bundle_savings_pct > 0 and card_variant == 'compact' -%}
|
|
61
|
+
<span class="runwell-bundle-card__badge runwell-bundle-card__badge--inline">Save {{ bundle_savings_pct }}%</span>
|
|
62
|
+
{%- endif -%}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{%- if card_variant == 'hero' or card_variant == 'default' -%}
|
|
66
|
+
<span class="runwell-bundle-card__cta button button--secondary">View bundle</span>
|
|
67
|
+
{%- endif -%}
|
|
68
|
+
</div>
|
|
69
|
+
</a>
|
|
70
|
+
{%- endif -%}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-cross-supplier.liquid.
|
|
3
|
+
Customer-facing disclosure when a bundle ships from multiple suppliers.
|
|
4
|
+
Copy template comes from the theme customizer setting
|
|
5
|
+
bundle_system__cross_supplier_disclosure (default: "This bundle ships
|
|
6
|
+
in {n} packages."), with {n} replaced by bundle_supplier_count.
|
|
7
|
+
|
|
8
|
+
Inputs:
|
|
9
|
+
bundle_supplier_count
|
|
10
|
+
{%- endcomment -%}
|
|
11
|
+
|
|
12
|
+
{%- assign template = settings.bundle_system__cross_supplier_disclosure | default: 'This bundle ships in {n} packages.' -%}
|
|
13
|
+
{%- assign disclosure = template | replace: '{n}', bundle_supplier_count -%}
|
|
14
|
+
|
|
15
|
+
<p class="runwell-bundle-system__cross-supplier" role="note">
|
|
16
|
+
<span class="runwell-bundle-system__cross-supplier-icon" aria-hidden="true">i</span>
|
|
17
|
+
<span class="runwell-bundle-system__cross-supplier-text">{{ disclosure }}</span>
|
|
18
|
+
</p>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
runwell-bundle-system: runwell-bundle-data.liquid.
|
|
3
|
+
Single read point for all runwell.bundle_* product metafields. Other
|
|
4
|
+
snippets and sections read the bundle_* Liquid variables this snippet
|
|
5
|
+
emits. Liquid has no first-class object literal so we emit flat vars
|
|
6
|
+
(bundle_mode, bundle_quantity_tiers, etc.) into the parent scope.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
{% render 'runwell-bundle-data', product: product %}
|
|
10
|
+
{{ bundle_mode }}
|
|
11
|
+
{% for tier in bundle_quantity_tiers %} ... {% endfor %}
|
|
12
|
+
|
|
13
|
+
See _clients/capital-v/lushi/specs/bundle-system/spec.md sections 2.1-2.2.
|
|
14
|
+
{%- endcomment -%}
|
|
15
|
+
|
|
16
|
+
{%- assign mf = product.metafields.runwell -%}
|
|
17
|
+
|
|
18
|
+
{%- assign bundle_mode = mf.bundle_mode.value | default: 'multi_product' -%}
|
|
19
|
+
{%- assign bundle_pricing_model = mf.bundle_pricing_model.value | default: 'fixed_price' -%}
|
|
20
|
+
{%- assign bundle_pricing_value = mf.bundle_pricing_value.value -%}
|
|
21
|
+
{%- assign bundle_components = mf.bundle_components.value -%}
|
|
22
|
+
{%- assign bundle_quantity_tiers = mf.bundle_quantity_tiers.value -%}
|
|
23
|
+
{%- assign bundle_show_in_catalog = mf.bundle_show_in_catalog.value | default: true -%}
|
|
24
|
+
{%- assign bundle_surfaces_enabled = mf.bundle_surfaces_enabled.value -%}
|
|
25
|
+
{%- assign bundle_copy = mf.bundle_copy.value -%}
|
|
26
|
+
{%- assign bundle_free_gift_enabled = mf.bundle_free_gift_enabled.value | default: false -%}
|
|
27
|
+
{%- assign bundle_free_gift_handle = mf.bundle_free_gift_handle.value | default: '' -%}
|
|
28
|
+
{%- assign bundle_fomo_mode = mf.bundle_fomo_mode.value | default: 'none' -%}
|
|
29
|
+
{%- assign bundle_fomo_cycle_days = mf.bundle_fomo_cycle_days.value | default: 30 -%}
|
|
30
|
+
{%- assign bundle_fomo_stock_count = mf.bundle_fomo_stock_count.value | default: 0 -%}
|
|
31
|
+
{%- assign bundle_cross_supplier = mf.bundle_cross_supplier.value | default: false -%}
|
|
32
|
+
{%- assign bundle_supplier_count = mf.bundle_supplier_count.value | default: 1 -%}
|
|
33
|
+
{%- assign bundle_savings_pct_pre = mf.bundle_savings_pct.value -%}
|
|
34
|
+
|
|
35
|
+
{%- comment -%} Mode C BYOB metafields (BS-201..BS-203). {%- endcomment -%}
|
|
36
|
+
{%- assign bundle_byob_candidates = mf.bundle_byob_candidates.value -%}
|
|
37
|
+
{%- assign bundle_byob_min_picks = mf.bundle_byob_min_picks.value | default: 1 -%}
|
|
38
|
+
{%- assign bundle_byob_max_picks = mf.bundle_byob_max_picks.value | default: 3 -%}
|
|
39
|
+
{%- assign bundle_byob_layout = mf.bundle_byob_layout.value | default: 'grid' -%}
|
|
40
|
+
{%- assign bundle_byob_categories = mf.bundle_byob_categories.value -%}
|
|
41
|
+
{%- assign bundle_byob_required_handles = mf.bundle_byob_required_handles.value -%}
|
|
42
|
+
|
|
43
|
+
{%- comment -%} Compute subtotal for multi_product mode by walking components. {%- endcomment -%}
|
|
44
|
+
{%- assign bundle_subtotal = 0 -%}
|
|
45
|
+
{%- if bundle_components and bundle_components.size > 0 -%}
|
|
46
|
+
{%- for c in bundle_components -%}
|
|
47
|
+
{%- assign comp_product = all_products[c.product_handle] -%}
|
|
48
|
+
{%- assign comp_qty = c.qty | default: 1 -%}
|
|
49
|
+
{%- assign comp_line = comp_product.price | times: comp_qty -%}
|
|
50
|
+
{%- assign bundle_subtotal = bundle_subtotal | plus: comp_line -%}
|
|
51
|
+
{%- endfor -%}
|
|
52
|
+
{%- endif -%}
|
|
53
|
+
|
|
54
|
+
{%- assign bundle_price = product.price -%}
|
|
55
|
+
{%- assign bundle_savings_amount = 0 -%}
|
|
56
|
+
{%- if bundle_subtotal > bundle_price -%}
|
|
57
|
+
{%- assign bundle_savings_amount = bundle_subtotal | minus: bundle_price -%}
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
|
|
60
|
+
{%- assign bundle_savings_pct = 0 -%}
|
|
61
|
+
{%- if bundle_savings_pct_pre -%}
|
|
62
|
+
{%- assign bundle_savings_pct = bundle_savings_pct_pre -%}
|
|
63
|
+
{%- elsif bundle_subtotal > 0 -%}
|
|
64
|
+
{%- assign bundle_savings_pct = bundle_savings_amount | times: 100 | divided_by: bundle_subtotal -%}
|
|
65
|
+
{%- endif -%}
|
|
66
|
+
|
|
67
|
+
{%- comment -%}
|
|
68
|
+
v2 only fields (BS-13). Parsed but unused in v1.
|
|
69
|
+
bundle_subscription_enabled, bundle_subscription_interval,
|
|
70
|
+
bundle_subscription_discount_pct, bundle_subscription_badge_copy.
|
|
71
|
+
{%- endcomment -%}
|
|
72
|
+
{%- assign bundle_subscription_enabled = mf.subscription_enabled.value | default: false -%}
|
|
73
|
+
{%- assign bundle_subscription_interval = mf.subscription_interval.value -%}
|
|
74
|
+
{%- assign bundle_subscription_discount_pct = mf.subscription_discount_pct.value -%}
|
|
75
|
+
{%- assign bundle_subscription_badge_copy = mf.subscription_badge_copy.value | default: '' -%}
|