@widelab-nc/widelab 1.1.35 → 1.1.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widelab-nc/widelab",
3
- "version": "1.1.35",
3
+ "version": "1.1.37",
4
4
  "description": "Widelab starter template based on Finsweet template + add-ons.",
5
5
  "homepage": "https://widelab.co",
6
6
  "license": "ISC",
package/src/index.js CHANGED
@@ -16,6 +16,23 @@ function customGsapEasing() {
16
16
  CustomEase.create('loader2', '0.77, 0, 0.175, 1');
17
17
  }
18
18
 
19
+ // Smoothly scrolls to section if current URL contains a hash
20
+ function scrollToHashOnLoad(offsetY = 0) {
21
+ window.addEventListener('load', () => {
22
+ const hash = window.location.hash;
23
+ if (!hash) return;
24
+
25
+ const target = document.querySelector(hash);
26
+ if (!target) return;
27
+
28
+ gsap.to(window, {
29
+ scrollTo: { y: target, offsetY: offsetY },
30
+ duration: 1,
31
+ ease: 'power2.out'
32
+ });
33
+ });
34
+ }
35
+
19
36
  // Functions definitions
20
37
 
21
38
  // Contact form open animation
@@ -260,6 +277,39 @@ if (infoBar) {
260
277
  });
261
278
  }
262
279
 
280
+
281
+
282
+ // Safe new version that uses the same classes and structure
283
+ window.customCursorTextUpdate = function(el) {
284
+ const cursorEl = document.querySelector('.custom_cursor_text');
285
+ if (!cursorEl) return;
286
+
287
+ const defaultText = cursorEl.dataset.default || '';
288
+
289
+ const getText = (node) =>
290
+ node.getAttribute('widecursortext') || node.getAttribute('wideCursorText') || '';
291
+
292
+ const activate = () => {
293
+ cursorEl.textContent = getText(el);
294
+ document.documentElement.classList.add('has-custom-cursor');
295
+ };
296
+
297
+ const maybeDeactivate = (ev) => {
298
+ const to = ev.relatedTarget;
299
+ // Don't deactivate if moving between elements that also use the custom cursor
300
+ if (to && to.closest && to.closest('[wide="customCursor"]')) return;
301
+
302
+ cursorEl.textContent = defaultText;
303
+ document.documentElement.classList.remove('has-custom-cursor');
304
+ };
305
+
306
+ el.addEventListener('pointerenter', activate);
307
+ el.addEventListener('pointerleave', maybeDeactivate);
308
+ };
309
+
310
+
311
+
312
+
263
313
  // Hero animation on load
264
314
  window.heroAnimationOnLoad = function(e) {
265
315
  const videoMask = e.querySelector('.hero_headline_ract-mask');
@@ -310,53 +360,75 @@ if (infoBar) {
310
360
  );
311
361
  }
312
362
 
313
- // Case study mask animation
314
- window.caseStudyInteraction = function(e) {
315
- // Video mask animation
316
- const mask = e.querySelector('.portfolio_item_image-mask');
317
- const maskAnimation = gsap.from(mask, {
318
- width: '100%',
319
- duration: 0.35,
320
- ease: 'loader2',
321
- paused: true,
322
- });
323
- ScrollTrigger.create({
324
- animation: maskAnimation,
325
- trigger: mask,
326
- start: 'top 80%',
327
- onEnter: () => maskAnimation.play(),
328
- });
363
+ // Case study mask animation + pill fade-in (last step)
364
+ window.caseStudyInteraction = function (e) {
365
+ // Video mask + pill timeline
366
+ const mask = e.querySelector('.portfolio_item_image-mask');
367
+ const pill = e.querySelector('.cs_pill');
329
368
 
330
- // Video on hover actions
331
- const videoElements = e.getElementsByTagName('video');
332
- if (window.innerWidth > 992) {
333
- const videoElement = videoElements.length > 0 ? videoElements[0] : null;
334
- if (!videoElement) return;
369
+ // Build a single timeline so the pill fades in AFTER the mask animation
370
+ const tl = gsap.timeline({
371
+ paused: true,
372
+ defaults: { ease: 'loader2' }
373
+ });
335
374
 
336
- videoElement.addEventListener('loadeddata', () => {
337
- videoElement.pause();
338
- });
339
- e.addEventListener('mouseenter', hoverIN, false);
340
- e.addEventListener('mouseleave', hoverOUT, false);
375
+ // Ensure pill starts hidden
376
+ if (pill) gsap.set(pill, { autoAlpha: 0 }); // autoAlpha controls visibility + opacity
341
377
 
342
- function hoverIN() {
343
- videoElement.play();
344
- }
378
+ // Mask reveals first...
379
+ tl.from(mask, {
380
+ width: '100%',
381
+ duration: 0.35
382
+ });
345
383
 
346
- function hoverOUT() {
347
- videoElement.pause();
348
- //videoElement.currentTime = 0;
349
- }
350
- } else {
351
- videoElements.forEach((videoElem) => {
352
- const poster = videoElem.getAttribute('poster');
353
- videoElem.innerHTML = '';
354
- videoElem.setAttribute('poster', poster);
355
- videoElem.load();
356
- videoElem.pause();
357
- });
384
+ // ...then pill fades in as the last step
385
+ if (pill) {
386
+ tl.to(pill, {
387
+ autoAlpha: 1,
388
+ duration: 0.25
389
+ });
390
+ }
391
+
392
+ // Trigger on scroll
393
+ ScrollTrigger.create({
394
+ animation: tl,
395
+ trigger: mask,
396
+ start: 'top 80%',
397
+ onEnter: () => tl.play()
398
+ });
399
+
400
+ // Video on hover actions
401
+ const videoElements = e.getElementsByTagName('video');
402
+ if (window.innerWidth > 992) {
403
+ const videoElement = videoElements.length > 0 ? videoElements[0] : null;
404
+ if (!videoElement) return;
405
+
406
+ videoElement.addEventListener('loadeddata', () => {
407
+ videoElement.pause();
408
+ });
409
+
410
+ e.addEventListener('mouseenter', hoverIN, false);
411
+ e.addEventListener('mouseleave', hoverOUT, false);
412
+
413
+ function hoverIN() {
414
+ videoElement.play();
415
+ }
416
+
417
+ function hoverOUT() {
418
+ videoElement.pause();
419
+ // videoElement.currentTime = 0;
358
420
  }
421
+ } else {
422
+ // Mobile: keep poster only
423
+ Array.from(videoElements).forEach((videoElem) => {
424
+ const poster = videoElem.getAttribute('poster');
425
+ videoElem.innerHTML = '';
426
+ videoElem.setAttribute('poster', poster);
427
+ videoElem.load();
428
+ videoElem.pause();
429
+ });
359
430
  }
431
+ };
360
432
 
361
433
  // Reviews slider
362
434
  window.reviewsSlider = function(e) {
@@ -413,61 +485,6 @@ if (infoBar) {
413
485
  });
414
486
  }
415
487
 
416
- // Reviews slider
417
- window.reviewsSlider = function(e) {
418
- // Put content of static slide into list
419
- const staticContent = e.querySelector(
420
- '.splide__slide.reviews_collection_item.is-static'
421
- ).innerHTML;
422
- const dynamicElement = e.querySelector('.reviews_collection_item');
423
- dynamicElement.innerHTML = staticContent;
424
- dynamicElement.classList.add('is-static');
425
-
426
- // Init reviews splide
427
- const reviewsSplide = new Splide(e, {
428
- type: 'slide',
429
- fixedWidth: '24.5rem',
430
- rewind: true,
431
- focus: 0,
432
- padding: { left: '2.5rem', right: 0 },
433
- gap: 0,
434
- omitEnd: true,
435
- arrows: false,
436
- drag: false,
437
- easing: 'cubic-bezier(0.83, 0, 0.17, 1)',
438
- snap: true,
439
- classes: {
440
- pagination: 'splide__pagination reviews-pagination',
441
- page: 'splide__pagination__page reviews-page',
442
- },
443
- breakpoints: {
444
- 992: { fixedWidth: '17.438rem', padding: { left: '1.75rem' }, drag: 'free', snap: false },
445
- 480: { fixedWidth: '16.25rem', padding: { left: '1.375rem' }},
446
- },
447
- }).mount();
448
-
449
- // Next slide on click
450
- if (window.innerWidth > 992) {
451
- reviewsSplide.on('click', function () {
452
- reviewsSplide.go('+1');
453
- });
454
- }
455
-
456
- // Animation in
457
- gsap.from(e, {
458
- scrollTrigger: {
459
- trigger: e,
460
- start: 'top bottom',
461
- end: 'bottom bottom-=15%',
462
- scrub: 1,
463
- once: true,
464
- },
465
- x: '10%',
466
- opacity: 0,
467
- ease: 'linear',
468
- });
469
- }
470
-
471
488
  // Facts slider
472
489
  window.factsSlider = function(e) {
473
490
  // Facts slider splide initialize
@@ -635,11 +652,11 @@ window.partnerSlider = function (e) {
635
652
  // Initialize Splide with drag restored
636
653
  const splide = new Splide(e, {
637
654
  type: 'slide',
638
- fixedWidth: '20%',
655
+ fixedWidth: '31.5%',
639
656
  padding: { left: '2.5rem', right: 0 },
640
657
  focus: 0,
641
658
  gap: 0,
642
- pagination: true,
659
+ pagination: false,
643
660
  omitEnd: true,
644
661
  arrows: false,
645
662
  drag: true, // restored dragging
@@ -1013,149 +1030,251 @@ window.badgeColorChange = function(e) {
1013
1030
  });
1014
1031
  }
1015
1032
 
1033
+ // Single entry you call from x-init
1034
+ window.clientFilters = function () {
1035
+ // Prevent double initialization if Alpine re-mounts
1036
+ if (window.__clientFiltersInitialized) return;
1037
+ window.__clientFiltersInitialized = true;
1038
+
1039
+ // --- Config ---
1040
+ const FIELDS = ["industry", "country", "services"];
1041
+ const SEL = {
1042
+ dropdown: '[fs-cmsfilter-element="dropdown"]',
1043
+ dropdownHolder: ".dropdown-holder",
1044
+ dropdownTitle: '[filters-clients_dropdown-field="tag-name-title"], .filters-clients_dropdown-field',
1045
+ tagNumber: '[fs-cmsfilter-element="tag-number"]',
1046
+ dynItem: ".w-dyn-item",
1047
+ checkboxWrap: ".w-checkbox",
1048
+ checkboxInput: 'input[type="checkbox"]',
1049
+ webflowChecked: "w--redirected-checked",
1050
+ fsActive: "fs-cmsfilter_active",
1051
+ listContainer: ".collection-list",
1052
+ count: ".count",
1053
+ crossIcon: ".cross-icon",
1054
+ clearButton: ".clear-button",
1055
+ activeDropdown: "active-dropdown",
1056
+ };
1016
1057
 
1017
- // Filtering logic on Client's page
1018
- window.clientFilters = function() {
1019
- function updateCheckboxCounts(excludeField) {
1058
+ // --- Utils ---
1059
+ function $(s, ctx = document) { return ctx.querySelector(s); }
1060
+ function $all(s, ctx = document) { return Array.from(ctx.querySelectorAll(s)); }
1061
+ function debounce(fn, ms) { let t; return (...a)=>{clearTimeout(t); t=setTimeout(()=>fn(...a), ms);} }
1062
+ function textClean(el){ return (el?.textContent || "").trim(); }
1063
+
1064
+ // Wait until Finsweet cmsfilter is ready (query params applied, classes toggled)
1065
+ function waitForCmsFilter() {
1066
+ return new Promise((resolve) => {
1067
+ const w = window;
1068
+ w.fsAttributes = w.fsAttributes || [];
1069
+ // already initialized?
1070
+ try {
1071
+ const exists = (w.fsAttributes.instances || []).some(i => i.name === "cmsfilter");
1072
+ if (exists) return resolve();
1073
+ } catch(e){}
1074
+ w.fsAttributes.push(["cmsfilter", function(){ resolve(); }]);
1075
+ });
1076
+ }
1020
1077
 
1021
- const fields = ['industry', 'country', 'services'];
1078
+ // Remember original texts for labels and toggles
1079
+ function hydrateOriginalTexts() {
1080
+ FIELDS.forEach((field) => {
1081
+ $all(`span[fs-cmsfilter-field="${field}"]`).forEach((span) => {
1082
+ if (!span.dataset.originalText) {
1083
+ span.dataset.originalText = textClean(span);
1084
+ }
1085
+ });
1086
+ });
1087
+ $all(SEL.dropdownHolder).forEach((holder) => {
1088
+ const toggle = $(SEL.dropdownTitle, holder);
1089
+ if (toggle && !toggle.dataset.defaultText) {
1090
+ toggle.dataset.defaultText = textClean(toggle);
1091
+ }
1092
+ });
1093
+ }
1022
1094
 
1023
- fields.forEach(function(field) {
1095
+ // Count matching results per value and sort UI items by count
1096
+ function updateCheckboxCounts(excludeField = null) {
1097
+ FIELDS.forEach((field) => {
1024
1098
  if (field === excludeField) return;
1025
1099
 
1026
- const checkboxes = document.querySelectorAll(`span[fs-cmsfilter-field="${field}"]`);
1027
-
1028
- checkboxes.forEach(function(checkbox) {
1029
- const originalText = checkbox.dataset.originalText || checkbox.textContent.trim();
1030
- checkbox.dataset.originalText = originalText;
1100
+ const spans = $all(`span[fs-cmsfilter-field="${field}"]`);
1101
+ spans.forEach((span) => {
1102
+ const originalText = span.dataset.originalText || textClean(span);
1103
+ span.dataset.originalText = originalText;
1031
1104
 
1032
- let matchedCount = 0;
1033
-
1034
- document.querySelectorAll(`div[fs-cmsfilter-field="${field}"]`).forEach(function(div) {
1035
- if (div.textContent.trim() === originalText) {
1036
- matchedCount++;
1037
- }
1105
+ // Count visible matches in results
1106
+ let matched = 0;
1107
+ $all(`div[fs-cmsfilter-field="${field}"]`).forEach((div) => {
1108
+ if (textClean(div) === originalText) matched++;
1038
1109
  });
1039
1110
 
1040
- let countSpan = checkbox.nextElementSibling;
1041
- if (!countSpan || !countSpan.classList.contains('count')) {
1042
- countSpan = document.createElement('span');
1043
- countSpan.className = 'count';
1044
- checkbox.insertAdjacentElement('afterend', countSpan);
1111
+ // Ensure "(n)" bubble
1112
+ let countSpan = span.nextElementSibling;
1113
+ if (!countSpan || !countSpan.classList.contains("count")) {
1114
+ countSpan = document.createElement("span");
1115
+ countSpan.className = "count";
1116
+ span.insertAdjacentElement("afterend", countSpan);
1045
1117
  }
1046
- countSpan.textContent = `(${matchedCount})`;
1118
+ countSpan.textContent = `(${matched})`;
1047
1119
 
1048
- const parentDiv = checkbox.closest('div.w-dyn-item');
1049
- if (matchedCount === 0) {
1050
- parentDiv.classList.add('empty-category');
1051
- } else {
1052
- parentDiv.classList.remove('empty-category');
1053
- }
1120
+ // Toggle empty-category on dyn item
1121
+ const dyn = span.closest(SEL.dynItem);
1122
+ if (dyn) dyn.classList.toggle("empty-category", matched === 0);
1054
1123
  });
1055
1124
 
1056
- sortCheckboxes(field);
1125
+ // Sort items in this field's collection-list by count desc
1126
+ const anySpan = document.querySelector(`span[fs-cmsfilter-field="${field}"]`);
1127
+ if (anySpan) {
1128
+ const list = anySpan.closest(SEL.listContainer);
1129
+ if (list) {
1130
+ const items = Array.from(list.children);
1131
+ items.sort((a, b) => {
1132
+ const ca = parseInt((a.querySelector(SEL.count)?.textContent || "").replace(/[()]/g, "")) || 0;
1133
+ const cb = parseInt((b.querySelector(SEL.count)?.textContent || "").replace(/[()]/g, "")) || 0;
1134
+ return cb - ca;
1135
+ });
1136
+ items.forEach((it) => list.appendChild(it));
1137
+ }
1138
+ }
1057
1139
  });
1058
1140
  }
1059
1141
 
1060
- function sortCheckboxes(field) {
1142
+ // Collect selected label names within a dropdown holder
1143
+ function getSelectedNames(holder) {
1144
+ const labels = $all(`${SEL.dynItem} ${SEL.checkboxWrap}, ${SEL.checkboxWrap}`, holder);
1145
+ return labels
1146
+ .filter((label) => {
1147
+ if (label.classList.contains(SEL.fsActive)) return true; // Finsweet
1148
+ const wbox = label.querySelector(".w-checkbox-input");
1149
+ if (wbox && wbox.classList.contains(SEL.webflowChecked)) return true; // Webflow styled
1150
+ const input = label.querySelector(SEL.checkboxInput);
1151
+ return !!(input && input.checked); // Native
1152
+ })
1153
+ .map((label) => {
1154
+ const span = label.querySelector('[fs-cmsfilter-field]');
1155
+ return (span && (span.getAttribute('data-original-text') || textClean(span))) || "";
1156
+ })
1157
+ .filter(Boolean);
1158
+ }
1061
1159
 
1062
- const parentList = document.querySelector(`span[fs-cmsfilter-field="${field}"]`).closest('.collection-list');
1063
- const items = Array.from(parentList.children);
1160
+ // Update dropdown button label (first selected + +N), class states and numeric tag
1161
+ function updateDropdownTitle(holder) {
1162
+ const toggle = $(SEL.dropdownTitle, holder);
1163
+ if (!toggle) return;
1164
+
1165
+ const defaults = toggle.dataset.defaultText || textClean(toggle);
1166
+ const names = getSelectedNames(holder);
1167
+
1168
+ // Persist "first selected" per dropdown (dataset)
1169
+ if (!holder.dataset.firstSelected) {
1170
+ holder.dataset.firstSelected = names[0] || "";
1171
+ } else if (names.length && !names.includes(holder.dataset.firstSelected)) {
1172
+ holder.dataset.firstSelected = names[0];
1173
+ } else if (!names.length) {
1174
+ holder.dataset.firstSelected = "";
1175
+ }
1064
1176
 
1065
- items.sort(function(a, b) {
1066
- const countA = parseInt((a.querySelector('.count')?.textContent || '').replace(/[()]/g, '')) || 0;
1067
- const countB = parseInt((b.querySelector('.count')?.textContent || '').replace(/[()]/g, '')) || 0;
1068
- return countB - countA;
1069
- });
1177
+ let display = defaults;
1178
+ if (names.length === 1) {
1179
+ display = names[0];
1180
+ } else if (names.length > 1) {
1181
+ display = `${holder.dataset.firstSelected || names[0]} +${names.length - 1}`;
1182
+ }
1183
+ toggle.textContent = display;
1070
1184
 
1071
- items.forEach(function(item) {
1072
- parentList.appendChild(item);
1073
- });
1185
+ holder.classList.toggle(SEL.activeDropdown, names.length > 0);
1186
+ toggle.classList.toggle(SEL.activeDropdown, names.length > 0);
1187
+
1188
+ const tagNum = $(SEL.tagNumber, holder);
1189
+ if (tagNum) {
1190
+ tagNum.textContent = names.length > 1 ? `+${names.length - 1}` : "";
1191
+ }
1074
1192
  }
1075
1193
 
1076
- updateCheckboxCounts();
1194
+ function updateAllDropdownTitles() {
1195
+ $all(SEL.dropdownHolder).forEach(updateDropdownTitle);
1196
+ }
1077
1197
 
1078
- function delayedUpdate(excludeField) {
1079
- setTimeout(function() {
1080
- updateCheckboxCounts(excludeField);
1081
- }, 200);
1198
+ function toggleClearButton() {
1199
+ const anyActive = !!$(`.${SEL.activeDropdown}`);
1200
+ const btn = $(SEL.clearButton);
1201
+ if (btn) btn.style.display = anyActive ? "block" : "none";
1082
1202
  }
1083
1203
 
1084
- document.querySelectorAll('.w-checkbox').forEach(function(checkbox) {
1085
- checkbox.addEventListener('click', function() {
1086
- const spanElement = checkbox.closest('label').querySelector('span[fs-cmsfilter-field]');
1087
- if (spanElement) {
1088
- const field = spanElement.getAttribute('fs-cmsfilter-field');
1089
- delayedUpdate(field);
1204
+ // Unified UI refresh
1205
+ const refreshUI = debounce(function (excludeField = null) {
1206
+ updateCheckboxCounts(excludeField);
1207
+ updateAllDropdownTitles();
1208
+ toggleClearButton();
1209
+ }, 60);
1210
+
1211
+ // --- Event wiring (delegated & once) ---
1212
+ function bindEventsOnce() {
1213
+ // Clicks: checkbox labels, dropdown toggles, cross icons, clear button
1214
+ document.addEventListener("click", (e) => {
1215
+ const label = e.target.closest(SEL.checkboxWrap);
1216
+ if (label) {
1217
+ // Let Finsweet/Webflow toggle classes first
1218
+ setTimeout(() => {
1219
+ const span = label.querySelector('[fs-cmsfilter-field]');
1220
+ const field = span?.getAttribute('fs-cmsfilter-field') || null;
1221
+ refreshUI(field);
1222
+ }, 0);
1223
+ return;
1090
1224
  }
1091
- });
1092
- });
1093
1225
 
1094
- document.querySelectorAll('.cross-icon').forEach(function(icon) {
1095
- icon.addEventListener('click', function() {
1096
- delayedUpdate(null); // Update all fields with 200ms delay
1226
+ const toggle = e.target.closest(SEL.dropdownTitle);
1227
+ if (toggle) {
1228
+ // After opening/closing or instant selections
1229
+ setTimeout(() => refreshUI(null), 120);
1230
+ return;
1231
+ }
1232
+
1233
+ const cross = e.target.closest(SEL.crossIcon);
1234
+ if (cross) {
1235
+ setTimeout(() => refreshUI(null), 80);
1236
+ return;
1237
+ }
1238
+
1239
+ const clear = e.target.closest(SEL.clearButton);
1240
+ if (clear) {
1241
+ setTimeout(() => refreshUI(null), 120);
1242
+ return;
1243
+ }
1097
1244
  });
1098
- });
1099
1245
 
1100
- document.querySelectorAll('.filters-clients_dropdown-field').forEach(function(dropdownField) {
1101
- dropdownField.addEventListener('click', function() {
1102
- delayedUpdate(null); // Update all fields with 200ms delay
1246
+ // Any checkbox state change (keyboard/tap)
1247
+ document.addEventListener("change", (e) => {
1248
+ if (e.target && e.target.matches(SEL.checkboxInput)) {
1249
+ const span = e.target.closest('label')?.querySelector('[fs-cmsfilter-field]') ||
1250
+ e.target.closest(SEL.checkboxWrap)?.querySelector('[fs-cmsfilter-field]');
1251
+ const field = span?.getAttribute('fs-cmsfilter-field') || null;
1252
+ const holder = e.target.closest(SEL.dropdownHolder);
1253
+ if (holder) updateDropdownTitle(holder);
1254
+ refreshUI(field);
1255
+ }
1103
1256
  });
1104
- });
1105
1257
 
1106
- function toggleClearButton() {
1107
- if (document.querySelectorAll('.active-dropdown').length > 0) {
1108
- document.querySelector('.clear-button').style.display = 'block';
1109
- } else {
1110
- document.querySelector('.clear-button').style.display = 'none';
1111
- }
1258
+ // Observe class mutations that might come from Finsweet
1259
+ const mo = new MutationObserver((muts) => {
1260
+ // If any class changed, softly refresh
1261
+ if (muts.some(m => m.type === "attributes" && m.attributeName === "class")) {
1262
+ refreshUI(null);
1263
+ }
1264
+ });
1265
+ mo.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["class"] });
1112
1266
  }
1113
- toggleClearButton();
1114
- const observer = new MutationObserver(toggleClearButton);
1115
-
1116
- observer.observe(document.body, {
1117
- childList: true,
1118
- subtree: true,
1119
- attributes: true,
1120
- attributeFilter: ['class']
1121
- });
1122
-
1123
- document.querySelectorAll('[fs-cmsfilter-element="dropdown"]').forEach(function(dropdown) {
1124
- const originalText = dropdown.querySelector('[filters-clients_dropdown-field="tag-name-title"]').textContent;
1125
- let firstCheckedText = null;
1126
-
1127
- dropdown.querySelectorAll('input[type="checkbox"]').forEach(function(checkbox) {
1128
- checkbox.addEventListener('change', function() {
1129
- const checkedBoxes = dropdown.querySelectorAll('input[type="checkbox"]:checked');
1130
- const titleField = dropdown.querySelector('[filters-clients_dropdown-field="tag-name-title"]');
1131
- const dropdownHolder = dropdown.closest('.dropdown-holder');
1132
- const tagNumber = dropdown.querySelector('[fs-cmsfilter-element="tag-number"]');
1133
-
1134
- if (checkedBoxes.length > 0) {
1135
- // Check if firstCheckedText is still checked
1136
- if (![...checkedBoxes].some(cb => cb.nextElementSibling.textContent.split('(')[0].trim() === firstCheckedText)) {
1137
- // If not, set it to the current checkbox text without the count
1138
- firstCheckedText = checkedBoxes[0].nextElementSibling.textContent.split('(')[0].trim();
1139
- }
1140
- titleField.textContent = firstCheckedText;
1141
- dropdownHolder.classList.add('active-dropdown');
1142
- } else {
1143
- titleField.textContent = originalText;
1144
- dropdownHolder.classList.remove('active-dropdown');
1145
- firstCheckedText = null;
1146
- }
1147
1267
 
1148
- if (checkedBoxes.length > 1) {
1149
- const numberText = `+${checkedBoxes.length - 1}`;
1150
- tagNumber.textContent = numberText;
1151
- } else {
1152
- tagNumber.textContent = '';
1153
- }
1154
- });
1155
- });
1268
+ // --- Boot ---
1269
+ waitForCmsFilter().then(() => {
1270
+ // Allow Finsweet to finish applying query param checks/classes
1271
+ setTimeout(() => {
1272
+ hydrateOriginalTexts();
1273
+ bindEventsOnce();
1274
+ refreshUI(null);
1275
+ }, 120);
1156
1276
  });
1157
- }
1158
-
1277
+ };
1159
1278
  // Filtering logic on Client's page
1160
1279
  window.formatCounter = function() {
1161
1280
  function format_number(x) {
@@ -1549,6 +1668,7 @@ window.initBentoRevealScrub = function (container) {
1549
1668
  }
1550
1669
  );
1551
1670
  };
1671
+
1552
1672
  window.initPartnerVideos = function (container) {
1553
1673
  const items = Array.from(container.querySelectorAll('.partner_item'));
1554
1674
  const vids = items.map(i => i.querySelector('video')).filter(Boolean);
@@ -1910,4 +2030,5 @@ window.closeInfoBar = function() {
1910
2030
  customCursorOnClick();
1911
2031
  contactFormOpenAnimation();
1912
2032
  disableScrollMenu();
2033
+ scrollToHashOnLoad();
1913
2034
  });