(function () {
    'use strict';

    var initialized = new WeakSet();

    /* Custom rAF smooth scroll — animates ourselves so it's always smooth, even when the
       browser downgrades native scrollIntoView({behavior:'smooth'}) to an instant jump
       (e.g. OS "Reduce Motion"). offset accounts for any fixed header (0 by default). */
    function smoothScrollTo(target, offset) {
        offset = offset || 0;
        var startY = window.pageYOffset || document.documentElement.scrollTop || 0;
        var endY   = startY + target.getBoundingClientRect().top - offset;
        var dist   = endY - startY;
        if (Math.abs(dist) < 1) { return; }
        var duration = 600;
        var startT   = null;
        function ease(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; } // easeInOutQuad
        function step(ts) {
            if (startT === null) { startT = ts; }
            var p = Math.min(1, (ts - startT) / duration);
            window.scrollTo(0, startY + dist * ease(p));
            if (p < 1) { requestAnimationFrame(step); }
        }
        requestAnimationFrame(step);
    }

    /* ---------- Dynamic Island (single-morph, content-based height) ---------- */
    function setupIsland(moduleEl, wrapper) {
        var island = moduleEl.querySelector('.df_an_island');
        if (!island) {
            return;
        }

        var openByDefault = wrapper.getAttribute('data-open-by-default') === 'on';

        var collapsedView = island.querySelector('.df_an_collapsed_view');
        var expandedView  = island.querySelector('.df_an_expanded_view');

        // Run fn after the island's height transition ends (with a timeout fallback).
        function afterHeightTransition(fn) {
            var done = false;
            function handler(e) {
                if (e && e.target === island && e.propertyName !== 'height') return;
                if (done) return;
                done = true;
                island.removeEventListener('transitionend', handler);
                fn();
            }
            island.addEventListener('transitionend', handler);
            setTimeout(handler, 700);
        }

        // Measure the island's natural content height at the FINAL expanded width,
        // with transitions off so width is applied instantly (avoids a too-tall reading).
        function measureOpenHeight() {
            var prevTransition = island.style.transition;
            var wasOpen        = island.classList.contains('df_an_open');
            island.style.transition = 'none';
            island.classList.add('df_an_open');
            island.style.height = 'auto';
            var h = island.offsetHeight;
            island.style.height = '';
            if (!wasOpen) island.classList.remove('df_an_open');
            void island.offsetHeight;
            island.style.transition = prevTransition;
            return h;
        }

        function setOpen(open) {
            if (open) {
                // Opening: restore the springy height ease + the open-state cross-fade timing
                // (clears any closing overrides set in the else branch below).
                island.style.transition = '';
                if (expandedView) { expandedView.style.transition = ''; expandedView.style.opacity = ''; }
                if (collapsedView) collapsedView.style.transition = '';
                var startH  = island.offsetHeight;
                var targetH = measureOpenHeight();
                island.classList.add('df_an_open');
                moduleEl.classList.add('df_an_is_open');
                island.style.height = startH + 'px';
                void island.offsetHeight;
                island.style.height = targetH + 'px';
                afterHeightTransition(function () {
                    if (island.classList.contains('df_an_open')) {
                        island.style.height = 'auto';
                    }
                });
                island.setAttribute('aria-expanded', 'true');
                if (collapsedView) collapsedView.setAttribute('aria-hidden', 'true');
                if (expandedView) expandedView.setAttribute('aria-hidden', 'false');
            } else {
                // Closing: show the trigger immediately and fade the expanded icons out fast so
                // they're gone before the box visibly shrinks; use a non-overshoot height ease so
                // the panel doesn't bounce/clip on the way down (the icons appeared to "collapse
                // above the menu" because the 0.25s fade lagged the 0.55s springy height collapse).
                island.style.transition = 'width 0.55s cubic-bezier(.4,0,.2,1), height 0.55s cubic-bezier(.4,0,.2,1), background-color 0.35s ease';
                // Hide the icons/content INSTANTLY (transition: none) so they never show — even
                // fading, they'd flash over/around the shrinking pill (they're wider than it).
                if (expandedView) { expandedView.style.transition = 'none'; expandedView.style.opacity = '0'; }
                if (collapsedView) collapsedView.style.transition = 'opacity 0.12s ease 0s';
                var curH = island.offsetHeight;
                island.style.height = curH + 'px';
                void island.offsetHeight;
                island.classList.remove('df_an_open');
                moduleEl.classList.remove('df_an_is_open');
                var collapsedH = island.parentElement ? island.parentElement.clientHeight : curH;
                island.style.height = collapsedH + 'px';
                afterHeightTransition(function () {
                    if (!island.classList.contains('df_an_open')) {
                        island.style.height = '';
                    }
                });
                island.setAttribute('aria-expanded', 'false');
                if (collapsedView) collapsedView.setAttribute('aria-hidden', 'false');
                if (expandedView) expandedView.setAttribute('aria-hidden', 'true');
            }
        }

        function toggle() {
            setOpen(!island.classList.contains('df_an_open'));
        }

        island.addEventListener('click', function (e) {
            if (island.classList.contains('df_an_open')) {
                var t = e.target;
                while (t && t !== island) {
                    if (t.tagName === 'A' || t.tagName === 'BUTTON' || t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT') {
                        return;
                    }
                    t = t.parentNode;
                }
            }
            e.stopPropagation();
            toggle();
        });

        island.addEventListener('keydown', function (e) {
            if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                toggle();
            }
            if (e.key === 'Escape' && island.classList.contains('df_an_open')) {
                setOpen(false);
                island.focus();
            }
        });

        // Always close the open island when clicking outside the module.
        document.addEventListener('click', function (e) {
            if (!island.classList.contains('df_an_open')) {
                return;
            }
            if (!moduleEl.contains(e.target)) {
                setOpen(false);
            }
        });

        if (openByDefault) {
            setOpen(true);
        }
    }

    /* ---------- Navigation Dock (macOS-style magnification) ----------
       Discrete, distance-based magnify: on mouseenter of an item, every item's width/height
       is set from its INDEX distance to the hovered one (0 → full, 1 → half, 2 → quarter,
       3+ → none). Real width/height changes (NOT transform: scale) so align-items can push
       the items along the magnify direction from a fixed baseline. Reset on dock mouseleave. */
    function setupDock(moduleEl, wrapper) {
        var dock = moduleEl.querySelector('.df_an_dock');
        if (!dock) {
            return;
        }

        var items = Array.prototype.slice.call(dock.querySelectorAll('.difl_animatednavigationitem'));
        if (!items.length) {
            return;
        }

        // The element we actually resize is the inner card (.df_an_item), NOT the
        // .difl_animatednavigationitem wrapper: the wrapper holds the in-flow separator (::after)
        // and the card is width/height:100%, so pinning a size on the wrapper would stretch the
        // card across the separator. Resizing the card keeps it square; the wrapper tracks it.
        var cards = items.map(function (it) {
            return (it.querySelector && it.querySelector('.df_an_item')) || it;
        });

        // Magnify scales the WHOLE card (icon + text + their gaps + padding) uniformly via a single
        // transform, and reserves the grown footprint with a margin so neighbours push apart.
        // Inline so it works without a SCSS rebuild.
        cards.forEach(function (c) { c.style.transition = 'transform 0.22s cubic-bezier(.25,.8,.35,1)'; });
        items.forEach(function (it) { it.style.transition = 'margin 0.22s cubic-bezier(.25,.8,.35,1)'; });

        // Mobile: let items wrap onto the next line instead of overflowing the viewport (inline so
        // it's live without a build; mirrored in SCSS). Toggled on the phone breakpoint.
        var phone = window.matchMedia('(max-width: 767px)');
        function applyWrap() {
            if (phone.matches) {
                dock.style.flexWrap = 'wrap';
                dock.style.justifyContent = 'center';
                dock.style.rowGap = 'var(--df_an_gap, 12px)'; // vertical spacing between wrapped rows
            } else {
                dock.style.flexWrap = '';
                dock.style.justifyContent = '';
                dock.style.rowGap = '';
            }
        }
        applyWrap();
        if (phone.addEventListener) { phone.addEventListener('change', applyWrap); }
        else if (phone.addListener) { phone.addListener(applyWrap); }

        // Peak magnification factor for the hovered item (from the Magnification control).
        var maxScale = parseFloat(wrapper.getAttribute('data-magnification'));
        if (isNaN(maxScale) || maxScale < 1) { maxScale = 1.6; }
        if (maxScale === 1) { return; }

        // Desktop-only (precise pointer); disabled on tablet + phone (≤1020px).
        var smallScreen = window.matchMedia('(max-width: 1020px)');
        var vertical = wrapper.getAttribute('data-orientation') === 'vertical';

        // Grow direction → the edge items anchor to (opposite edge stays put). Top = grow up.
        var magnifyDir = (String(moduleEl.className).match(/df_an_magnify_(top|bottom|left|right)/) || [])[1] || 'top';
        var crossAlign = 'center';
        if (vertical) { // cross axis is horizontal
            if (magnifyDir === 'left') { crossAlign = 'flex-end'; }
            else if (magnifyDir === 'right') { crossAlign = 'flex-start'; }
        } else { // cross axis is vertical
            if (magnifyDir === 'top') { crossAlign = 'flex-end'; }
            else if (magnifyDir === 'bottom') { crossAlign = 'flex-start'; }
        }
        dock.style.alignItems = crossAlign;

        // Anchor point so the scaled card grows the right way (opposite edge stays put).
        var origin = vertical
            ? (magnifyDir === 'right' ? 'left center' : (magnifyDir === 'left' ? 'right center' : 'center'))
            : (magnifyDir === 'bottom' ? 'center top' : 'center bottom');
        cards.forEach(function (c) { c.style.transformOrigin = origin; });

        // Each card's natural box, captured at rest — used to compute the reserved margin.
        var baseW = [], baseH = [], measured = false;
        function setBase() {            // reset to rest: no scale, no reserved margin
            for (var i = 0; i < cards.length; i++) {
                cards[i].style.transform = '';
                items[i].style.margin = '';
                items[i].style.removeProperty('--df_an_sep_shift');
            }
        }
        function measure() {
            // Clear any transform/margin so we read each card's natural (control-defined) size.
            for (var k = 0; k < cards.length; k++) { cards[k].style.transform = ''; items[k].style.margin = ''; }
            for (var i = 0; i < cards.length; i++) {
                baseW[i] = cards[i].offsetWidth;
                baseH[i] = cards[i].offsetHeight;
            }
            measured = true;
        }

        if (document.readyState === 'complete') { measure(); }
        else { window.addEventListener('load', measure); }
        window.addEventListener('resize', measure);

        // Distance falloff (mirrors the reference 48/56/64/80 ratios, scaled to maxScale).
        function factor(dist) {
            if (dist === 0) { return maxScale; }
            if (dist === 1) { return 1 + (maxScale - 1) * 0.5; }
            if (dist === 2) { return 1 + (maxScale - 1) * 0.25; }
            return 1;
        }

        function applyScales(hoveredIndex) {
            if (smallScreen.matches) { reset(); return; }
            if (!measured) { measure(); }
            for (var i = 0; i < cards.length; i++) {
                var f = factor(Math.abs(i - hoveredIndex));
                cards[i].style.transform = f === 1 ? '' : 'scale(' + f + ')';
                var extra = (f - 1) * (vertical ? baseH[i] : baseW[i]) / 2;
                if (vertical) {
                    items[i].style.marginTop = extra + 'px';
                    items[i].style.marginBottom = extra + 'px';
                    items[i].style.setProperty('--df_an_sep_shift', extra + 'px');
                } else {
                    items[i].style.marginLeft = extra + 'px';
                    items[i].style.marginRight = extra + 'px';
                    items[i].style.setProperty('--df_an_sep_shift', extra + 'px');
                }
            }
            // Tooltip is anchored to the card, which is now transform-scaled and shifting — keep
            // its Popper position in sync while the magnify transition plays.
            syncTip(cards[hoveredIndex]);
        }

        // Re-position the hovered item's Tippy each frame for the magnify transition duration.
        function syncTip(card) {
            var start = null;
            function step(ts) {
                if (start === null) { start = ts; }
                var tip = card && card._tippy;
                if (tip && tip.state && tip.state.isVisible && tip.popperInstance) {
                    tip.popperInstance.update();
                }
                if (ts - start < 300) { requestAnimationFrame(step); }
            }
            requestAnimationFrame(step);
        }

        function reset() {
            setBase();
        }

        items.forEach(function (item, index) {
            item.addEventListener('mouseenter', function () { applyScales(index); });
        });
        dock.addEventListener('mouseleave', reset);

        // Crossing into a responsive viewport mid-magnify: flatten back to base size.
        var onBreakpoint = function () { if (smallScreen.matches) { reset(); } };
        if (smallScreen.addEventListener) {
            smallScreen.addEventListener('change', onBreakpoint);
        } else if (smallScreen.addListener) {
            smallScreen.addListener(onBreakpoint); // Safari < 14 fallback
        }

        // Touch: no magnification, ensure base size.
        reset();
    }

    /* ---------- Expandable Tab — Icon Only + Expand Title (rAF reveal) ----------
       Animates the label width 0 → its measured natural width (CSS can't tween to auto),
       toggling display:none↔inline-block so it's fully out of layout when collapsed.
       Spacing uses the flex gap. cubic-bezier(.33,1,.68,1) ≈ easeOutCubic. One open at a time. */
    function setupTab(moduleEl) {
        var tab = moduleEl.querySelector('.df_an_tab');
        if (!tab) {
            return;
        }
        // Only the Icon-Only + Expand-Title combination uses the JS reveal.
        if (!moduleEl.classList.contains('df_an_tab_display_icon') ||
            !moduleEl.classList.contains('df_an_tab_hover_expand')) {
            return;
        }

        var DURATION = 300;
        var GAP      = 8;
        var ease     = function (t) { return 1 - Math.pow(1 - t, 3); }; // easeOutCubic ≈ cubic-bezier(.33,1,.68,1)
        var items    = Array.prototype.slice.call(tab.querySelectorAll('.df_an_item'));
        var current  = null;

        function raf(onFrame, onDone) {
            var start = null, id;
            function step(ts) {
                if (start === null) start = ts;
                var p = Math.min(1, (ts - start) / DURATION);
                onFrame(ease(p));
                if (p < 1) { id = requestAnimationFrame(step); }
                else if (onDone) { onDone(); }
            }
            id = requestAnimationFrame(step);
            return function cancel() { cancelAnimationFrame(id); };
        }

        // Reveal targets = the nested elements AND the title, in flow order (content, then label).
        function revealTargets(item) {
            return ['.df_an_item_content', '.df_an_item_label']
                .map(function (sel) { return item.querySelector(sel); })
                .filter(Boolean);
        }

        function measure(el) {
            var prevPos = el.style.position, prevVis = el.style.visibility, prevW = el.style.width, prevD = el.style.display;
            el.style.display = 'inline-block';
            el.style.position = 'absolute';
            el.style.visibility = 'hidden';
            el.style.width = 'auto';
            var w = Math.ceil(el.getBoundingClientRect().width);
            el.style.position = prevPos;
            el.style.visibility = prevVis;
            el.style.width = prevW;
            el.style.display = prevD;
            return w;
        }

        function expand(item) {
            var targets = revealTargets(item);
            if (!targets.length) { return; }
            if (item._anCancel) { item._anCancel(); }
            var metrics = targets.map(function (el) {
                el.style.display = 'inline-block';
                el.style.overflow = 'hidden';
                el.style.whiteSpace = 'nowrap';
                return { el: el, w: measure(el) };
            });
            item._anCancel = raf(function (e) {
                metrics.forEach(function (m) {
                    m.el.style.width = (m.w * e) + 'px';
                    m.el.style.opacity = e;
                });
                item.style.gap = (GAP * e) + 'px';
            }, function () {
                metrics.forEach(function (m) { m.el.style.width = 'auto'; }); // settle so it reflows
                item._anCancel = null;
            });
        }

        function collapse(item) {
            var targets = revealTargets(item);
            if (!targets.length) { return; }
            if (item._anCancel) { item._anCancel(); }
            var metrics = targets.map(function (el) { return { el: el, w: el.offsetWidth }; });
            var startGap = parseFloat(window.getComputedStyle(item).gap) || GAP;
            item._anCancel = raf(function (e) {
                var k = 1 - e;
                metrics.forEach(function (m) {
                    m.el.style.width = (m.w * k) + 'px';
                    m.el.style.opacity = k;
                });
                item.style.gap = (startGap * k) + 'px';
            }, function () {
                metrics.forEach(function (m) {
                    m.el.style.display = 'none';
                    m.el.style.width = '0px';
                    m.el.style.opacity = '0';
                });
                item.style.gap = '0px';
                item._anCancel = null;
            });
        }

        items.forEach(function (item) {
            function open() {
                if (current && current !== item) { collapse(current); }
                current = item;
                expand(item);
            }
            function close() {
                if (current === item) { current = null; }
                collapse(item);
            }
            item.addEventListener('mouseenter', open);
            item.addEventListener('mouseleave', close);
            // Keyboard focus reveals the label for accessibility, but a mouse CLICK also sets :focus
            // (not :focus-visible) — so gate on :focus-visible to avoid re-expanding on click. Hover handles the rest.
            item.addEventListener('focusin', function (e) {
                var t = e.target;
                if (t && typeof t.matches === 'function' && t.matches(':focus-visible')) { open(); }
            });
            item.addEventListener('focusout', function (e) {
                if (!item.contains(e.relatedTarget)) { close(); }
            });
        });
    }

    /* ---------- Tooltip (Tippy.js) — Dock + Expandable Tab, icon-only ----------
       Settings come from the wrapper's data-tooltip JSON (emitted by PHP). Each item's
       hidden .df_an_item_label supplies the tooltip text. Tippy is appended inside the
       module root so the scoped Tooltip / Tooltip Text design styles apply. */
    function setupTooltips(moduleEl, wrapper) {
        var raw = wrapper.getAttribute('data-tooltip');
        if (!raw || typeof window.tippy !== 'function') {
            return;
        }
        var s;
        try { s = JSON.parse(raw); } catch (e) { return; }

        // Per-module theme = order class (with leading dot) so the scoped .tippy-box
        // design styles match a body-appended tooltip.
        var orderClass = '';
        var classes = (moduleEl.className || '').split(/\s+/);
        for (var c = 0; c < classes.length; c++) {
            if (/^difl_animatednavigation_/.test(classes[c])) {
                orderClass = '.' + classes[c];
                break;
            }
        }

        // Escape the title text (it's combined with the elements' HTML under allowHTML: true).
        function esc(t) { var d = document.createElement('div'); d.textContent = t; return d.innerHTML; }

        var items = Array.prototype.slice.call(moduleEl.querySelectorAll('.df_an_item'));
        items.forEach(function (item) {
            if (item._tippy) { item._tippy.destroy(); }

            var labelEl   = item.querySelector('.df_an_item_label');
            var contentEl = item.querySelector('.df_an_item_content');
            var title     = labelEl ? labelEl.textContent.trim() : '';
            var elHtml    = contentEl ? contentEl.innerHTML.trim() : '';

            var html = '';
            if (title)  { html += '<div class="df_an_tip_title">' + esc(title) + '</div>'; }
            if (elHtml) { html += '<div class="df_an_tip_content">' + elHtml + '</div>'; }
            if (!html) { return; }

            window.tippy(item, {
                content: html,
                allowHTML: true,
                arrow: !!s.arrow,
                animation: s.animation || 'fade',
                placement: s.placement || 'top',
                theme: orderClass,
                // Append to body — dock items have CSS transforms (containing block) that
                // would otherwise mis-position a nested tooltip.
                appendTo: function () { return document.body; }
            });
        });
    }

    /* ---------- Item links — hash links toggle an active state ----------
       Normal links navigate as usual (no JS). For in-page hash links (#target),
       clicking marks that item active (and clears its siblings); the browser still
       scrolls to the anchor. On load, the item matching location.hash starts active. */
    function setupItemLinks(moduleEl) {
        var hashLinks = moduleEl.querySelectorAll('.df_an_item_link[href^="#"]');
        if (!hashLinks.length) {
            return;
        }
        var allItems = moduleEl.querySelectorAll('.df_an_item');

        function activate(item) {
            for (var i = 0; i < allItems.length; i++) {
                allItems[i].classList.remove('df_an_active');
            }
            if (item) { item.classList.add('df_an_active'); }
        }

        Array.prototype.forEach.call(hashLinks, function (link) {
            link.addEventListener('click', function (e) {
                var hash = link.getAttribute('href');
                var target = (hash && hash.length > 1) ? document.getElementById(hash.slice(1)) : null;
                if (target) {
                    // Smooth-scroll to the section instead of an instant jump (custom rAF
                    // tween so it animates reliably regardless of native/OS settings).
                    e.preventDefault();
                    smoothScrollTo(target, 0);
                    if (window.history && history.replaceState) {
                        history.replaceState(null, '', hash);
                    }
                }
                activate(link.closest('.df_an_item'));
            });
        });

        // Sync the active item from the current URL hash on load.
        if (window.location.hash) {
            for (var i = 0; i < hashLinks.length; i++) {
                if (hashLinks[i].getAttribute('href') === window.location.hash) {
                    activate(hashLinks[i].closest('.df_an_item'));
                    break;
                }
            }
        }
    }

    function setupModule(moduleEl) {
        if (initialized.has(moduleEl)) {
            return;
        }
        initialized.add(moduleEl);

        var wrapper = moduleEl.querySelector('.df_an_wrapper');
        if (!wrapper) {
            return;
        }

        var navType = wrapper.getAttribute('data-nav-type') || 'dynamic_island';

        if (navType === 'navigation_dock') {
            // Hover Effect decides the behavior: magnify (.df_an_dock) / expand label (.df_an_tab) / none.
            if (moduleEl.classList.contains('df_an_tab_hover_expand')) {
                setupTab(moduleEl);
            } else if (moduleEl.classList.contains('df_an_tab_hover_magnify')) {
                setupDock(moduleEl, wrapper);
            }
            setupTooltips(moduleEl, wrapper);
        } else if (navType === 'dynamic_island') {
            setupIsland(moduleEl, wrapper);
        }

        // Item links / hash-link active state — applies to every nav type.
        setupItemLinks(moduleEl);
    }

    function initAll() {
        var modules = document.querySelectorAll('.difl_animatednavigation');
        for (var i = 0; i < modules.length; i++) {
            setupModule(modules[i]);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initAll);
    } else {
        initAll();
    }

    document.addEventListener('et_pb_after_init_modules', initAll);
})();
