@runwell/shopify-toolkit 0.5.0 → 0.7.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 (23) hide show
  1. package/lib/sync.js +12 -1
  2. package/modules/INDEX.md +35 -9
  3. package/modules/bundle-builder/sections/runwell-bundle-builder.liquid +0 -1
  4. package/modules/comparison-table/variants/feature-rows/sections/runwell-comparison-table.liquid +12 -12
  5. package/modules/editorial-hero/module.json +36 -53
  6. package/modules/editorial-hero/variants/hero-with-card/assets/runwell-editorial-hero.css +108 -0
  7. package/modules/editorial-hero/variants/hero-with-card/sections/runwell-editorial-hero.liquid +91 -0
  8. package/modules/reviews/sections/runwell-pdp-reviews.liquid +0 -1
  9. package/modules/scrolling-ticker/assets/runwell-scrolling-ticker.css +51 -0
  10. package/modules/scrolling-ticker/module.json +13 -0
  11. package/modules/scrolling-ticker/sections/runwell-scrolling-ticker.liquid +82 -0
  12. package/modules/social-proof-banner/assets/runwell-social-proof-banner.css +30 -0
  13. package/modules/social-proof-banner/module.json +13 -0
  14. package/modules/social-proof-banner/sections/runwell-social-proof-banner.liquid +66 -0
  15. package/modules/stats-bar/assets/runwell-stats-bar.css +49 -0
  16. package/modules/stats-bar/module.json +13 -0
  17. package/modules/stats-bar/sections/runwell-stats-bar.liquid +87 -0
  18. package/modules/testimonials/module.json +24 -26
  19. package/modules/testimonials/variants/carousel-ugc/assets/runwell-testimonials.css +169 -0
  20. package/modules/testimonials/variants/carousel-ugc/sections/runwell-testimonials.liquid +148 -0
  21. package/package.json +1 -1
  22. /package/modules/editorial-hero/{sections → variants/video-bg/sections}/runwell-video-hero.liquid +0 -0
  23. /package/modules/testimonials/{sections → variants/grid-quotes/sections}/runwell-testimonials.liquid +0 -0
package/lib/sync.js CHANGED
@@ -32,8 +32,19 @@ export async function sync(flags) {
32
32
  // Determine which files to copy: variant files override base files when present
33
33
  const fileGroups = (variant && variant.files) || manifest.files || {};
34
34
 
35
+ // Merge schema defaults under merchant config so {{config.X}} interpolates
36
+ // even when the client did not override every key. Schema lives in
37
+ // module.json config.schema and (for variants) variants.<name>.config_extra.
38
+ const baseSchema = (manifest.config && manifest.config.schema) || {};
39
+ const variantExtra = (variant && variant.config_extra) || {};
40
+ const defaults = {};
41
+ for (const [k, v] of Object.entries({ ...baseSchema, ...variantExtra })) {
42
+ if (v && Object.prototype.hasOwnProperty.call(v, 'default')) defaults[k] = v.default;
43
+ }
44
+ const resolvedConfig = { ...defaults, ...moduleConfig };
45
+
35
46
  const interpolationVars = {
36
- config: moduleConfig,
47
+ config: resolvedConfig,
37
48
  brand: config.brand,
38
49
  client: { name: config.client, store: config.store }
39
50
  };
package/modules/INDEX.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Auto-generated by `scripts/generate-index.mjs`. Do not edit by hand.
4
4
  Skills (shopify-storefront, shopify-cro, shopify-cli-ops, shopify-admin-browser) grep this file before writing custom Liquid.
5
5
 
6
- Total modules: 31.
6
+ Total modules: 34.
7
7
 
8
8
  ## By category
9
9
 
@@ -11,7 +11,7 @@ Total modules: 31.
11
11
  - **conversion**: cart-cross-sell, cart-freeship-progress, cart-usps, exit-intent, gift-with-purchase, post-purchase-upsell, quick-view, risk-reversal, shipping-bar, sticky-atc
12
12
  - **customer**: loyalty-tiers, wishlist
13
13
  - **pdp**: comparison-table, delivery-estimate, inventory-urgency, pdp-ingredients, pdp-journal-link, pdp-trust-checks, recently-viewed
14
- - **social-proof**: press-bar, product-badges, reviews, testimonials, trust-badges
14
+ - **social-proof**: press-bar, product-badges, reviews, scrolling-ticker, social-proof-banner, stats-bar, testimonials, trust-badges
15
15
  - **storefront**: editorial-block, editorial-hero, faq, how-it-works
16
16
 
17
17
  ## Modules
@@ -25,7 +25,7 @@ Total modules: 31.
25
25
  | `comparison-table` | pdp | app-driven comparison widgets | | eyebrow, heading, lede, background_color, text_color | feature-columns, feature-rows | (none) |
26
26
  | `delivery-estimate` | pdp | (native build) | snippets:1 | (none) | (none) | (none) |
27
27
  | `editorial-block` | storefront | (native build) | sections:1 | eyebrow, heading, lede, background_color, text_color | (none) | (none) |
28
- | `editorial-hero` | storefront | (native build) | sections:1 | eyebrow, heading, subheading, video_url, poster_image, min_height, button_label_primary, button_link_primary, button_label_secondary, button_link_secondary | (none) | (none) |
28
+ | `editorial-hero` | storefront | Dawn's image-banner / video-banner | | eyebrow, heading, subheading | video-bg, hero-with-card | (none) |
29
29
  | `exit-intent` | conversion | (native build) | sections:1 assets:1 | eyebrow, heading, lede | (none) | (none) |
30
30
  | `faq` | storefront | (native build) | sections:1 | eyebrow, heading, background_color, text_color | (none) | (none) |
31
31
  | `gift-with-purchase` | conversion | (native build) | snippets:1 assets:1 | threshold_cents, gift_handle, unlocked_message, locked_message_suffix | (none) | create-gift-product + create-gift-discount |
@@ -43,10 +43,13 @@ Total modules: 31.
43
43
  | `recently-viewed` | pdp | (native build) | sections:1 assets:1 | eyebrow, heading, background_color | (none) | (none) |
44
44
  | `reviews` | social-proof | (native build) | sections:1 | heading | (none) | (none) |
45
45
  | `risk-reversal` | conversion | (native build) | sections:1 | icon, heading, body, link_label, link_url, background_color, text_color | (none) | (none) |
46
+ | `scrolling-ticker` | social-proof | announcement-bar / scrolling-text apps | sections:1 assets:1 | (none) | (none) | (none) |
46
47
  | `shipping-bar` | conversion | (native build) | sections:1 | threshold_cents, message_below, message_qualified, message_default, background_color, text_color | (none) | (none) |
48
+ | `social-proof-banner` | social-proof | fixed-text social-proof apps | sections:1 assets:1 | (none) | (none) | (none) |
49
+ | `stats-bar` | social-proof | app-driven stat counters | sections:1 assets:1 | (none) | (none) | (none) |
47
50
  | `sticky-atc` | conversion | Sticky Add To Cart Booster Pro and similar | sections:1 assets:1 | (none) | (none) | (none) |
48
51
  | `subscriptions` | catalog | Recharge / Bold Subscriptions / Appstle for the storefront display layer; subscription management still uses Shopify's native customer account | snippets:1 | one_time_label, subscribe_label, fineprint | (none) | install-shopify-subscriptions + create-selling-plan-group + enable-customer-account-tab |
49
- | `testimonials` | social-proof | (native build) | sections:1 | eyebrow, heading, background_color, text_color | (none) | (none) |
52
+ | `testimonials` | social-proof | app-driven review widgets at the social-proof display layer | | eyebrow, heading, background_color, text_color | grid-quotes, carousel-ugc | (none) |
50
53
  | `trust-badges` | social-proof | (native build) | sections:1 | background_color, text_color | (none) | (none) |
51
54
  | `wishlist` | customer | Wishlist Plus, Wishlist King, Globo Wishlist | sections:1 snippets:1 assets:1 templates:1 | max_items | (none) | create-wishlist-page |
52
55
 
@@ -107,9 +110,10 @@ Total modules: 31.
107
110
  ### editorial-hero
108
111
 
109
112
  - Category: storefront
110
- - What: Lushi video hero module migrated from Lushi.
111
- - Files: sections:1
112
- - Config: eyebrow, heading, subheading, video_url, poster_image, min_height, button_label_primary, button_link_primary, button_label_secondary, button_link_secondary
113
+ - Replaces: Dawn's image-banner / video-banner
114
+ - What: Editorial hero section.
115
+ - Config: eyebrow, heading, subheading
116
+ - Variants: video-bg, hero-with-card
113
117
 
114
118
  ### exit-intent
115
119
 
@@ -233,6 +237,13 @@ Total modules: 31.
233
237
  - Files: sections:1
234
238
  - Config: icon, heading, body, link_label, link_url, background_color, text_color
235
239
 
240
+ ### scrolling-ticker
241
+
242
+ - Category: social-proof
243
+ - Replaces: announcement-bar / scrolling-text apps
244
+ - What: Top-of-page horizontally-scrolling promotional ticker (free shipping, sale messaging, FOMO copy).
245
+ - Files: sections:1 assets:1
246
+
236
247
  ### shipping-bar
237
248
 
238
249
  - Category: conversion
@@ -240,6 +251,20 @@ Total modules: 31.
240
251
  - Files: sections:1
241
252
  - Config: threshold_cents, message_below, message_qualified, message_default, background_color, text_color
242
253
 
254
+ ### social-proof-banner
255
+
256
+ - Category: social-proof
257
+ - Replaces: fixed-text social-proof apps
258
+ - What: Single-line social-proof banner (e.g.
259
+ - Files: sections:1 assets:1
260
+
261
+ ### stats-bar
262
+
263
+ - Category: social-proof
264
+ - Replaces: app-driven stat counters
265
+ - What: Three-up (or N-up) stats row with big number + supporting label per block.
266
+ - Files: sections:1 assets:1
267
+
243
268
  ### sticky-atc
244
269
 
245
270
  - Category: conversion
@@ -259,9 +284,10 @@ Total modules: 31.
259
284
  ### testimonials
260
285
 
261
286
  - Category: social-proof
262
- - What: Lushi testimonials module migrated from Lushi.
263
- - Files: sections:1
287
+ - Replaces: app-driven review widgets at the social-proof display layer
288
+ - What: Customer testimonials section.
264
289
  - Config: eyebrow, heading, background_color, text_color
290
+ - Variants: grid-quotes, carousel-ugc
265
291
 
266
292
  ### trust-badges
267
293
 
@@ -62,7 +62,6 @@
62
62
  {%- assign cycle_seconds = section.settings.fomo_cycle_days | times: 86400 -%}
63
63
  {%- assign cycle_position = now_epoch | modulo: cycle_seconds -%}
64
64
  {%- assign remaining_seconds = cycle_seconds | minus: cycle_position -%}
65
- {%- assign remaining_days = remaining_seconds | divided_by: 86400 -%}
66
65
  {%- assign end_epoch = now_epoch | plus: remaining_seconds -%}
67
66
  {%- assign end_date = end_epoch | date: '%B %e' -%}
68
67
 
@@ -29,20 +29,20 @@
29
29
  {%- endif -%}
30
30
 
31
31
  {%- if section.blocks.size > 0 -%}
32
- <div style="overflow-x: auto;">
33
- <table style="width: 100%; border-collapse: collapse; min-width: 520px; font-family: var(--font-body-family); font-size: 0.95rem;">
32
+ <div style="overflow-x: auto; margin: 0 auto; max-width: 880px;">
33
+ <table style="width: 100%; border-collapse: collapse; min-width: 600px; font-family: var(--font-body-family); font-size: 1.05rem;">
34
34
  <thead>
35
35
  <tr>
36
- <th style="text-align: left; padding: 0.9rem 1rem 0.9rem 0; font-family: var(--font-body-family); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.6; vertical-align: bottom; border-bottom: 2px solid currentColor;">
36
+ <th style="text-align: left; width: 55%; padding: 1.1rem 1.2rem 1.1rem 0; font-family: var(--font-body-family); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.55; vertical-align: bottom; border-bottom: 2px solid currentColor;">
37
37
  {{ section.settings.feature_label }}
38
38
  </th>
39
- <th style="text-align: center; padding: 0.9rem 1rem; vertical-align: bottom; border-bottom: 2px solid currentColor;">
40
- <div style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.15rem; line-height: 1.2;">
39
+ <th style="text-align: center; width: 22.5%; padding: 1.1rem 1rem; vertical-align: bottom; border-bottom: 2px solid currentColor;">
40
+ <div style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.4rem; line-height: 1.2;">
41
41
  {{ section.settings.brand_name }}
42
42
  </div>
43
43
  </th>
44
- <th style="text-align: center; padding: 0.9rem 1rem; vertical-align: bottom; border-bottom: 2px solid currentColor;">
45
- <div style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.15rem; line-height: 1.2; opacity: 0.7;">
44
+ <th style="text-align: center; width: 22.5%; padding: 1.1rem 1rem; vertical-align: bottom; border-bottom: 2px solid currentColor;">
45
+ <div style="font-family: var(--font-heading-family); font-style: italic; font-weight: 400; font-size: 1.4rem; line-height: 1.2; opacity: 0.7;">
46
46
  {{ section.settings.competitor_name }}
47
47
  </div>
48
48
  </th>
@@ -51,21 +51,21 @@
51
51
  <tbody>
52
52
  {%- for block in section.blocks -%}
53
53
  <tr {{ block.shopify_attributes }}>
54
- <td style="padding: 0.9rem 1rem 0.9rem 0; vertical-align: top; border-bottom: 1px solid rgba(0,0,0,0.08); font-weight: 600;">
54
+ <td style="padding: 1.05rem 1.2rem 1.05rem 0; vertical-align: middle; border-bottom: 1px solid rgba(0,0,0,0.08); font-weight: 600; line-height: 1.4;">
55
55
  {{ block.settings.feature }}
56
56
  </td>
57
- <td style="padding: 0.9rem 1rem; vertical-align: top; border-bottom: 1px solid rgba(0,0,0,0.08); text-align: center;">
57
+ <td style="padding: 1.05rem 1rem; vertical-align: middle; border-bottom: 1px solid rgba(0,0,0,0.08); text-align: center;">
58
58
  {%- if block.settings.ours == 'yes' -%}
59
- <span style="display: inline-block; min-width: 1.6rem; font-weight: 700; color: var(--runwell-rain-forrest, currentColor);">&check;</span>
59
+ <span style="display: inline-block; min-width: 1.6rem; font-size: 1.3rem; font-weight: 700; color: var(--runwell-rain-forrest, currentColor);">&check;</span>
60
60
  {%- elsif block.settings.ours == 'no' -%}
61
61
  <span style="display: inline-block; min-width: 1.6rem; opacity: 0.4;">&times;</span>
62
62
  {%- else -%}
63
63
  <span style="opacity: 0.92;">{{ block.settings.ours_text }}</span>
64
64
  {%- endif -%}
65
65
  </td>
66
- <td style="padding: 0.9rem 1rem; vertical-align: top; border-bottom: 1px solid rgba(0,0,0,0.08); text-align: center;">
66
+ <td style="padding: 1.05rem 1rem; vertical-align: middle; border-bottom: 1px solid rgba(0,0,0,0.08); text-align: center;">
67
67
  {%- if block.settings.theirs == 'yes' -%}
68
- <span style="display: inline-block; min-width: 1.6rem; font-weight: 700; opacity: 0.6;">&check;</span>
68
+ <span style="display: inline-block; min-width: 1.6rem; font-size: 1.3rem; font-weight: 700; opacity: 0.55;">&check;</span>
69
69
  {%- elsif block.settings.theirs == 'no' -%}
70
70
  <span style="display: inline-block; min-width: 1.6rem; opacity: 0.4;">&times;</span>
71
71
  {%- else -%}
@@ -1,61 +1,44 @@
1
1
  {
2
2
  "name": "editorial-hero",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "category": "storefront",
5
- "description": "Lushi video hero module migrated from Lushi.",
6
- "files": {
7
- "sections": [
8
- "sections/runwell-video-hero.liquid"
9
- ]
10
- },
5
+ "description": "Editorial hero section. Two variants: video-bg (default) renders a full-bleed background video with eyebrow + heading + subhead + dual CTA, ideal for Lushi-style editorial brands; hero-with-card renders a background image with a centered text card (Hero with Card pattern), ideal for product-led brands. Replaces Dawn's image-banner / video-banner.",
6
+ "default_variant": "video-bg",
11
7
  "config": {
12
8
  "schema": {
13
- "eyebrow": {
14
- "type": "string",
15
- "default": "Wellness, curated",
16
- "label": "Eyebrow text"
17
- },
18
- "heading": {
19
- "type": "string",
20
- "default": "Glow from within.",
21
- "label": "Headline"
22
- },
23
- "subheading": {
24
- "type": "string",
25
- "default": "Lushi is a modern wellness brand offering curated supplements, skincare, and rituals for everyday health.",
26
- "label": "Subheading"
27
- },
28
- "video_url": {
29
- "type": "string",
30
- "label": "Background video URL (mp4)"
31
- },
32
- "poster_image": {
33
- "type": "string",
34
- "label": "Poster image (and fallback)"
35
- },
36
- "min_height": {
37
- "type": "number",
38
- "default": 100,
39
- "label": "Hero min-height (vh)"
40
- },
41
- "button_label_primary": {
42
- "type": "string",
43
- "default": "Shop wellness",
44
- "label": "Primary button label"
45
- },
46
- "button_link_primary": {
47
- "type": "string",
48
- "label": "Primary button link"
49
- },
50
- "button_label_secondary": {
51
- "type": "string",
52
- "default": "Read the journal",
53
- "label": "Secondary button label"
54
- },
55
- "button_link_secondary": {
56
- "type": "string",
57
- "label": "Secondary button link"
9
+ "eyebrow": { "type": "string", "default": "Wellness, curated", "label": "Eyebrow text" },
10
+ "heading": { "type": "string", "default": "Glow from within.", "label": "Headline" },
11
+ "subheading": { "type": "string", "default": "Lushi is a modern wellness brand offering curated supplements, skincare, and rituals for everyday health.", "label": "Subheading" }
12
+ }
13
+ },
14
+ "variants": {
15
+ "video-bg": {
16
+ "description": "Full-bleed background video (mp4) with poster fallback, eyebrow + heading + subhead + primary/secondary CTAs. Section type: runwell-video-hero.",
17
+ "files": {
18
+ "sections": ["variants/video-bg/sections/runwell-video-hero.liquid"]
19
+ },
20
+ "config_extra": {
21
+ "video_url": { "type": "string", "label": "Background video URL (mp4)" },
22
+ "poster_image": { "type": "string", "label": "Poster image (and fallback)" },
23
+ "fallback_asset": { "type": "string", "label": "Bundled fallback asset filename (e.g. lushi-hero-bg.jpg)" },
24
+ "min_height": { "type": "number", "default": 100, "label": "Hero min-height (vh)" },
25
+ "button_label_primary": { "type": "string", "default": "Shop wellness", "label": "Primary button label" },
26
+ "button_link_primary": { "type": "string", "label": "Primary button link" },
27
+ "button_label_secondary": { "type": "string", "default": "Read the journal", "label": "Secondary button label" },
28
+ "button_link_secondary": { "type": "string", "label": "Secondary button link" }
29
+ }
30
+ },
31
+ "hero-with-card": {
32
+ "description": "Background image (image_picker) with a centered text card overlay. Single CTA. Image overlay opacity is configurable. Section type: runwell-editorial-hero. Ported from Lusha bespoke (guabrasha-store/sections/hero-with-card.liquid).",
33
+ "files": {
34
+ "sections": ["variants/hero-with-card/sections/runwell-editorial-hero.liquid"],
35
+ "assets": ["variants/hero-with-card/assets/runwell-editorial-hero.css"]
36
+ },
37
+ "config_extra": {
38
+ "overlay_opacity": { "type": "number", "default": 20, "label": "Image overlay opacity (%)" },
39
+ "button_label": { "type": "string", "default": "Shop now", "label": "Button label" },
40
+ "button_link": { "type": "string", "label": "Button link" }
58
41
  }
59
42
  }
60
43
  }
61
- }
44
+ }
@@ -0,0 +1,108 @@
1
+ .runwell-editorial-hero--card {
2
+ position: relative;
3
+ min-height: 85vh;
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ overflow: hidden;
8
+ }
9
+
10
+ .runwell-editorial-hero__media {
11
+ position: absolute;
12
+ inset: 0;
13
+ z-index: 0;
14
+ }
15
+
16
+ .runwell-editorial-hero__image {
17
+ width: 100%;
18
+ height: 100%;
19
+ object-fit: cover;
20
+ }
21
+
22
+ .runwell-editorial-hero__placeholder {
23
+ width: 100%;
24
+ height: 100%;
25
+ background: #E8D5C4;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ }
30
+
31
+ .runwell-editorial-hero__placeholder svg {
32
+ width: 60%;
33
+ max-width: 600px;
34
+ opacity: 0.3;
35
+ }
36
+
37
+ .runwell-editorial-hero__overlay {
38
+ position: absolute;
39
+ inset: 0;
40
+ background: #000;
41
+ }
42
+
43
+ .runwell-editorial-hero__content-wrapper {
44
+ position: relative;
45
+ z-index: 1;
46
+ padding: 2rem;
47
+ width: 100%;
48
+ display: flex;
49
+ justify-content: center;
50
+ }
51
+
52
+ .runwell-editorial-hero__card {
53
+ background: rgba(255, 255, 255, 0.95);
54
+ backdrop-filter: blur(12px);
55
+ border-radius: 20px;
56
+ padding: 3.6rem 4rem;
57
+ max-width: 560px;
58
+ text-align: center;
59
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.08);
60
+ }
61
+
62
+ .runwell-editorial-hero__heading {
63
+ margin-bottom: 1.2rem;
64
+ color: #2A2622;
65
+ }
66
+
67
+ .runwell-editorial-hero__subheading {
68
+ font-size: 1.5rem;
69
+ color: #6B6259;
70
+ line-height: 1.6;
71
+ margin-bottom: 2rem;
72
+ }
73
+
74
+ .runwell-editorial-hero__button {
75
+ min-width: 200px;
76
+ font-size: 1.5rem;
77
+ padding: 1.4rem 3.2rem;
78
+ background: #2A2622;
79
+ color: #fff;
80
+ border: none;
81
+ border-radius: 8px;
82
+ font-weight: 600;
83
+ letter-spacing: 0.02em;
84
+ cursor: pointer;
85
+ text-decoration: none;
86
+ display: inline-block;
87
+ transition: background 0.2s;
88
+ }
89
+
90
+ .runwell-editorial-hero__button:hover {
91
+ background: #3F5B4C;
92
+ }
93
+
94
+ @media screen and (max-width: 749px) {
95
+ .runwell-editorial-hero--card {
96
+ min-height: 70vh;
97
+ }
98
+
99
+ .runwell-editorial-hero__card {
100
+ padding: 2.4rem 2rem;
101
+ margin: 0 1rem;
102
+ border-radius: 16px;
103
+ }
104
+
105
+ .runwell-editorial-hero__button {
106
+ width: 100%;
107
+ }
108
+ }
@@ -0,0 +1,91 @@
1
+ {{ 'runwell-editorial-hero.css' | asset_url | stylesheet_tag }}
2
+
3
+ <div class="runwell-editorial-hero runwell-editorial-hero--card color-{{ section.settings.color_scheme }}">
4
+ <div class="runwell-editorial-hero__media">
5
+ {% if section.settings.image != blank %}
6
+ {{ section.settings.image | image_url: width: 1920 | image_tag: loading: 'eager', class: 'runwell-editorial-hero__image', sizes: '100vw' }}
7
+ {% else %}
8
+ <div class="runwell-editorial-hero__placeholder">{{ 'lifestyle-1' | placeholder_svg_tag }}</div>
9
+ {% endif %}
10
+ <div class="runwell-editorial-hero__overlay" style="opacity: {{ section.settings.overlay_opacity | divided_by: 100.0 }};"></div>
11
+ </div>
12
+
13
+ <div class="runwell-editorial-hero__content-wrapper">
14
+ <div class="runwell-editorial-hero__card color-scheme-1">
15
+ {% if section.settings.heading != blank %}
16
+ <h1 class="runwell-editorial-hero__heading {{ section.settings.heading_size }}">{{ section.settings.heading }}</h1>
17
+ {% endif %}
18
+ {% if section.settings.subheading != blank %}
19
+ <p class="runwell-editorial-hero__subheading">{{ section.settings.subheading }}</p>
20
+ {% endif %}
21
+ {% if section.settings.button_label != blank %}
22
+ <a href="{{ section.settings.button_link }}" class="button runwell-editorial-hero__button">{{ section.settings.button_label }}</a>
23
+ {% endif %}
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ {% schema %}
29
+ {
30
+ "name": "Editorial hero (card)",
31
+ "tag": "section",
32
+ "class": "section-runwell-editorial-hero",
33
+ "settings": [
34
+ {
35
+ "type": "image_picker",
36
+ "id": "image",
37
+ "label": "Background image"
38
+ },
39
+ {
40
+ "type": "range",
41
+ "id": "overlay_opacity",
42
+ "label": "Image overlay opacity",
43
+ "min": 0, "max": 80, "step": 5, "default": 20, "unit": "%"
44
+ },
45
+ {
46
+ "type": "text",
47
+ "id": "heading",
48
+ "label": "Heading",
49
+ "default": "A Moment of Care, Every Day"
50
+ },
51
+ {
52
+ "type": "select",
53
+ "id": "heading_size",
54
+ "label": "Heading size",
55
+ "options": [
56
+ { "value": "h1", "label": "Large" },
57
+ { "value": "h0", "label": "Extra large" }
58
+ ],
59
+ "default": "h1"
60
+ },
61
+ {
62
+ "type": "text",
63
+ "id": "subheading",
64
+ "label": "Subheading",
65
+ "default": "Discover gentle sculpting tools designed for everyday comfort; a thoughtful addition to your morning ritual."
66
+ },
67
+ {
68
+ "type": "text",
69
+ "id": "button_label",
70
+ "label": "Button label",
71
+ "default": "Shop Now"
72
+ },
73
+ {
74
+ "type": "url",
75
+ "id": "button_link",
76
+ "label": "Button link"
77
+ },
78
+ {
79
+ "type": "color_scheme",
80
+ "id": "color_scheme",
81
+ "label": "Color scheme",
82
+ "default": "scheme-1"
83
+ }
84
+ ],
85
+ "presets": [
86
+ {
87
+ "name": "Editorial hero (card)"
88
+ }
89
+ ]
90
+ }
91
+ {% endschema %}
@@ -16,7 +16,6 @@
16
16
  {%- for r in reviews_json -%}
17
17
  {%- assign sum = sum | plus: r.rating -%}
18
18
  {%- endfor -%}
19
- {%- assign avg = sum | divided_by: reviews_json.size | times: 1.0 -%}
20
19
  {%- assign avg_int = sum | divided_by: reviews_json.size -%}
21
20
  <div class="runwell-reviews__summary">
22
21
  <span class="runwell-reviews__stars" aria-label="{{ avg_int }} out of 5">
@@ -0,0 +1,51 @@
1
+ .runwell-scrolling-ticker {
2
+ overflow: hidden;
3
+ white-space: nowrap;
4
+ padding: 12px 0;
5
+ font-size: 1.4rem;
6
+ font-weight: 600;
7
+ letter-spacing: 0.08em;
8
+ text-transform: uppercase;
9
+ }
10
+
11
+ .scrolling-ticker__inner {
12
+ display: flex;
13
+ width: max-content;
14
+ animation: ticker-scroll var(--ticker-speed, 25s) linear infinite;
15
+ }
16
+
17
+ .scrolling-ticker__content {
18
+ display: flex;
19
+ align-items: center;
20
+ flex-shrink: 0;
21
+ }
22
+
23
+ .scrolling-ticker__item {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: 6px;
27
+ }
28
+
29
+ .scrolling-ticker__separator {
30
+ opacity: 0.6;
31
+ }
32
+
33
+ .scrolling-ticker__icon {
34
+ font-size: 1.2em;
35
+ }
36
+
37
+ @keyframes ticker-scroll {
38
+ 0% { transform: translateX(0); }
39
+ 100% { transform: translateX(-25%); }
40
+ }
41
+
42
+ .scrolling-ticker:hover .scrolling-ticker__inner {
43
+ animation-play-state: paused;
44
+ }
45
+
46
+ @media screen and (max-width: 749px) {
47
+ .runwell-scrolling-ticker {
48
+ font-size: 1.2rem;
49
+ padding: 10px 0;
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "scrolling-ticker",
3
+ "version": "0.1.0",
4
+ "category": "social-proof",
5
+ "description": "Top-of-page horizontally-scrolling promotional ticker (free shipping, sale messaging, FOMO copy). One block per text item. Replaces announcement-bar / scrolling-text apps. Promoted from Lusha bespoke (guabrasha-store/sections/scrolling-ticker.liquid).",
6
+ "files": {
7
+ "sections": ["sections/runwell-scrolling-ticker.liquid"],
8
+ "assets": ["assets/runwell-scrolling-ticker.css"]
9
+ },
10
+ "config": {
11
+ "schema": {}
12
+ }
13
+ }
@@ -0,0 +1,82 @@
1
+ {{ 'runwell-scrolling-ticker.css' | asset_url | stylesheet_tag }}
2
+
3
+ <div class="runwell-scrolling-ticker color-{{ section.settings.color_scheme }}" role="marquee">
4
+ <div class="scrolling-ticker__inner">
5
+ {% for i in (1..4) %}
6
+ <div class="scrolling-ticker__content" {% if forloop.index > 1 %}aria-hidden="true"{% endif %}>
7
+ {% for block in section.blocks %}
8
+ <span class="scrolling-ticker__item">
9
+ {% if block.settings.icon != 'none' %}
10
+ <span class="scrolling-ticker__icon">{{ block.settings.icon }}</span>
11
+ {% endif %}
12
+ {{ block.settings.text }}
13
+ </span>
14
+ <span class="scrolling-ticker__separator">&nbsp;&nbsp;{{ section.settings.separator }}&nbsp;&nbsp;</span>
15
+ {% endfor %}
16
+ </div>
17
+ {% endfor %}
18
+ </div>
19
+ </div>
20
+
21
+ {% schema %}
22
+ {
23
+ "name": "Scrolling Ticker",
24
+ "tag": "section",
25
+ "class": "section-runwell-scrolling-ticker",
26
+ "settings": [
27
+ {
28
+ "type": "color_scheme",
29
+ "id": "color_scheme",
30
+ "label": "Color scheme",
31
+ "default": "scheme-3"
32
+ },
33
+ {
34
+ "type": "text",
35
+ "id": "separator",
36
+ "label": "Separator",
37
+ "default": "\u2728"
38
+ },
39
+ {
40
+ "type": "range",
41
+ "id": "speed",
42
+ "label": "Scroll speed (seconds)",
43
+ "min": 10,
44
+ "max": 60,
45
+ "step": 5,
46
+ "default": 25,
47
+ "unit": "s"
48
+ }
49
+ ],
50
+ "blocks": [
51
+ {
52
+ "type": "text",
53
+ "name": "Text item",
54
+ "settings": [
55
+ {
56
+ "type": "text",
57
+ "id": "text",
58
+ "label": "Text",
59
+ "default": "Free shipping on orders over $50"
60
+ },
61
+ {
62
+ "type": "text",
63
+ "id": "icon",
64
+ "label": "Icon (emoji)",
65
+ "default": "none"
66
+ }
67
+ ]
68
+ }
69
+ ],
70
+ "presets": [
71
+ {
72
+ "name": "Scrolling Ticker",
73
+ "blocks": [
74
+ { "type": "text", "settings": { "text": "FREE SHIPPING on orders over $50", "icon": "\u2728" } },
75
+ { "type": "text", "settings": { "text": "Your 2-minute morning sculpting ritual", "icon": "\u2728" } },
76
+ { "type": "text", "settings": { "text": "Inspired by centuries of facial sculpting wisdom", "icon": "\u2728" } },
77
+ { "type": "text", "settings": { "text": "90-Day Satisfaction Guarantee", "icon": "\u2728" } }
78
+ ]
79
+ }
80
+ ]
81
+ }
82
+ {% endschema %}
@@ -0,0 +1,30 @@
1
+ .social-proof-banner__content {
2
+ display: flex;
3
+ align-items: baseline;
4
+ justify-content: center;
5
+ gap: 0.6rem;
6
+ text-align: center;
7
+ }
8
+
9
+ .social-proof-banner__count {
10
+ font-family: var(--font-heading-family);
11
+ font-size: 3.6rem;
12
+ font-weight: 600;
13
+ line-height: 1;
14
+ }
15
+
16
+ .social-proof-banner__text {
17
+ font-size: 1.6rem;
18
+ opacity: 0.8;
19
+ }
20
+
21
+ @media screen and (max-width: 749px) {
22
+ .social-proof-banner__content {
23
+ flex-direction: column;
24
+ gap: 0.2rem;
25
+ }
26
+
27
+ .social-proof-banner__count {
28
+ font-size: 2.8rem;
29
+ }
30
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "social-proof-banner",
3
+ "version": "0.1.0",
4
+ "category": "social-proof",
5
+ "description": "Single-line social-proof banner (e.g. 'Loved by 12,400+ customers'). Compact form for tucking under hero or above CTA. Replaces fixed-text social-proof apps. Promoted from Lusha bespoke (guabrasha-store/sections/social-proof-banner.liquid).",
6
+ "files": {
7
+ "sections": ["sections/runwell-social-proof-banner.liquid"],
8
+ "assets": ["assets/runwell-social-proof-banner.css"]
9
+ },
10
+ "config": {
11
+ "schema": {}
12
+ }
13
+ }
@@ -0,0 +1,66 @@
1
+ {{ 'runwell-social-proof-banner.css' | asset_url | stylesheet_tag }}
2
+
3
+ <div class="runwell-social-proof-banner color-{{ section.settings.color_scheme }} section-{{ section.id }}-padding">
4
+ <div class="page-width">
5
+ <div class="social-proof-banner__content">
6
+ {% if section.settings.count != blank %}
7
+ <span class="social-proof-banner__count">{{ section.settings.count }}</span>
8
+ {% endif %}
9
+ {% if section.settings.text != blank %}
10
+ <span class="social-proof-banner__text">{{ section.settings.text }}</span>
11
+ {% endif %}
12
+ </div>
13
+ </div>
14
+ </div>
15
+
16
+ {% schema %}
17
+ {
18
+ "name": "Social Proof Banner",
19
+ "tag": "section",
20
+ "class": "section-runwell-social-proof-banner",
21
+ "settings": [
22
+ {
23
+ "type": "text",
24
+ "id": "count",
25
+ "label": "Count",
26
+ "default": "2,400+"
27
+ },
28
+ {
29
+ "type": "text",
30
+ "id": "text",
31
+ "label": "Text",
32
+ "default": "Happy Customers and Counting"
33
+ },
34
+ {
35
+ "type": "color_scheme",
36
+ "id": "color_scheme",
37
+ "label": "Color scheme",
38
+ "default": "scheme-2"
39
+ },
40
+ {
41
+ "type": "range",
42
+ "id": "padding_top",
43
+ "label": "Top padding",
44
+ "min": 0, "max": 60, "step": 4, "default": 24, "unit": "px"
45
+ },
46
+ {
47
+ "type": "range",
48
+ "id": "padding_bottom",
49
+ "label": "Bottom padding",
50
+ "min": 0, "max": 60, "step": 4, "default": 24, "unit": "px"
51
+ }
52
+ ],
53
+ "presets": [
54
+ {
55
+ "name": "Social Proof Banner"
56
+ }
57
+ ]
58
+ }
59
+ {% endschema %}
60
+
61
+ <style>
62
+ .section-{{ section.id }}-padding {
63
+ padding-top: {{ section.settings.padding_top }}px;
64
+ padding-bottom: {{ section.settings.padding_bottom }}px;
65
+ }
66
+ </style>
@@ -0,0 +1,49 @@
1
+ .stats-bar__grid {
2
+ display: grid;
3
+ gap: 2rem;
4
+ text-align: center;
5
+ }
6
+
7
+ .stats-bar__grid--3-col {
8
+ grid-template-columns: repeat(3, 1fr);
9
+ }
10
+
11
+ .stats-bar__grid--2-col {
12
+ grid-template-columns: repeat(2, 1fr);
13
+ }
14
+
15
+ .stats-bar__grid--4-col {
16
+ grid-template-columns: repeat(4, 1fr);
17
+ }
18
+
19
+ .stats-bar__item {
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ gap: 0.8rem;
24
+ padding: 2rem 1rem;
25
+ }
26
+
27
+ .stats-bar__number {
28
+ font-size: 4.8rem;
29
+ line-height: 1;
30
+ font-weight: 600;
31
+ }
32
+
33
+ .stats-bar__label {
34
+ font-size: 1.4rem;
35
+ opacity: 0.85;
36
+ max-width: 24ch;
37
+ line-height: 1.4;
38
+ }
39
+
40
+ @media screen and (max-width: 749px) {
41
+ .stats-bar__grid--3-col,
42
+ .stats-bar__grid--4-col {
43
+ grid-template-columns: 1fr;
44
+ }
45
+
46
+ .stats-bar__number {
47
+ font-size: 3.6rem;
48
+ }
49
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "stats-bar",
3
+ "version": "0.1.0",
4
+ "category": "social-proof",
5
+ "description": "Three-up (or N-up) stats row with big number + supporting label per block. Editorial social proof: clinical results, customer surveys, NPS, etc. Replaces app-driven stat counters. Promoted from Lusha bespoke (guabrasha-store/sections/stats-bar.liquid).",
6
+ "files": {
7
+ "sections": ["sections/runwell-stats-bar.liquid"],
8
+ "assets": ["assets/runwell-stats-bar.css"]
9
+ },
10
+ "config": {
11
+ "schema": {}
12
+ }
13
+ }
@@ -0,0 +1,87 @@
1
+ {{ 'runwell-stats-bar.css' | asset_url | stylesheet_tag }}
2
+
3
+ <div class="runwell-stats-bar color-{{ section.settings.color_scheme }} section-{{ section.id }}-padding">
4
+ <div class="page-width">
5
+ <div class="stats-bar__grid stats-bar__grid--{{ section.blocks.size }}-col">
6
+ {% for block in section.blocks %}
7
+ <div class="stats-bar__item" {{ block.shopify_attributes }}>
8
+ <span class="stats-bar__number h0">{{ block.settings.number }}</span>
9
+ <span class="stats-bar__label">{{ block.settings.label }}</span>
10
+ </div>
11
+ {% endfor %}
12
+ </div>
13
+ </div>
14
+ </div>
15
+
16
+ {% schema %}
17
+ {
18
+ "name": "Stats Bar",
19
+ "tag": "section",
20
+ "class": "section-runwell-stats-bar",
21
+ "settings": [
22
+ {
23
+ "type": "color_scheme",
24
+ "id": "color_scheme",
25
+ "label": "Color scheme",
26
+ "default": "scheme-3"
27
+ },
28
+ {
29
+ "type": "range",
30
+ "id": "padding_top",
31
+ "label": "Top padding",
32
+ "min": 0,
33
+ "max": 100,
34
+ "step": 4,
35
+ "default": 40,
36
+ "unit": "px"
37
+ },
38
+ {
39
+ "type": "range",
40
+ "id": "padding_bottom",
41
+ "label": "Bottom padding",
42
+ "min": 0,
43
+ "max": 100,
44
+ "step": 4,
45
+ "default": 40,
46
+ "unit": "px"
47
+ }
48
+ ],
49
+ "blocks": [
50
+ {
51
+ "type": "stat",
52
+ "name": "Statistic",
53
+ "settings": [
54
+ {
55
+ "type": "text",
56
+ "id": "number",
57
+ "label": "Number",
58
+ "default": "89%"
59
+ },
60
+ {
61
+ "type": "text",
62
+ "id": "label",
63
+ "label": "Label",
64
+ "default": "saw reduced puffiness in 7 days"
65
+ }
66
+ ]
67
+ }
68
+ ],
69
+ "presets": [
70
+ {
71
+ "name": "Stats Bar",
72
+ "blocks": [
73
+ { "type": "stat", "settings": { "number": "89%", "label": "saw reduced puffiness in 7 days" } },
74
+ { "type": "stat", "settings": { "number": "94%", "label": "noticed a more sculpted jawline in 4 weeks" } },
75
+ { "type": "stat", "settings": { "number": "91%", "label": "said their skin felt softer after first use" } }
76
+ ]
77
+ }
78
+ ]
79
+ }
80
+ {% endschema %}
81
+
82
+ <style>
83
+ .section-{{ section.id }}-padding {
84
+ padding-top: {{ section.settings.padding_top }}px;
85
+ padding-bottom: {{ section.settings.padding_bottom }}px;
86
+ }
87
+ </style>
@@ -1,35 +1,33 @@
1
1
  {
2
2
  "name": "testimonials",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "category": "social-proof",
5
- "description": "Lushi testimonials module migrated from Lushi.",
6
- "files": {
7
- "sections": [
8
- "sections/runwell-testimonials.liquid"
9
- ]
10
- },
5
+ "description": "Customer testimonials section. Two visual treatments: grid-quotes (default) renders 3 to 6 quote cards in a responsive grid, ideal for editorial brands; carousel-ugc renders a coverflow-style image carousel of UGC photos with arrows + click-to-jump, ideal for lifestyle/social-led brands. Replaces app-driven review widgets at the social-proof display layer.",
6
+ "default_variant": "grid-quotes",
11
7
  "config": {
12
8
  "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"
9
+ "eyebrow": { "type": "string", "default": "From the inbox", "label": "Eyebrow" },
10
+ "heading": { "type": "string", "default": "What customers are writing in.", "label": "Heading" },
11
+ "background_color": { "type": "string", "default": "#F5F0EE", "label": "Background" },
12
+ "text_color": { "type": "string", "default": "#0B3D38", "label": "Text" }
13
+ }
14
+ },
15
+ "variants": {
16
+ "grid-quotes": {
17
+ "description": "3 to 6 customer quote cards in a responsive grid. Each block is a quote with author + context + 0-5 star rating. Best for editorial / wellness brands.",
18
+ "files": {
19
+ "sections": ["variants/grid-quotes/sections/runwell-testimonials.liquid"]
20
+ }
21
+ },
22
+ "carousel-ugc": {
23
+ "description": "Coverflow-style UGC image carousel with arrows + click-to-jump. Each block is an image (image_picker or asset filename fallback) with alt text. Best for lifestyle / social-led brands. Ported from Lusha bespoke (guabrasha-store/sections/ugc-carousel.liquid).",
24
+ "files": {
25
+ "sections": ["variants/carousel-ugc/sections/runwell-testimonials.liquid"],
26
+ "assets": ["variants/carousel-ugc/assets/runwell-testimonials.css"]
27
27
  },
28
- "text_color": {
29
- "type": "string",
30
- "default": "#0B3D38",
31
- "label": "Text"
28
+ "config_extra": {
29
+ "subheading": { "type": "string", "default": "See how they sculpt", "label": "Subheading" }
32
30
  }
33
31
  }
34
32
  }
35
- }
33
+ }
@@ -0,0 +1,169 @@
1
+ /* UGC Carousel: Coverflow-style image showcase */
2
+ .runwell-testimonials--carousel {
3
+ padding: 40px 0;
4
+ background: var(--gradient-background);
5
+ }
6
+
7
+ .runwell-testimonials__heading {
8
+ font-family: var(--font-heading-family);
9
+ font-size: 2.4rem;
10
+ text-align: center;
11
+ margin-bottom: 8px;
12
+ color: rgba(var(--color-foreground), 1);
13
+ }
14
+
15
+ .runwell-testimonials__subheading {
16
+ font-size: 1.4rem;
17
+ text-align: center;
18
+ color: rgba(var(--color-foreground), 0.65);
19
+ margin-bottom: 32px;
20
+ }
21
+
22
+ .runwell-testimonials__viewport {
23
+ position: relative;
24
+ width: 100%;
25
+ max-width: 900px;
26
+ height: 420px;
27
+ margin: 0 auto;
28
+ overflow: hidden;
29
+ }
30
+
31
+ .runwell-testimonials__track {
32
+ position: relative;
33
+ width: 100%;
34
+ height: 100%;
35
+ }
36
+
37
+ .runwell-testimonials__item {
38
+ position: absolute;
39
+ top: 50%;
40
+ left: 50%;
41
+ width: 240px;
42
+ height: 340px;
43
+ border-radius: 12px;
44
+ overflow: hidden;
45
+ transition: transform 0.5s ease, filter 0.5s ease, opacity 0.5s ease, box-shadow 0.5s ease;
46
+ cursor: pointer;
47
+ }
48
+
49
+ .runwell-testimonials__item img {
50
+ width: 100%;
51
+ height: 100%;
52
+ object-fit: cover;
53
+ display: block;
54
+ }
55
+
56
+ /* Position states */
57
+ .runwell-testimonials__item[data-pos="0"] {
58
+ transform: translate(-50%, -50%) scale(1.15);
59
+ filter: blur(0) brightness(1);
60
+ opacity: 1;
61
+ z-index: 5;
62
+ box-shadow: 0 20px 60px rgba(42, 38, 34, 0.25);
63
+ }
64
+
65
+ .runwell-testimonials__item[data-pos="1"] {
66
+ transform: translate(calc(-50% + 260px), -50%) scale(0.85);
67
+ filter: blur(1.5px) brightness(0.75);
68
+ opacity: 0.8;
69
+ z-index: 3;
70
+ }
71
+
72
+ .runwell-testimonials__item[data-pos="-1"] {
73
+ transform: translate(calc(-50% - 260px), -50%) scale(0.85);
74
+ filter: blur(1.5px) brightness(0.75);
75
+ opacity: 0.8;
76
+ z-index: 3;
77
+ }
78
+
79
+ .runwell-testimonials__item[data-pos="2"] {
80
+ transform: translate(calc(-50% + 440px), -50%) scale(0.65);
81
+ filter: blur(3px) brightness(0.6);
82
+ opacity: 0.5;
83
+ z-index: 1;
84
+ }
85
+
86
+ .runwell-testimonials__item[data-pos="-2"] {
87
+ transform: translate(calc(-50% - 440px), -50%) scale(0.65);
88
+ filter: blur(3px) brightness(0.6);
89
+ opacity: 0.5;
90
+ z-index: 1;
91
+ }
92
+
93
+ .runwell-testimonials__item[data-pos="hidden"] {
94
+ transform: translate(-50%, -50%) scale(0.4);
95
+ filter: blur(6px);
96
+ opacity: 0;
97
+ z-index: 0;
98
+ pointer-events: none;
99
+ }
100
+
101
+ /* Navigation arrows */
102
+ .runwell-testimonials__arrow {
103
+ position: absolute;
104
+ top: 50%;
105
+ transform: translateY(-50%);
106
+ z-index: 10;
107
+ width: 44px;
108
+ height: 44px;
109
+ border-radius: 50%;
110
+ border: 2px solid rgba(var(--color-foreground), 0.12);
111
+ background: rgba(var(--color-background), 0.85);
112
+ backdrop-filter: blur(8px);
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ font-size: 20px;
118
+ color: rgba(var(--color-foreground), 0.8);
119
+ transition: border-color 0.2s, background 0.2s;
120
+ }
121
+
122
+ .runwell-testimonials__arrow:hover {
123
+ border-color: rgba(var(--color-foreground), 0.3);
124
+ background: rgba(var(--color-background), 1);
125
+ }
126
+
127
+ .runwell-testimonials__arrow--left {
128
+ left: 12px;
129
+ }
130
+
131
+ .runwell-testimonials__arrow--right {
132
+ right: 12px;
133
+ }
134
+
135
+ /* Mobile responsive */
136
+ @media screen and (max-width: 749px) {
137
+ .runwell-testimonials__viewport {
138
+ height: 300px;
139
+ }
140
+
141
+ .runwell-testimonials__item {
142
+ width: 160px;
143
+ height: 230px;
144
+ }
145
+
146
+ .runwell-testimonials__item[data-pos="1"] {
147
+ transform: translate(calc(-50% + 150px), -50%) scale(0.8);
148
+ }
149
+
150
+ .runwell-testimonials__item[data-pos="-1"] {
151
+ transform: translate(calc(-50% - 150px), -50%) scale(0.8);
152
+ }
153
+
154
+ .runwell-testimonials__item[data-pos="2"],
155
+ .runwell-testimonials__item[data-pos="-2"] {
156
+ opacity: 0;
157
+ pointer-events: none;
158
+ }
159
+
160
+ .runwell-testimonials__arrow {
161
+ width: 36px;
162
+ height: 36px;
163
+ font-size: 16px;
164
+ }
165
+
166
+ .runwell-testimonials__heading {
167
+ font-size: 2rem;
168
+ }
169
+ }
@@ -0,0 +1,148 @@
1
+ {{ 'runwell-testimonials.css' | asset_url | stylesheet_tag }}
2
+
3
+ <div class="runwell-testimonials runwell-testimonials--carousel color-{{ section.settings.color_scheme }}">
4
+ <div class="page-width">
5
+ {%- if section.settings.heading != blank -%}
6
+ <h2 class="runwell-testimonials__heading">{{ section.settings.heading }}</h2>
7
+ {%- endif -%}
8
+ {%- if section.settings.subheading != blank -%}
9
+ <p class="runwell-testimonials__subheading">{{ section.settings.subheading }}</p>
10
+ {%- endif -%}
11
+ </div>
12
+
13
+ <div class="runwell-testimonials__viewport" id="runwell-testimonials-{{ section.id }}">
14
+ <div class="runwell-testimonials__track">
15
+ {%- assign idx = 0 -%}
16
+ {%- for block in section.blocks -%}
17
+ {%- if block.type == 'image' -%}
18
+ <div class="runwell-testimonials__item" data-idx="{{ idx }}" data-pos="{{ idx }}" {{ block.shopify_attributes }}>
19
+ {%- if block.settings.image != blank -%}
20
+ {{ block.settings.image | image_url: width: 600 | image_tag:
21
+ loading: 'lazy',
22
+ widths: '300, 400, 600',
23
+ sizes: '240px',
24
+ alt: block.settings.alt
25
+ }}
26
+ {%- elsif block.settings.asset_filename != blank -%}
27
+ <img src="{{ block.settings.asset_filename | asset_url }}"
28
+ alt="{{ block.settings.alt }}"
29
+ loading="lazy"
30
+ width="240"
31
+ height="340">
32
+ {%- endif -%}
33
+ </div>
34
+ {%- assign idx = idx | plus: 1 -%}
35
+ {%- endif -%}
36
+ {%- endfor -%}
37
+ </div>
38
+ <button class="runwell-testimonials__arrow runwell-testimonials__arrow--left" aria-label="Previous image">&#8249;</button>
39
+ <button class="runwell-testimonials__arrow runwell-testimonials__arrow--right" aria-label="Next image">&#8250;</button>
40
+ </div>
41
+ </div>
42
+
43
+ <script>
44
+ (function() {
45
+ var container = document.getElementById('runwell-testimonials-{{ section.id }}');
46
+ if (!container) return;
47
+ var items = container.querySelectorAll('.runwell-testimonials__item');
48
+ var total = items.length;
49
+ if (total === 0) return;
50
+ var active = 0;
51
+
52
+ function setPositions() {
53
+ items.forEach(function(item, i) {
54
+ var offset = i - active;
55
+ if (offset > total / 2) offset -= total;
56
+ if (offset < -total / 2) offset += total;
57
+ if (offset >= -2 && offset <= 2) {
58
+ item.setAttribute('data-pos', String(offset));
59
+ } else {
60
+ item.setAttribute('data-pos', 'hidden');
61
+ }
62
+ });
63
+ }
64
+
65
+ function navigate(direction) {
66
+ active = (active + direction + total) % total;
67
+ setPositions();
68
+ }
69
+
70
+ container.querySelector('.runwell-testimonials__arrow--right').addEventListener('click', function() { navigate(1); });
71
+ container.querySelector('.runwell-testimonials__arrow--left').addEventListener('click', function() { navigate(-1); });
72
+
73
+ items.forEach(function(item, i) {
74
+ item.addEventListener('click', function() {
75
+ if (i === active) return;
76
+ active = i;
77
+ setPositions();
78
+ });
79
+ });
80
+
81
+ setPositions();
82
+ })();
83
+ </script>
84
+
85
+ {% schema %}
86
+ {
87
+ "name": "Testimonials (UGC)",
88
+ "tag": "section",
89
+ "class": "section",
90
+ "settings": [
91
+ {
92
+ "type": "text",
93
+ "id": "heading",
94
+ "label": "Heading",
95
+ "default": "Real Women, Real Results"
96
+ },
97
+ {
98
+ "type": "text",
99
+ "id": "subheading",
100
+ "label": "Subheading",
101
+ "default": "See how they sculpt"
102
+ },
103
+ {
104
+ "type": "color_scheme",
105
+ "id": "color_scheme",
106
+ "label": "Color scheme",
107
+ "default": "scheme-1"
108
+ }
109
+ ],
110
+ "blocks": [
111
+ {
112
+ "type": "image",
113
+ "name": "Image",
114
+ "settings": [
115
+ {
116
+ "type": "image_picker",
117
+ "id": "image",
118
+ "label": "Image"
119
+ },
120
+ {
121
+ "type": "text",
122
+ "id": "asset_filename",
123
+ "label": "Asset filename (fallback)",
124
+ "info": "Use if image picker is empty. Enter the filename from assets/ directory."
125
+ },
126
+ {
127
+ "type": "text",
128
+ "id": "alt",
129
+ "label": "Alt text"
130
+ }
131
+ ]
132
+ }
133
+ ],
134
+ "presets": [
135
+ {
136
+ "name": "Testimonials (UGC)",
137
+ "blocks": [
138
+ { "type": "image", "settings": { "asset_filename": "ugc-mediterranean-cheekbone.png", "alt": "Woman sculpting cheekbone" } },
139
+ { "type": "image", "settings": { "asset_filename": "ugc-black-woman-cheek.png", "alt": "Woman using brush on cheek" } },
140
+ { "type": "image", "settings": { "asset_filename": "ugc-east-asian-jawline.png", "alt": "Jawline sculpting technique" } },
141
+ { "type": "image", "settings": { "asset_filename": "ugc-latina-presenting.png", "alt": "Presenting brush with case" } },
142
+ { "type": "image", "settings": { "asset_filename": "ugc-south-asian-playful.png", "alt": "Playful pose with brush" } },
143
+ { "type": "image", "settings": { "asset_filename": "ugc-blonde-presenting.png", "alt": "Blonde woman presenting Lusha brush" } }
144
+ ]
145
+ }
146
+ ]
147
+ }
148
+ {% endschema %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runwell/shopify-toolkit",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",