zz-shopify-components 0.35.1-beta.5 → 0.35.1-beta.7

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.
@@ -1,6 +1,7 @@
1
1
  {% schema %}
2
2
  {
3
3
  "name": "Button v2",
4
+ "class": "zz-button-v2",
4
5
  "settings": [
5
6
  {
6
7
  "type": "text",
@@ -102,6 +103,18 @@
102
103
  ],
103
104
  "default": "black",
104
105
  },
106
+ {
107
+ "type": "color",
108
+ "id": "custom_background_color",
109
+ "label": "自定义背景颜色",
110
+ "info": "按钮颜色有限,颜色自定义"
111
+ },
112
+ {
113
+ "type": "color",
114
+ "id": "custom_text_color",
115
+ "label": "自定义文字颜色",
116
+ "info": "按钮文字颜色有限,颜色自定义"
117
+ },
105
118
  {
106
119
  "type": "select",
107
120
  "id": "size",
@@ -213,6 +226,18 @@
213
226
  }
214
227
  }
215
228
  {% endif %}
229
+
230
+ {% if block.settings.custom_background_color != blank %}
231
+ #shopify-block-{{ block.id }} .{{ btn_class }} {
232
+ background: {{ block.settings.custom_background_color }} !important;
233
+ }
234
+ {% endif %}
235
+
236
+ {% if block.settings.custom_text_color != blank %}
237
+ #shopify-block-{{ block.id }} .{{ btn_class }} {
238
+ color: {{ block.settings.custom_text_color }} !important;
239
+ }
240
+ {% endif %}
216
241
  </style>
217
242
 
218
243
  {% render 'zz-button-v2',
@@ -102,6 +102,14 @@
102
102
  ],
103
103
  "default": "left"
104
104
  },
105
+ {
106
+ "type": "checkbox",
107
+ "id": "separate_display",
108
+ "label": "区分显示pc还是mb",
109
+ "default": false,
110
+ "info": "区分显示pc还是mb, true时 没有配置PC与mb将不会显示。不会使用PC的配置来配置mb"
111
+
112
+ },
105
113
  {
106
114
  "type": "select",
107
115
  "id": "animation",
@@ -182,7 +190,8 @@
182
190
  mobile_font_size: block.settings.mobile_font_size,
183
191
  pc_text_align: block.settings.pc_text_align,
184
192
  mobile_text_align: block.settings.mobile_text_align,
185
- class_name: block.settings.animation
193
+ class_name: block.settings.animation,
194
+ separate_display: block.settings.separate_display,
186
195
  %}
187
196
 
188
197
  {% if block.settings.use_margin %}
@@ -153,6 +153,14 @@
153
153
  "default": false,
154
154
  "info": "Blod 高亮文字是否加粗"
155
155
 
156
+ },
157
+ {
158
+ "type": "checkbox",
159
+ "id": "separate_display",
160
+ "label": "区分显示pc还是mb",
161
+ "default": false,
162
+ "info": "区分显示pc还是mb, true时 没有配置PC与mb将不会显示。不会使用PC的配置来配置mb"
163
+
156
164
  },
157
165
 
158
166
  {
@@ -221,6 +229,7 @@
221
229
  mobile_text_align: block.settings.mobile_text_align,
222
230
  pc_font_line_height: block.settings.pc_font_line_height,
223
231
  mobile_font_line_height: block.settings.mobile_font_line_height,
232
+ separate_display: block.settings.separate_display,
224
233
  %}
225
234
 
226
235
 
@@ -0,0 +1,255 @@
1
+ <style>
2
+ #shopify-block-{{ block.id }} .zz-countdown {
3
+ display: flex;
4
+ }
5
+
6
+ #shopify-block-{{ block.id }} .zz-countdown-title {
7
+ font-weight: 700;
8
+ font-size: 20px;
9
+ line-height: 120%;
10
+ letter-spacing: 0%;
11
+ {% if block.settings.theme_color == 'white' %}
12
+ color: #FFFFFF;
13
+ {% else %}
14
+ color: #2D2D2D !important;
15
+ {% endif %}
16
+
17
+ {% if block.settings.title_position == 'front' %}
18
+ margin-right: 24px;
19
+ {% else %}
20
+ margin-left: 24px;
21
+ {% endif %}
22
+ }
23
+
24
+ #shopify-block-{{ block.id }} .zz-countdown-timer {
25
+ display: flex;
26
+ gap: 20px;
27
+
28
+ }
29
+
30
+ #shopify-block-{{ block.id }} .zz-countdown-unit {
31
+ display: flex;
32
+ flex-direction: column;
33
+ align-items: center;
34
+ gap: 4px;
35
+ position: relative;
36
+ }
37
+
38
+ #shopify-block-{{ block.id }} .zz-countdown-number {
39
+ width: 48px;
40
+ height: 48px;
41
+ display: flex;
42
+
43
+ align-items: center;
44
+ justify-content: center;
45
+ {% if block.settings.theme_color == 'white' %}
46
+ background: #FFFFFF33;
47
+ color: #FFFFFF;
48
+ {% else %}
49
+ background: #2D2D2D66;
50
+ color:#2D2D2D;
51
+ {% endif %}
52
+
53
+ border-radius: 4px;
54
+ font-size: 18px;
55
+ font-weight: 600;
56
+ }
57
+
58
+ #shopify-block-{{ block.id }} .zz-countdown-label {
59
+ font-size: 12px;
60
+ font-weight: 400;
61
+ {% if block.settings.theme_color == 'white' %}
62
+ color: #F5F5F5B2;
63
+ {% else %}
64
+ color: #2D2D2D;
65
+ {% endif %}
66
+ }
67
+
68
+ #shopify-block-{{ block.id }} .zz-countdown-separator {
69
+ font-size: 12px;
70
+ font-weight: 700;
71
+ {% if block.settings.theme_color == 'white' %}
72
+ color: #FFFFFF;
73
+ {% else %}
74
+ color: #2D2D2D;
75
+ {% endif %}
76
+ margin-top: 14px;
77
+ }
78
+
79
+ /* 响应式设计 */
80
+ @media screen and (max-width: 1024px) {
81
+ #shopify-block-{{ block.id }} .zz-countdown-title {
82
+ font-size: 20px;
83
+ }
84
+
85
+ #shopify-block-{{ block.id }} .zz-countdown-timer {
86
+ gap: 12px;
87
+ }
88
+
89
+ #shopify-block-{{ block.id }} .zz-countdown-number {
90
+ width: 34px;
91
+ height: 34px;
92
+ font-size: 14px;
93
+ }
94
+
95
+ #shopify-block-{{ block.id }} .zz-countdown-label {
96
+ font-size: 10px;
97
+ }
98
+
99
+ #shopify-block-{{ block.id }} .zz-countdown-separator {
100
+ font-size: 12px;
101
+ font-weight: 700;
102
+ margin-top: 10px;
103
+ }
104
+ #shopify-block-{{ block.id }} .zz-countdown-title {
105
+ font-size: 14px;
106
+ }
107
+
108
+
109
+ }
110
+
111
+
112
+ </style>
113
+
114
+ <div
115
+ class="zz-countdown tw-flex {% if block.settings.title_position == 'back' %} tw-flex-row-reverse {% endif %}">
116
+ <div class="zz-countdown-title">{{ block.settings.title }}</div>
117
+ <div class="zz-countdown-timer">
118
+ <div class="zz-countdown-unit">
119
+ <div class="zz-countdown-number" id="zz-countdown-days">00</div>
120
+ <div class="zz-countdown-label">{{ block.settings.day_label | default: 'Day' }}</div>
121
+ </div>
122
+ <div class="zz-countdown-separator">:</div>
123
+ <div class="zz-countdown-unit">
124
+ <div class="zz-countdown-number" id="zz-countdown-hours">00</div>
125
+ <div class="zz-countdown-label">{{ block.settings.hour_label | default: 'Hour' }}</div>
126
+ </div>
127
+ <div class="zz-countdown-separator">:</div>
128
+ <div class="zz-countdown-unit">
129
+ <div class="zz-countdown-number" id="zz-countdown-minutes">00</div>
130
+ <div class="zz-countdown-label">{{ block.settings.minute_label | default: 'Minute' }}</div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <script>
136
+ document.addEventListener('DOMContentLoaded', function() {
137
+ // 获取结束时间
138
+ const endTimeStr = '{{ block.settings.end_time | date: "%s" }}';
139
+
140
+ // 解析结束时间
141
+ const endTime = Number(endTimeStr) * 1000;
142
+ if (isNaN(endTime)) {
143
+ console.error('Invalid end time format');
144
+ return;
145
+ }
146
+
147
+ // 获取倒计时元素
148
+ const daysElement = document.getElementById('zz-countdown-days');
149
+ const hoursElement = document.getElementById('zz-countdown-hours');
150
+ const minutesElement = document.getElementById('zz-countdown-minutes');
151
+
152
+ // 补零函数
153
+ function padZero(num) {
154
+ return num < 10 ? '0' + num : num;
155
+ }
156
+
157
+ // 更新倒计时函数
158
+ function updateCountdown() {
159
+ const now = Date.now();
160
+ const distance = endTime - now;
161
+
162
+ // 如果倒计时结束
163
+ if (distance < 0) {
164
+ daysElement.textContent = '00';
165
+ hoursElement.textContent = '00';
166
+ minutesElement.textContent = '00';
167
+ return;
168
+ }
169
+
170
+ // 计算剩余时间
171
+ const days = Math.floor(distance / (1000 * 60 * 60 * 24));
172
+ const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
173
+ const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
174
+
175
+ // 更新UI
176
+ daysElement.textContent = padZero(days);
177
+ hoursElement.textContent = padZero(hours);
178
+ minutesElement.textContent = padZero(minutes);
179
+ }
180
+
181
+ // 初始更新
182
+ updateCountdown();
183
+
184
+ // 每分钟更新一次
185
+ const timer = setInterval(updateCountdown, 60000);
186
+
187
+ // 清理定时器
188
+ window.addEventListener('beforeunload', function() {
189
+ clearInterval(timer);
190
+ });
191
+ });
192
+ </script>
193
+
194
+ {% schema %}
195
+ {
196
+ "name": "Count Down",
197
+ "settings": [
198
+ {
199
+ "type": "text",
200
+ "id": "end_time",
201
+ "default": "2026-04-28 23:59:59",
202
+ "label": "结束时间",
203
+ "info": "shopify后台配置的时区,格式:YYYY-MM-DD HH:MM:SS"
204
+ },
205
+ {
206
+ "type": "richtext",
207
+ "id": "title",
208
+ "label": "标题"
209
+ },
210
+ {
211
+ "type": "select",
212
+ "id": "theme_color",
213
+ "label": "文案颜色模式",
214
+ "options": [
215
+ { "value": "black", "label": "黑色模式" },
216
+ { "value": "white", "label": "白色模式" },
217
+ ],
218
+ "default": "white",
219
+ },
220
+ {
221
+ "type": "select",
222
+ "id": "title_position",
223
+ "label": "标题位置",
224
+ "options": [
225
+ { "value": "front", "label": "前置" },
226
+ { "value": "back", "label": "后置" },
227
+ ],
228
+ "default": "front",
229
+ },
230
+ {
231
+ "type": "text",
232
+ "id": "day_label",
233
+ "label": "天数标签",
234
+ "default": "Day"
235
+ },
236
+ {
237
+ "type": "text",
238
+ "id": "hour_label",
239
+ "label": "小时标签",
240
+ "default": "Hour"
241
+ },
242
+ {
243
+ "type": "text",
244
+ "id": "minute_label",
245
+ "label": "分钟标签",
246
+ "default": "Minute"
247
+ }
248
+ ],
249
+ "presets": [
250
+ {
251
+ "name": "Count Down"
252
+ }
253
+ ]
254
+ }
255
+ {% endschema %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zz-shopify-components",
3
- "version": "0.35.1-beta.5",
3
+ "version": "0.35.1-beta.7",
4
4
  "description": "Reusable Shopify components for theme projects",
5
5
  "keywords": [
6
6
  "shopify",
@@ -0,0 +1,417 @@
1
+ {% schema %}
2
+ {
3
+ "name": "ZZ Product Subnav",
4
+ "class": "zz-product-subnav-section",
5
+ "settings": [
6
+ {
7
+ "type": "number",
8
+ "id": "top_offset",
9
+ "label": "吸附偏移量 (sticky top)",
10
+ "default": 50
11
+ },
12
+ {
13
+ "type": "number",
14
+ "id": "scroll_offset_desktop",
15
+ "label": "点击滚动偏移量 (PC)",
16
+ "default": 120
17
+ },
18
+ {
19
+ "type": "number",
20
+ "id": "scroll_offset_mobile",
21
+ "label": "点击滚动偏移量 (Mob)",
22
+ "default": 100
23
+ },
24
+ {
25
+ "type": "text",
26
+ "id": "max_width",
27
+ "label": "内容最大宽度 (PC, CSS value)",
28
+ "default": "1520px",
29
+ "info": "例如:1520px / 1660px / 100%"
30
+ },
31
+ {
32
+ "type": "color",
33
+ "id": "background_color",
34
+ "label": "背景颜色",
35
+ "default": "#FDBD33"
36
+ },
37
+ {
38
+ "type": "select",
39
+ "id": "text_color_mode",
40
+ "label": "文案颜色模式",
41
+ "default": "black",
42
+ "options": [
43
+ {
44
+ "value": "black",
45
+ "label": "黑色"
46
+ },
47
+ {
48
+ "value": "white",
49
+ "label": "白色"
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ "blocks": [
55
+ {
56
+ "type": "tab",
57
+ "name": "Tab",
58
+ "settings": [
59
+ {
60
+ "type": "text",
61
+ "id": "label",
62
+ "label": "Tab 文案",
63
+ "default": "X1 PRO"
64
+ },
65
+ {
66
+ "type": "text",
67
+ "id": "target_module_name",
68
+ "label": "目标模块 data-zz-module-name",
69
+ "info": "用于定位页面模块,如:x1-pro(匹配 [data-zz-module-name='x1-pro'])"
70
+ },
71
+ {
72
+ "type": "text",
73
+ "id": "target_selector",
74
+ "label": "目标选择器 (可选)",
75
+ "info": "优先级高于模块名。例如:#x1-pro 或 .my-section 或 [data-zz-module-name~='x1-pro']"
76
+ }
77
+ ]
78
+ }
79
+ ],
80
+ "presets": [
81
+ {
82
+ "name": "ZZ Product Subnav"
83
+ }
84
+ ]
85
+ }
86
+ {% endschema %}
87
+
88
+ {%- assign top_offset = section.settings.top_offset | default: 50 -%}
89
+ {%- assign text_color_mode = section.settings.text_color_mode | default: 'black' -%}
90
+ {%- if text_color_mode == 'white' -%}
91
+ {%- assign tab_text_color = 'rgba(255,255,255,0.8)' -%}
92
+ {%- assign tab_active_text_color = '#FFFFFF' -%}
93
+ {%- else -%}
94
+ {%- assign tab_text_color = 'rgba(45,45,45,0.8)' -%}
95
+ {%- assign tab_active_text_color = '#2D2D2D' -%}
96
+ {%- endif -%}
97
+
98
+ <nav class="zz-product-subnav" aria-label="Product sub navigation">
99
+ <div class="zz-product-subnav__wrap">
100
+ <div class="zz-product-subnav__track" role="tablist">
101
+ {%- for block in section.blocks -%}
102
+ {%- assign label = block.settings.label | default: '' -%}
103
+ {%- assign module_name = block.settings.target_module_name | default: '' -%}
104
+ {%- assign selector = block.settings.target_selector | default: '' -%}
105
+ <button
106
+ type="button"
107
+ class="zz-product-subnav__tab{% if forloop.first %} is-active{% endif %}"
108
+ role="tab"
109
+ aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
110
+ data-zz-subnav-label="{{ label | escape }}"
111
+ data-zz-subnav-module="{{ module_name | escape }}"
112
+ data-zz-subnav-selector="{{ selector | escape }}"
113
+ {{ block.shopify_attributes }}
114
+ >
115
+ <span class="zz-product-subnav__tabText">{{ label }}</span>
116
+ </button>
117
+ {%- endfor -%}
118
+ </div>
119
+ </div>
120
+ </nav>
121
+
122
+ <style>
123
+ #shopify-section-{{ section.id }} {
124
+ position: sticky;
125
+ top: {{ top_offset }}px;
126
+ z-index: 30;
127
+ }
128
+
129
+ #shopify-section-{{ section.id }} .zz-product-subnav {
130
+ -webkit-backdrop-filter: blur(10px);
131
+ backdrop-filter: blur(10px);
132
+ background: {{ section.settings.background_color | default: '#FDBD33' }};
133
+ }
134
+
135
+ #shopify-section-{{ section.id }} .zz-product-subnav__wrap {
136
+ max-width: {{ section.settings.max_width | default: '1520px' }};
137
+ margin: 0 auto;
138
+ padding: 0 20px;
139
+ }
140
+
141
+ #shopify-section-{{ section.id }} .zz-product-subnav__track {
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 60px;
145
+ justify-content: center;
146
+ overflow-x: auto;
147
+ overflow-y: hidden;
148
+ scrollbar-width: none;
149
+ -ms-overflow-style: none;
150
+ -webkit-overflow-scrolling: touch;
151
+ }
152
+
153
+ #shopify-section-{{ section.id }} .zz-product-subnav__track::-webkit-scrollbar {
154
+ width: 0;
155
+ height: 0;
156
+ display: none;
157
+ }
158
+
159
+ #shopify-section-{{ section.id }} .zz-product-subnav__tab {
160
+ appearance: none;
161
+ background: transparent;
162
+ border: 0;
163
+ padding: 16px 0;
164
+ cursor: pointer;
165
+ flex: 0 0 auto;
166
+ display: inline-flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ border-bottom: 3px solid transparent;
170
+ }
171
+
172
+ #shopify-section-{{ section.id }} .zz-product-subnav__tabText {
173
+ font-family: Poppins, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
174
+ font-size: 16px;
175
+ line-height: 24px;
176
+ font-weight: 500;
177
+ color: {{ tab_text_color }};
178
+ white-space: nowrap;
179
+ }
180
+
181
+ #shopify-section-{{ section.id }} .zz-product-subnav__tab.is-active {
182
+ border-bottom-color: {{ tab_active_text_color }};
183
+ }
184
+
185
+ #shopify-section-{{ section.id }} .zz-product-subnav__tab.is-active .zz-product-subnav__tabText {
186
+ font-weight: 700;
187
+ color: {{ tab_active_text_color }};
188
+ }
189
+
190
+ @media (max-width: 1023px) {
191
+ #shopify-section-{{ section.id }} .zz-product-subnav__wrap {
192
+ padding: 0 20px;
193
+ }
194
+
195
+ #shopify-section-{{ section.id }} .zz-product-subnav__track {
196
+ justify-content: flex-start;
197
+ gap: 24px;
198
+ }
199
+
200
+ #shopify-section-{{ section.id }} .zz-product-subnav__tab {
201
+ padding: 12px 0;
202
+ }
203
+
204
+ #shopify-section-{{ section.id }} .zz-product-subnav__tabText {
205
+ font-size: 14px;
206
+ }
207
+ }
208
+ </style>
209
+
210
+ <script>
211
+ (function () {
212
+ const sectionEl = document.getElementById('shopify-section-{{ section.id }}');
213
+ if (!sectionEl) return;
214
+
215
+ // Prevent duplicate bindings (theme editor / section re-render)
216
+ if (typeof sectionEl.__zzProductSubnavCleanup === 'function') {
217
+ sectionEl.__zzProductSubnavCleanup();
218
+ sectionEl.__zzProductSubnavCleanup = null;
219
+ }
220
+
221
+ const trackEl = sectionEl.querySelector('.zz-product-subnav__track');
222
+ const tabs = Array.from(sectionEl.querySelectorAll('.zz-product-subnav__tab'));
223
+ if (!trackEl || tabs.length === 0) return;
224
+
225
+ const scrollOffsetDesktop = Number({{ section.settings.scroll_offset_desktop | default: 120 }}) || 0;
226
+ const scrollOffsetMobile = Number({{ section.settings.scroll_offset_mobile | default: 100 }}) || 0;
227
+ const mqDesktop = window.matchMedia('(min-width: 1024px)');
228
+
229
+ const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
230
+ const getScrollOffset = () => (mqDesktop.matches ? scrollOffsetDesktop : scrollOffsetMobile);
231
+
232
+ function resolveTarget(tabEl) {
233
+ const selector = (tabEl.getAttribute('data-zz-subnav-selector') || '').trim();
234
+ const moduleName = (tabEl.getAttribute('data-zz-subnav-module') || '').trim();
235
+
236
+ if (selector) {
237
+ try {
238
+ return document.querySelector(selector);
239
+ } catch (e) {
240
+ return null;
241
+ }
242
+ }
243
+
244
+ if (moduleName) {
245
+ return document.querySelector(`[data-zz-module-name="${CSS.escape(moduleName)}"]`);
246
+ }
247
+
248
+ return null;
249
+ }
250
+
251
+ function setActive(tabEl, shouldScrollIntoView) {
252
+ tabs.forEach((t) => {
253
+ const isActive = t === tabEl;
254
+ t.classList.toggle('is-active', isActive);
255
+ t.setAttribute('aria-selected', isActive ? 'true' : 'false');
256
+ });
257
+
258
+ if (shouldScrollIntoView) {
259
+ const containerWidth = trackEl.clientWidth;
260
+ const tabLeft = tabEl.offsetLeft;
261
+ const tabWidth = tabEl.offsetWidth;
262
+ const targetLeft = tabLeft - (containerWidth / 2) + (tabWidth / 2);
263
+ const nextLeft = clamp(targetLeft, 0, Math.max(0, trackEl.scrollWidth - containerWidth));
264
+ trackEl.scrollTo({ left: nextLeft, behavior: 'smooth' });
265
+ }
266
+ }
267
+
268
+ function scrollToTarget(tabEl) {
269
+ const target = resolveTarget(tabEl);
270
+ if (!target) return;
271
+ const rect = target.getBoundingClientRect();
272
+ const offsetTop = rect.top + window.scrollY - getScrollOffset();
273
+ window.scrollTo({ top: offsetTop, behavior: 'smooth' });
274
+ }
275
+
276
+ // Click to scroll
277
+ tabs.forEach((tabEl) => {
278
+ tabEl.addEventListener('click', () => {
279
+ setActive(tabEl, true);
280
+ scrollToTarget(tabEl);
281
+ });
282
+ });
283
+
284
+ // Scroll-spy (activate when target crosses viewport center)
285
+ const ac = new AbortController();
286
+ let targets = [];
287
+ let rafId = 0;
288
+ let domObserver = null;
289
+
290
+ function buildTargets() {
291
+ targets = tabs
292
+ .map((tabEl) => ({ tabEl, target: resolveTarget(tabEl) }))
293
+ .filter((x) => !!x.target);
294
+ return targets;
295
+ }
296
+
297
+ function computeActiveByViewportCenter() {
298
+ if (targets.length === 0) return;
299
+ const mid = window.innerHeight / 2;
300
+
301
+ let best = null;
302
+ let bestDist = Infinity;
303
+
304
+ for (const x of targets) {
305
+ const r = x.target.getBoundingClientRect();
306
+ if (!r || r.height <= 0) continue;
307
+
308
+ // Primary rule: viewport center is inside the target region
309
+ if (r.top <= mid && r.bottom >= mid) {
310
+ const center = r.top + r.height / 2;
311
+ const dist = Math.abs(center - mid);
312
+ if (dist < bestDist) {
313
+ best = x;
314
+ bestDist = dist;
315
+ }
316
+ }
317
+ }
318
+
319
+ // Fallback: choose the last section above the center
320
+ if (!best) {
321
+ let above = null;
322
+ let aboveTop = -Infinity;
323
+ for (const x of targets) {
324
+ const r = x.target.getBoundingClientRect();
325
+ if (!r || r.height <= 0) continue;
326
+ if (r.top <= mid && r.top > aboveTop) {
327
+ above = x;
328
+ aboveTop = r.top;
329
+ }
330
+ }
331
+ best = above;
332
+ }
333
+
334
+ if (!best) return;
335
+
336
+ const currentActive = sectionEl.querySelector('.zz-product-subnav__tab.is-active');
337
+ if (currentActive !== best.tabEl) {
338
+ setActive(best.tabEl, true);
339
+ }
340
+ }
341
+
342
+ function scheduleCompute() {
343
+ if (rafId) return;
344
+ rafId = window.requestAnimationFrame(() => {
345
+ rafId = 0;
346
+ computeActiveByViewportCenter();
347
+ });
348
+ }
349
+
350
+ function startSpy() {
351
+ if (buildTargets().length === 0) return false;
352
+
353
+ // Keep it simple: scroll/resize-driven, rAF-throttled
354
+ window.addEventListener('scroll', scheduleCompute, { passive: true, signal: ac.signal });
355
+ window.addEventListener('resize', scheduleCompute, { passive: true, signal: ac.signal });
356
+ mqDesktop.addEventListener('change', scheduleCompute, { signal: ac.signal });
357
+
358
+ // Initial sync
359
+ scheduleCompute();
360
+ return true;
361
+ }
362
+
363
+ function tryStartSpyWithWait() {
364
+ if (startSpy()) return;
365
+
366
+ // Targets not in DOM yet: wait for insertion
367
+ if (domObserver) return;
368
+ domObserver = new MutationObserver(() => {
369
+ if (startSpy()) {
370
+ domObserver.disconnect();
371
+ domObserver = null;
372
+ }
373
+ });
374
+
375
+ const root = document.body || document.documentElement;
376
+ if (root) {
377
+ domObserver.observe(root, { childList: true, subtree: true });
378
+ }
379
+
380
+ // Also retry a few times in case mutations are not observed (e.g. large async render)
381
+ let attempts = 0;
382
+ const maxAttempts = 20;
383
+ const tick = () => {
384
+ if (startSpy()) {
385
+ if (domObserver) domObserver.disconnect();
386
+ domObserver = null;
387
+ return;
388
+ }
389
+ attempts += 1;
390
+ if (attempts >= maxAttempts) return;
391
+ window.setTimeout(tick, 150);
392
+ };
393
+ window.setTimeout(tick, 0);
394
+ }
395
+
396
+ // If this script runs before the rest of the page modules mount, defer spy start.
397
+ if (document.readyState === 'loading') {
398
+ document.addEventListener(
399
+ 'DOMContentLoaded',
400
+ () => {
401
+ tryStartSpyWithWait();
402
+ },
403
+ { once: true, signal: ac.signal }
404
+ );
405
+ } else {
406
+ tryStartSpyWithWait();
407
+ }
408
+
409
+ sectionEl.__zzProductSubnavCleanup = () => {
410
+ ac.abort();
411
+ if (rafId) window.cancelAnimationFrame(rafId);
412
+ rafId = 0;
413
+ if (domObserver) domObserver.disconnect();
414
+ domObserver = null;
415
+ };
416
+ })();
417
+ </script>
@@ -10,7 +10,13 @@
10
10
  pc_text_align: pc端对齐方式
11
11
  font_weight: 字体粗细
12
12
  mobile_text_align: 移动端对齐方式
13
+ separate_display: 区分显示pc还是mb, true时 没有配置PC与mb将不会显示。不会使用PC的配置来配置mb
13
14
  {% endcomment %}
15
+ {% if separate_display == true %}
16
+ {% assign separate_display = true %}
17
+ {% else %}
18
+ {% assign separate_display = false %}
19
+ {% endif %}
14
20
 
15
21
  {% if text != blank %}
16
22
  <div class="content-text content-text-pc max-lg:tw-hidden tw-text-[#170B01B2] tw-text-[16px] tw-leading-[1.5] tw-text-left {{ class_name }} "
@@ -23,7 +29,11 @@
23
29
  </div>
24
30
  {% endif %}
25
31
 
26
- {% if mb_text != blank %}
32
+ {% assign mobile_text = mb_text %}
33
+ {% if mb_text == blank and separate_display == false %}
34
+ {% assign mobile_text = text %}
35
+ {% endif %}
36
+ {% if mobile_text != blank %}
27
37
  <div
28
38
  class="content-text content-text-mobile lg:tw-hidden tw-text-[#170B01B2] tw-text-[1.4rem] tw-leading-[1.5] tw-text-left {{ class_name }}"
29
39
  style="color: {{ text_color }};font-weight: {{ font_weight }};
@@ -31,7 +41,7 @@
31
41
  text-align: {{ mobile_text_align }};
32
42
  {% if mobile_font_line_height %}line-height: {{ mobile_font_line_height }};{% endif %}"
33
43
  >
34
- {{ mb_text | default: text }}
44
+ {{ mobile_text | default: text }}
35
45
  </div>
36
46
  {% endif %}
37
47