PageSourceSearch

https://cdn.kitchencabinetkings.com/skin/frontend/ultimo/default/js/kck-header-2026.js?v=6q2wqwq7

js kitchencabinetkings.com collected 2026-09-24 08:31:25 UTC 28,970 bytes, 800 lines download raw bytes

1(function () {
2  'use strict';
3
4  var items = Array.prototype.slice.call(document.querySelectorAll('.kck-nav__item[data-menu]'));
5  var panels = Array.prototype.slice.call(document.querySelectorAll('.kck-mega'));
6  var closeTimer = null;
7  /* True while a menu is open because of a touch tap. Touch browsers can fire
8     an emulated mouseleave right after the tap, which would close the menu
9     200ms later, so the hover close is skipped until the next outside tap. */
10  var touchOpen = false;
11
12  /* The top-level trigger is a <button> for Resources and an <a> for Shop
13     Cabinets, Gallery and Company, which open their menu on hover and go to
14     their hub page on click. Everything below looks the trigger up this way. */
15  function trigger(it) {
16    return it.querySelector('a, button');
17  }
18
19  function closeMenus() {
20    touchOpen = false;
21    items.forEach(function (it) {
22      it.classList.remove('is-open');
23      var b = trigger(it);
24      if (b) b.setAttribute('aria-expanded', 'false');
25    });
26    panels.forEach(function (p) {
27      p.classList.remove('is-open');
28    });
29    document.body.classList.remove('kck-menuopen');
30  }
31
32  function openMenu(key) {
33    closeMenus();
34    var it = items.find(function (x) {
35      return x.dataset.menu === key;
36    });
37    var p = panels.find(function (x) {
38      return x.dataset.panel === key;
39    });
40    if (!it || !p) return;
41    it.classList.add('is-open');
42    p.classList.add('is-open');
43    var b = trigger(it);
44    if (b) b.setAttribute('aria-expanded', 'true');
45    document.body.classList.add('kck-menuopen');
46  }
47
48  items.forEach(function (it) {
49    it.addEventListener('mouseenter', function () {
50      clearTimeout(closeTimer);
51      openMenu(it.dataset.menu);
52    });
53    it.addEventListener('mouseleave', function () {
54      if (touchOpen) return;
55      closeTimer = setTimeout(closeMenus, 200);
56    });
57    var link = it.querySelector('a');
58    if (link) {
59      /* Touch at desktop widths (a sideways iPad): the first tap opens the
60         menu, the second goes to the page. Whether the menu was open is read
61         on pointerdown, before the browser's emulated mouseenter opens it. */
62      var touchTap = false;
63      var wasOpen = false;
64      link.addEventListener('pointerdown', function (e) {
65        touchTap = e.pointerType === 'touch' || e.pointerType === 'pen';
66        wasOpen = it.classList.contains('is-open');
67      });
68      link.addEventListener('click', function (e) {
69        if (touchTap && !wasOpen) {
70          e.preventDefault();
71          clearTimeout(closeTimer);
72          openMenu(it.dataset.menu);
73          touchOpen = true;
74        }
75        touchTap = false;
76      });
77      /* Keyboard: Enter follows the link. Arrow Down opens the menu and moves
78         focus to its first link. */
79      link.addEventListener('keydown', function (e) {
80        if (e.key !== 'ArrowDown') return;
81        e.preventDefault();
82        openMenu(it.dataset.menu);
83        var p = document.getElementById(link.getAttribute('aria-controls'));
84        var first = p && p.querySelector('a[href]');
85        if (first) first.focus();
86      });
87      return;
88    }
89    var btn = it.querySelector('button');
90    if (!btn) return;
91    btn.addEventListener('click', function (e) {
92
93      var keyboard = e.detail === 0;
94      var isOpen = it.classList.contains('is-open');
95      if (keyboard) {
96        isOpen ? closeMenus() : openMenu(it.dataset.menu);
97      } else if (!isOpen) {
98        openMenu(it.dataset.menu);
99      }
100    });
101  });
102
103  /* A touch-opened menu closes on the next tap outside the top bar items and
104     the open panel. */
105  document.addEventListener('pointerdown', function (e) {
106    if (!touchOpen || !e.target.closest) return;
107    if (e.target.closest('.kck-nav__item[data-menu], .kck-mega')) return;
108    closeMenus();
109  }, true);
110
111  panels.forEach(function (p) {
112    p.addEventListener('mouseenter', function () {
113      clearTimeout(closeTimer);
114    });
115    p.addEventListener('mouseleave', function () {
116      if (touchOpen) return;
117      closeTimer = setTimeout(closeMenus, 200);
118    });
119  });
120
121  (function () {
122    var rail = document.querySelector('.kck-rl');
123    if (!rail) return;
124    var tabs = Array.prototype.slice.call(rail.querySelectorAll('.kck-rl__item'));
125    var panes = Array.prototype.slice.call(document.querySelectorAll('.kck-rp'));
126
127    function preview(key) {
128      tabs.forEach(function (t) {
129        if (t.dataset.pane === key) t.setAttribute('aria-current', 'true');
130        else t.removeAttribute('aria-current');
131      });
132      panes.forEach(function (p) {
133        var on = p.dataset.pane === key;
134        p.classList.toggle('is-on', on);
135        p.hidden = !on;
136      });
137    }
138
139    tabs.forEach(function (t) {
140      t.addEventListener('mouseenter', function () {
141        preview(t.dataset.pane);
142      });
143      t.addEventListener('focus', function () {
144        preview(t.dataset.pane);
145      });
146    });
147
148    var panel = document.getElementById('p-resources');
149    if (panel) {
150      panel.addEventListener('mouseleave', function () {
151        if (tabs.length) preview(tabs[0].dataset.pane);
152      });
153    }
154  })();
155
156  document.querySelectorAll('.kck-acc__tog').forEach(function (btn) {
157    btn.addEventListener('click', function () {
158      var open = btn.getAttribute('aria-expanded') === 'true';
159      btn.setAttribute('aria-expanded', String(!open));
160      var panel = document.getElementById(btn.getAttribute('aria-controls'));
161      if (panel) panel.hidden = open;
162    });
163  });
164
165  var searchCanonical = document.querySelector('[data-kck-search-canonical]');
166  var desktopSearchSlot = document.querySelector('[data-kck-search-slot="desktop"]');
167  var mobileSearchSlot = document.querySelector('[data-kck-search-slot="mobile"]');
168  var searchMQ = window.matchMedia('(max-width:1023px)');
169
170  function placeSearch() {
171    if (!searchCanonical) return;
172    var target = searchMQ.matches ? mobileSearchSlot : desktopSearchSlot;
173    if (target && searchCanonical.parentNode !== target) target.appendChild(searchCanonical);
174  }
175
176  placeSearch();
177  if (searchMQ.addEventListener) searchMQ.addEventListener('change', placeSearch);
178  else if (searchMQ.addListener) searchMQ.addListener(placeSearch);
179
180  /* IS THE AUTOCOMPLETE ACTUALLY OPEN?
181
182     Everything below used to run on every outside mousedown, whether or not
183     there was anything to close. On a page like checkout, where the customer
184     never touches the search box, that meant a full close routine on EVERY
185     click into EVERY field.
186
187     The close routine calls jQuery's $input.blur(), and jQuery's .blur() with
188     no argument is .trigger('blur') - it fires the handlers whether or not the
189     element has focus. That reached Algolia's typeahead _onBlur, which does
190     $input.removeAttr('aria-activedescendant'), and jQuery 1.7's removeAttr
191     sets an attribute to "" before removing it. Firefox then tries to resolve
192     an element with an empty id and logs
193
194        Empty string passed to getElementById().
195
196     once per click, forever, on every page with the search box. Harmless, but
197     it is our loop and it should not be running at all.
198
199     "Open" is answered from the DOM rather than from a flag, because the
200     dropdown can be closed by Algolia itself, by a click, or by Escape, and a
201     flag would drift out of step with at least one of those. */
202  function algoliaAutocompleteIsOpen() {
203    var menus = document.querySelectorAll(
204      '#algolia-autocomplete-container .aa-dropdown-menu, .aa-dropdown-menu, .aa-Panel'
205    );
206    for (var i = 0; i < menus.length; i++) {
207      var m = menus[i];
208      /* offsetParent is null for anything display:none or detached, which is
209         what a closed dropdown is. aria-hidden covers the ones this file has
210         already closed but left in the layout. */
211      if (m.offsetParent !== null && m.getAttribute('aria-hidden') !== 'true') return true;
212    }
213
214    /* Still open in spirit if focus is inside the search widget - the customer
215       has clicked away and we do want that blur to land. */
216    var active = document.activeElement;
217    if (
218      active &&
219      active.closest &&
220      active.closest('.kck-search-canonical, .algolia-autocomplete, #algolia-autocomplete-container')
221    ) {
222      return true;
223    }
224
225    return false;
226  }
227
228  function closeAlgoliaAutocomplete() {
229    var input = searchCanonical
230      ? searchCanonical.querySelector('#search, input[type="search"], input[type="text"]')
231      : null;
232    /* Read focus ONCE, before anything below changes it. The native blur on
233       the next line moves activeElement to <body>, so a later check against
234       document.activeElement would always be false and the jQuery branch would
235       never blur the input it was supposed to. */
236    var activeBefore = document.activeElement;
237
238    /* Only blur what is actually focused. */
239    if (input && input === activeBefore && typeof input.blur === 'function') input.blur();
240
241    var config = window.algoliaConfig;
242    var bundle = window.algoliaBundle;
243    if (config && config.autocomplete && config.autocomplete.selector && bundle && bundle.$) {
244      var $ = bundle.$;
245      $(config.autocomplete.selector).each(function () {
246        var $input = $(this);
247        var data = $input.data('aaAutocomplete');
248        /* jQuery's .blur() with no argument is .trigger('blur'), which runs
249           every bound handler regardless of focus. That is the call that
250           reached Algolia's _onBlur on every click. */
251        if (this === activeBefore) $input.blur();
252        if (data && data.dropdown) {
253          if (typeof data.dropdown.close === 'function') data.dropdown.close();
254          if (data.dropdown.$menu) data.dropdown.$menu.hide().attr('aria-hidden', 'true');
255        }
256      });
257      $('#algolia-autocomplete-container .aa-dropdown-menu, .aa-dropdown-menu, .aa-Panel')
258        .hide()
259        .attr('aria-hidden', 'true');
260      return;
261    }
262
263    Array.prototype.forEach.call(
264      document.querySelectorAll('#algolia-autocomplete-container .aa-dropdown-menu, .aa-dropdown-menu, .aa-Panel'),
265      function (menu) {
266        menu.style.display = 'none';
267        menu.setAttribute('aria-hidden', 'true');
268      },
269    );
270  }
271
272  function algoliaAutocompleteOutsideHandler(e) {
273    var target = e.target;
274    if (!target) return;
275
276    if (
277      target.closest &&
278      target.closest('.kck-search-canonical, .algolia-autocomplete, .aa-dropdown-menu, .aa-Panel, #algolia-autocomplete-container')
279    ) {
280      return;
281    }
282
283    var config = window.algoliaConfig;
284    var bundle = window.algoliaBundle;
285    if (config && config.autocomplete && config.autocomplete.selector && bundle && bundle.$) {
286      if (bundle.$(target).closest(config.autocomplete.selector).length) return;
287    }
288
289    /* Nothing open, nothing to close. This one line is what stops the handler
290       doing a full teardown on every click on every page. */
291    if (!algoliaAutocompleteIsOpen()) return;
292
293    closeAlgoliaAutocomplete();
294  }
295
296  if (window.__kckAlgoliaAutocompleteOutsideHandler) {
297    document.removeEventListener('touchstart', window.__kckAlgoliaAutocompleteOutsideHandler, true);
298    document.removeEventListener('mousedown', window.__kckAlgoliaAutocompleteOutsideHandler, true);
299  }
300  window.__kckAlgoliaAutocompleteOutsideHandler = algoliaAutocompleteOutsideHandler;
301  document.addEventListener('touchstart', window.__kckAlgoliaAutocompleteOutsideHandler, true);
302  document.addEventListener('mousedown', window.__kckAlgoliaAutocompleteOutsideHandler, true);
303
304  function focusQuietly(el) {
305    if (!el) return;
306    el.setAttribute('data-quiet-focus', '');
307    el.focus();
308    function release() {
309      el.removeAttribute('data-quiet-focus');
310      el.removeEventListener('blur', release);
311      el.removeEventListener('keydown', release);
312    }
313    el.addEventListener('blur', release);
314    el.addEventListener('keydown', release);
315  }
316
317  var drawer = document.getElementById('kck-drawer');
318  var scrim = document.getElementById('kck-scrim');
319  var burger = document.getElementById('kck-burger');
320  var drawerClose = document.getElementById('kck-drawer-close');
321  var levels = drawer
322    ? Array.prototype.slice.call(drawer.querySelectorAll('.kck-drawer__level'))
323    : [];
324
325  var openers = Array.prototype.slice.call(document.querySelectorAll('#kck-burger'));
326  var lastFocus = null;
327
328  function showLevel(name) {
329    levels.forEach(function (l) {
330      var isRoot = l.dataset.level === 'root';
331      var active = l.dataset.level === name;
332      l.classList.toggle('is-active', active && !isRoot);
333      if (isRoot) l.classList.toggle('is-behind', name !== 'root');
334      l.setAttribute('aria-hidden', String(!active));
335    });
336    var lv = levels.find(function (l) {
337      return l.dataset.level === name;
338    });
339    if (!lv) return;
340    lv.scrollTop = 0;
341  }
342
343  function openDrawer() {
344    if (!drawer) return;
345    lastFocus = document.activeElement;
346    drawer.classList.add('is-open');
347    scrim.classList.add('is-open');
348    document.body.classList.add('kck-drawer-open');
349    openers.forEach(function (o) {
350      o.setAttribute('aria-expanded', 'true');
351    });
352    drawer.removeAttribute('inert');
353    showLevel('root');
354
355    focusQuietly(drawer);
356  }
357
358  function closeDrawer() {
359    if (!drawer) return;
360    drawer.classList.remove('is-open');
361    scrim.classList.remove('is-open');
362    document.body.classList.remove('kck-drawer-open');
363    openers.forEach(function (o) {
364      o.setAttribute('aria-expanded', 'false');
365    });
366    drawer.setAttribute('inert', '');
367    focusQuietly(lastFocus);
368  }
369
370  openers.forEach(function (o) {
371    o.addEventListener('click', openDrawer);
372  });
373  if (drawerClose) drawerClose.addEventListener('click', closeDrawer);
374  if (scrim) scrim.addEventListener('click', closeDrawer);
375
376  if (drawer) {
377    drawer.setAttribute('inert', '');
378    drawer.addEventListener('click', function (e) {
379      var go = e.target.closest('[data-goto]');
380      if (go) {
381        e.preventDefault();
382        showLevel(go.dataset.goto);
383        return;
384      }
385      var back = e.target.closest('[data-back]');
386      if (back) {
387        e.preventDefault();
388        showLevel(back.dataset.back);
389      }
390    });
391
392    drawer.addEventListener('keydown', function (e) {
393      if (e.key !== 'Tab') return;
394      var f = drawer.querySelectorAll(
395        'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])',
396      );
397      var vis = Array.prototype.filter.call(f, function (el) {
398        return el.offsetParent !== null;
399      });
400      if (!vis.length) return;
401      var first = vis[0],
402        last = vis[vis.length - 1];
403      if (e.shiftKey && document.activeElement === first) {
404        e.preventDefault();
405        last.focus();
406      } else if (!e.shiftKey && document.activeElement === last) {
407        e.preventDefault();
408        first.focus();
409      }
410    });
411  }
412
413  document.addEventListener('keydown', function (e) {
414    if (e.key !== 'Escape') return;
415    if (drawer && drawer.classList.contains('is-open')) {
416      closeDrawer();
417      return;
418    }
419    var open = items.find(function (i) {
420      return i.classList.contains('is-open');
421    });
422    if (open) {
423      var b = trigger(open);
424      closeMenus();
425      if (b) b.focus();
426    }
427  });
428
429  var bar = document.querySelector('.kck-bar');
430  if (bar) {
431    var pinAt = 0,
432      ticking = false;
433
434    var chrome = document.querySelector('.kckhf');
435
436    function measure() {
437
438      var wasPinned = bar.classList.contains('is-pinned');
439      if (wasPinned) bar.classList.remove('is-pinned');
440      if (chrome) chrome.style.minHeight = '';
441      pinAt = bar.getBoundingClientRect().top + window.pageYOffset;
442      pinnedHeight = null;          /* the viewport changed, re-measure on next pin */
443      if (chrome) chrome.style.minHeight = chrome.getBoundingClientRect().height + 'px';
444      if (wasPinned) bar.classList.add('is-pinned');
445    }
446
447    /* THIS RUNS ON EVERY ANIMATION FRAME WHILE THE PAGE IS SCROLLING, so what
448       it does per call matters more than what it does.
449
450       As written it did three things per frame that it did not need to do:
451
452       1. WROTE THREE CUSTOM PROPERTIES ON documentElement. A custom property
453          on :root cascades to the whole document, so writing one invalidates
454          style for every element on the page. Three writes per frame, on a
455          checkout page with a large DOM, is a full style recalculation per
456          frame - and the values were almost always the same ones that were
457          already there. This is the expensive one.
458
459       2. CALLED getBoundingClientRect() ON THE PINNED BAR. That forces layout
460          mid-frame. The bar's height only changes when the viewport does, so
461          it is measured once per pin and reused.
462
463       3. BUILT A NEW MediaQueryList EVERY FRAME via window.matchMedia(). The
464          query is constant; the object is now created once.
465
466       The guard is the point: compute, compare, and return without touching
467       the DOM when nothing moved. Scrolling now writes only at the moment the
468       header actually pins or unpins. */
469    var compactMQ = window.matchMedia ? window.matchMedia('(max-width: 1023px)') : null;
470    var lastCompactHeight = null;
471    var lastCategoryOffset = null;
472    var pinnedHeight = null;
473
474    function syncStickyOffsets() {
475      var compactHeight = compactMQ && compactMQ.matches ? 57 : 65;
476
477      var categoryOffset = 0;
478      if (bar.classList.contains('is-pinned')) {
479        if (pinnedHeight === null) pinnedHeight = Math.ceil(bar.getBoundingClientRect().height);
480        categoryOffset = pinnedHeight;
481      } else {
482        pinnedHeight = null;
483      }
484
485      if (compactHeight === lastCompactHeight && categoryOffset === lastCategoryOffset) return;
486      lastCompactHeight = compactHeight;
487      lastCategoryOffset = categoryOffset;
488
489      document.documentElement.style.setProperty('--kck-category-sticky-top', categoryOffset + 'px');
490      document.documentElement.style.setProperty('--header-height', compactHeight + 'px');
491      document.documentElement.style.setProperty('--kck-sticky-header-offset', categoryOffset + 'px');
492    }
493
494    function update() {
495      ticking = false;
496      var shouldPin = window.pageYOffset > pinAt;
497      var wasPinned = bar.classList.contains('is-pinned');
498      if (shouldPin !== wasPinned) {
499        bar.classList.toggle('is-pinned', shouldPin);
500        if (typeof closeMenus === 'function') closeMenus();
501      }
502      syncStickyOffsets();
503    }
504
505    function onScroll() {
506      if (ticking) return;
507      ticking = true;
508      requestAnimationFrame(update);
509    }
510
511    measure();
512    update();
513    window.addEventListener('scroll', onScroll, { passive: true });
514    window.addEventListener('resize', function () {
515      measure();
516      update();
517    });
518
519    window.addEventListener('load', function () {
520      measure();
521      update();
522    });
523  }
524
525  /* ------------------------------------------------------------------
526     PROMO BAR COUNTDOWN
527     Ticks the Labor Day sale bar in header.phtml and hides the whole
528     .kck-promo block once the deadline passes. The deadline lives in
529     the markup (data-countdown-deadline), not here.
530     ------------------------------------------------------------------ */
531  function initPromoCountdown() {
532    var el = document.querySelector('[data-countdown-deadline]');
533    if (!el) return;
534
535    var deadline = new Date(el.getAttribute('data-countdown-deadline')).getTime();
536    if (isNaN(deadline)) return;
537
538    var promo = el.closest ? el.closest('.kck-promo') : null;
539    var out = {
540      days:    el.querySelector('[data-countdown="days"]'),
541      hours:   el.querySelector('[data-countdown="hours"]'),
542      minutes: el.querySelector('[data-countdown="minutes"]'),
543      seconds: el.querySelector('[data-countdown="seconds"]')
544    };
545
546    var timer = null;
547
548    function pad(n) {
549      return String(n).length < 2 ? '0' + n : String(n);
550    }
551
552    function set(node, val) {
553      if (node && node.textContent !== val) node.textContent = val;
554    }
555
556    function tick() {
557      var diff = deadline - Date.now();
558
559      if (diff <= 0) {
560        if (promo) promo.hidden = true;
561        if (timer) clearInterval(timer);
562        return;
563      }
564
565      set(out.days,    pad(Math.floor(diff / 86400000)));
566      set(out.hours,   pad(Math.floor((diff % 86400000) / 3600000)));
567      set(out.minutes, pad(Math.floor((diff % 3600000) / 60000)));
568      set(out.seconds, pad(Math.floor((diff % 60000) / 1000)));
569    }
570
571    tick();
572    timer = setInterval(tick, 1000);
573
574    // A throttled background tab can drift a full second or more. Resync the
575    // moment it comes back rather than waiting for the next interval.
576    document.addEventListener('visibilitychange', function () {
577      if (!document.hidden) tick();
578    });
579  }
580
581  function initUtilityDropdowns() {
582    var root = document.querySelector('.kck-util');
583    if (!root) return;
584    var openDropdown = null;
585
586    function getDropdown(target) {
587      return target && target.closest ? target.closest('.kckheader-utility__dropdown') : null;
588    }
589
590    function closeDropdown(dropdown) {
591      if (!dropdown) return;
592      dropdown.classList.remove('is-open');
593      var trigger = dropdown.querySelector('.kckheader-utility__action');
594      if (trigger) trigger.setAttribute('aria-expanded', 'false');
595      if (openDropdown === dropdown) openDropdown = null;
596    }
597
598    function closeAllDropdowns() {
599      Array.prototype.forEach.call(root.querySelectorAll('.kckheader-utility__dropdown.is-open'), closeDropdown);
600      openDropdown = null;
601    }
602
603    function openDropdownMenu(dropdown) {
604      if (!dropdown) return;
605      if (openDropdown && openDropdown !== dropdown) closeDropdown(openDropdown);
606      dropdown.classList.add('is-open');
607      var trigger = dropdown.querySelector('.kckheader-utility__action');
608      if (trigger) trigger.setAttribute('aria-expanded', 'true');
609      openDropdown = dropdown;
610    }
611
612    document.addEventListener('click', function (e) {
613      var trigger = e.target.closest ? e.target.closest('.kckheader-utility__action') : null;
614      var dropdown = getDropdown(trigger);
615      if (dropdown && dropdown.querySelector('.kckheader-utility__menu')) {
616        e.preventDefault();
617        if (dropdown.classList.contains('is-open')) closeDropdown(dropdown);
618        else openDropdownMenu(dropdown);
619        return;
620      }
621      if (openDropdown && (!e.target.closest || !e.target.closest('.kckheader-utility__dropdown'))) closeAllDropdowns();
622    });
623
624    root.addEventListener('mouseover', function (e) {
625      var dropdown = getDropdown(e.target);
626      if (!dropdown || !dropdown.querySelector('.kckheader-utility__menu')) return;
627      if (e.relatedTarget && dropdown.contains(e.relatedTarget)) return;
628      openDropdownMenu(dropdown);
629    });
630
631    root.addEventListener('mouseout', function (e) {
632      var dropdown = getDropdown(e.target);
633      if (!dropdown || !dropdown.querySelector('.kckheader-utility__menu')) return;
634      if (e.relatedTarget && dropdown.contains(e.relatedTarget)) return;
635      window.setTimeout(function () {
636        if (!dropdown.matches(':hover')) closeDropdown(dropdown);
637      }, 180);
638    });
639
640    document.addEventListener('keydown', function (e) {
641      if (e.key === 'Escape' && openDropdown) closeAllDropdowns();
642    });
643  }
644
645  function setCartCount(n) {
646    n = parseInt(n, 10);
647    document.querySelectorAll('[data-cart]').forEach(function (el) {
648      var badge = el.querySelector('.kck-util__badge');
649      if (!n || n < 1) {
650        if (badge) badge.remove();
651        return;
652      }
653      if (!badge) {
654        badge = document.createElement('span');
655        badge.className = 'kck-util__badge';
656        el.appendChild(badge);
657      }
658      badge.textContent = n > 99 ? '99+' : String(n);
659      el.setAttribute('aria-label', 'Cart, ' + n + ' item' + (n === 1 ? '' : 's'));
660    });
661  }
662  function initCartBadgeSync() {
663    var cartHost = document.querySelector('.kck-util__cart-live');
664    if (!cartHost) return;
665    var lastQty = null;
666
667    function parseQty(text) {
668      if (!text) return null;
669      var paren = String(text).match(/\((\d+)\)/);
670      if (paren) return parseInt(paren[1], 10);
671      var bare = String(text).trim().match(/^(\d+)$/);
672      return bare ? parseInt(bare[1], 10) : null;
673    }
674
675    function readQty() {
676      var mini = document.getElementById('mini-cart');
677      if (!mini) return null;
678      var counter = mini.querySelector('.counter-number');
679      var qty = counter ? parseQty(counter.textContent) : null;
680      if (qty !== null) return qty;
681      var badge = mini.querySelector('.kckheader-utility__cart-badge');
682      qty = badge ? parseQty(badge.textContent) : null;
683      if (qty !== null) return qty;
684      var label = mini.querySelector('.mini-cart-heading .label');
685      return label ? parseQty(label.textContent) : null;
686    }
687
688    function sync() {
689      var qty = readQty();
690      if (qty === null) return;
691      var text = String(qty);
692      Array.prototype.forEach.call(document.querySelectorAll('.kckheader-utility__cart-badge, .kckheader-utility__cart-qty, #mini-cart .counter-number'), function (el) {
693        if (el.textContent.trim() !== text) el.textContent = text;
694      });
695      if (qty !== lastQty) {
696        lastQty = qty;
697        setCartCount(qty);
698      }
699      var mini = document.getElementById('mini-cart');
700      if (mini) {
701        mini.classList.remove('is-empty');
702        mini.classList.add('is-not-empty');
703      }
704    }
705
706    sync();
707    if ('MutationObserver' in window) new MutationObserver(sync).observe(cartHost, { childList: true, subtree: true, characterData: true });
708    document.addEventListener('ajaxComplete', sync);
709    document.addEventListener('cart:updated', sync);
710  }
711
712
713  function initScrollTop() {
714    if (!document.body || document.getElementById('kck-totop')) return;
715
716    if (!document.getElementById('i-top')) {
717      var svgNs = 'http://www.w3.org/2000/svg';
718      var sprite = document.createElementNS(svgNs, 'svg');
719      sprite.setAttribute('aria-hidden', 'true');
720      sprite.setAttribute('width', '0');
721      sprite.setAttribute('height', '0');
722      sprite.style.position = 'absolute';
723      sprite.style.width = '0';
724      sprite.style.height = '0';
725      sprite.style.overflow = 'hidden';
726
727      var defs = document.createElementNS(svgNs, 'defs');
728      var symbol = document.createElementNS(svgNs, 'symbol');
729      symbol.setAttribute('id', 'i-top');
730      symbol.setAttribute('viewBox', '0 0 24 24');
731
732      var path1 = document.createElementNS(svgNs, 'path');
733      path1.setAttribute('d', 'M12 19V6');
734      var path2 = document.createElementNS(svgNs, 'path');
735      path2.setAttribute('d', 'M6 11.5L12 5.5l6 6');
736
737      symbol.appendChild(path1);
738      symbol.appendChild(path2);
739      defs.appendChild(symbol);
740      sprite.appendChild(defs);
741      document.body.appendChild(sprite);
742    }
743
744    var toTop = document.createElement('a');
745    toTop.className = 'kck-totop';
746    toTop.id = 'kck-totop';
747    toTop.href = '#main';
748    toTop.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false"><use href="#i-top" xlink:href="#i-top"></use></svg><span>Back to top</span>';
749    document.body.appendChild(toTop);
750
751    var threshold = 800;
752    var scrollTicking = false;
753    var motionQuery = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
754
755    function updateScrollTop() {
756      var y = window.pageYOffset || document.documentElement.scrollTop || 0;
757      toTop.classList.toggle('is-visible', y > threshold);
758      scrollTicking = false;
759    }
760
761    function onScrollTopScroll() {
762      if (scrollTicking) return;
763      scrollTicking = true;
764      window.requestAnimationFrame(updateScrollTop);
765    }
766
767    toTop.addEventListener('click', function (e) {
768      e.preventDefault();
769      var behavior = motionQuery && motionQuery.matches ? 'auto' : 'smooth';
770      try {
771        window.scrollTo({ top: 0, behavior: behavior });
772      } catch (err) {
773        window.scrollTo(0, 0);
774      }
775
776      if (e.detail === 0) {
777        var top = document.querySelector('.kck-logo, .kck-bar__logo, .logo');
778        if (top && typeof top.focus === 'function') top.focus();
779      }
780    });
781
782    window.addEventListener('scroll', onScrollTopScroll, { passive: true });
783    updateScrollTop();
784  }
785
786  if (document.body) initScrollTop();
787  else document.addEventListener('DOMContentLoaded', initScrollTop);
788
789  /* A VERSION MARKER, so you can tell which build a given tab is running.
790     Check it in any console with:   KCK_HEADER
791     Bump the date when this file changes. */
792  window.KCK_HEADER = { version: '2026.09.04' };
793
794  window.kckSetCartCount = setCartCount;
795  initUtilityDropdowns();
796  initCartBadgeSync();
797  initPromoCountdown();
798  if (typeof window.KCK_CART_COUNT === 'number') setCartCount(window.KCK_CART_COUNT);
799
800})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.