zz-shopify-components 0.35.1-beta.6 → 0.35.1-beta.8
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/assets/zz-components.css
CHANGED
|
@@ -311,51 +311,52 @@ body.zz-dialog-open {
|
|
|
311
311
|
.zz-btn.zz-btn-default.zz-btn-black {
|
|
312
312
|
background: transparent;
|
|
313
313
|
color: #2D2D2D;
|
|
314
|
-
border:
|
|
314
|
+
border: 1px solid #2D2D2D;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
.zz-btn.zz-btn-default.zz-btn-white {
|
|
318
318
|
background: transparent;
|
|
319
319
|
color: #fff;
|
|
320
|
-
border:
|
|
320
|
+
border: 1px solid #FFFFFF;
|
|
321
|
+
font-weight: 400;
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
.zz-btn.zz-btn-default.zz-btn-orange {
|
|
324
325
|
background: transparent;
|
|
325
326
|
color: #FC6C0F;
|
|
326
|
-
border:
|
|
327
|
+
border: 1px solid #FC6C0F;
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
.zz-btn.zz-btn-default.zz-btn-yellow {
|
|
330
331
|
background: transparent;
|
|
331
332
|
color: #000;
|
|
332
|
-
border:
|
|
333
|
+
border: 1px solid #FFEB00;
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
.zz-btn.zz-btn-default.zz-btn-blue {
|
|
336
337
|
background: transparent;
|
|
337
338
|
color: #378DDD;
|
|
338
|
-
border:
|
|
339
|
+
border: 1px solid #378DDD;
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
.zz-btn.zz-btn-round.zz-btn-black {
|
|
342
343
|
background: transparent;
|
|
343
344
|
color: #2D2D2D;
|
|
344
|
-
border:
|
|
345
|
+
border: 1px solid #2D2D2D;
|
|
345
346
|
border-radius: 30px;
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
.zz-btn.zz-btn-round.zz-btn-white {
|
|
349
350
|
background: transparent;
|
|
350
351
|
color: #fff;
|
|
351
|
-
border:
|
|
352
|
+
border: 1px solid #FFFFFF4D;
|
|
352
353
|
border-radius: 30px;
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
.zz-btn.zz-btn-round.zz-btn-orange {
|
|
356
357
|
background: transparent;
|
|
357
358
|
color: #ff9900;
|
|
358
|
-
border:
|
|
359
|
+
border: 1px solid #ff9900;
|
|
359
360
|
border-radius: 30px;
|
|
360
361
|
}
|
|
361
362
|
|
|
@@ -408,6 +409,11 @@ body.zz-dialog-open {
|
|
|
408
409
|
font-size: 14px;
|
|
409
410
|
}
|
|
410
411
|
|
|
412
|
+
.zz-btn.zz-btn-large.zz-btn-default {
|
|
413
|
+
padding: 0px 1.6rem;
|
|
414
|
+
|
|
415
|
+
}
|
|
416
|
+
|
|
411
417
|
@media (min-width: 1024px) {
|
|
412
418
|
.zz-btn {
|
|
413
419
|
height: 40px;
|
|
@@ -434,6 +440,10 @@ body.zz-dialog-open {
|
|
|
434
440
|
.zz-btn-link.zz-btn-link-large {
|
|
435
441
|
font-size: 16px;
|
|
436
442
|
}
|
|
443
|
+
|
|
444
|
+
.zz-btn.zz-btn-large.zz-btn-default {
|
|
445
|
+
padding: 0px 30px;
|
|
446
|
+
}
|
|
437
447
|
}
|
|
438
448
|
|
|
439
449
|
.zz-video-btn.zz-btn.zz-btn-white {
|
|
@@ -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',
|
|
@@ -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
|
@@ -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>
|