(gina.ready(function onGinaReady($){
/**
 * Home handler
 */

var HomeHandler = ( function onHomeHandled() {
    var self                = {};

    var init = function() {
        handle();
    }


    var handle = function () {

        document.querySelectorAll('a.animateScrollTo').forEach(function(el) { el.addEventListener('click', function(event) {
            event.preventDefault();
            var
                href        = this.getAttribute('href'),
                element     = document.querySelector(href)
                ;
            animateScrollTo(element);
            document.getElementById('registration-email').focus();
        }); });

        // Plan billing period toggle (cf. plan §8.10 — mirroirs dashboard subscription pattern).
        // The card price AND the popover title each carry .price-monthly / .price-yearly spans (scoped to #tarifs) ;
        // we toggle the native [hidden] attribute on each depending on the active radio. The DS
        // public bundle ships no `.hidden` utility class (legacy public.css did) — the UA rule
        // `[hidden]{display:none}` (present in ds-public.css) does the hiding, and [hidden] also
        // removes the inactive price from the accessibility tree.
        document.querySelectorAll('input[name="home-plan-billing"]').forEach(function(radio) {
            radio.addEventListener('change', function() {
                var period         = this.value;
                var monthlyEls     = document.querySelectorAll('#tarifs .price-monthly');
                var yearlyEls      = document.querySelectorAll('#tarifs .price-yearly');
                monthlyEls.forEach(function(el) {
                    el.hidden = (period !== 'monthly');
                });
                yearlyEls.forEach(function(el) {
                    el.hidden = (period !== 'yearly');
                });
            });
        });

        // Map-marks now use the native Popover API via <button popovertarget> (cf. plan §14 commit 7).
        // The previous preventDefault loop guarded against the <a href="#"> jump-to-top — obsolete.

        // Popover positioning fallback (cf. plan §5.1 — anchoring 2026-05-12).
        // CSS Anchor Positioning is only native on Chrome 125+ today ; Safari/Firefox would otherwise
        // render the popovers as centered modals (the .map-mark-popover / .home-plan-popover :popover-open
        // fallback). Rather than vendor the Oddbird polyfill chain (~10KB, deferred to commit 7ter),
        // we JS-position each popover near its trigger on beforetoggle — works on every browser that
        // supports the Popover API itself. The centered modal SASS rules stay as last-resort fallback
        // if this JS errors out or never runs.
        // Both orientation (below vs above) and horizontal placement (left + arrow-x) are
        // locked at open time and stored on the popover's dataset. Subsequent scroll-reposition
        // calls only recompute the vertical coord — the page only scrolls vertically, so X
        // never needs to track scrolling, and freezing it sidesteps any `window.innerWidth`
        // shift that could happen mid-scroll (macOS overlay scrollbar appearing, browser
        // chrome change, etc.) and any popW shift between the first open (offsetWidth=0 →
        // CSS-mirrored fallback) and subsequent calls (offsetWidth=real value).
        var positionPopover = function (popover, trigger, opts) {
            opts = opts || {};
            var rect    = trigger.getBoundingClientRect();
            var pad     = 8;
            var gap     = 12;
            var vpH     = window.innerHeight;
            var vpW     = window.innerWidth;

            // Horizontal placement : compute on first open, freeze on scroll.
            var left, arrowX;
            if (typeof(opts.left) != 'undefined') {
                left   = opts.left;
                arrowX = opts.arrowX;
            } else {
                // popW : on first open, offsetWidth reads 0 (the popover is still display:none
                // during beforetoggle), so falling back to a single constant would mis-centre
                // .home-plan-popover (360px max-width) by 30px. Mirror the SASS
                // `width: calc(100vw - 32px); max-width: <360|300>px` exactly.
                var popW = popover.offsetWidth;
                if (!popW) {
                    var maxW = popover.classList.contains('home-plan-popover') ? 360 : 300;
                    popW = Math.min(vpW - 32, maxW);
                }

                // Center on the trigger, clamp inside the viewport.
                left        = rect.left + (rect.width / 2) - (popW / 2);
                var maxLeft = vpW - popW - pad;
                if (left < pad)     { left = pad; }
                if (left > maxLeft) { left = maxLeft; }

                // Arrow horizontal alignment : the popover is clamped to the viewport, so its
                // horizontal centre is not always the trigger's centre. Compute the arrow's
                // left offset relative to the popover so it visually points at the trigger.
                var triggerCenterX = rect.left + (rect.width / 2);
                arrowX             = triggerCenterX - left;
                // Clamp inside the popover so the arrow can't visually escape its rounded corners.
                if (arrowX < 16)        { arrowX = 16; }
                if (arrowX > popW - 16) { arrowX = popW - 16; }

                popover.dataset.popoverLeft  = left;
                popover.dataset.popoverArrow = arrowX;
            }

            // Vertically : on first open, place below if trigger sits in the top half of the
            // viewport, else above. Subsequent reposition calls (scroll) reuse the stored
            // choice via opts.below so orientation doesn't flip mid-scroll.
            var below;
            if (typeof(opts.below) != 'undefined') {
                below = opts.below;
            } else {
                below = rect.top < vpH / 2;
                popover.dataset.popoverBelow = below ? '1' : '0';
            }

            // `position: fixed` is required because [popover] elements render in the top layer,
            // whose containing block is always the viewport regardless of `position`. We
            // compensate for that by re-running this function on scroll : the trigger's
            // getBoundingClientRect() updates as the user scrolls, so the popover tracks it
            // and reads as "anchored to the page" rather than to the viewport.
            popover.style.position  = 'fixed';
            popover.style.inset     = 'auto';
            popover.style.left      = left + 'px';
            popover.style.transform = 'none';

            if (below) {
                popover.style.top    = (rect.bottom + gap) + 'px';
                popover.style.bottom = 'auto';
                popover.classList.remove('is-above');
            } else {
                popover.style.top    = 'auto';
                popover.style.bottom = (vpH - rect.top + gap) + 'px';
                popover.classList.add('is-above');
            }

            popover.style.setProperty('--arrow-x', arrowX + 'px');
        };

        // Centre a plan-details popover on its CARD (not its trigger), so the popover — which
        // mirrors the card markup — reads as the card "unfolded". We set ONLY the x/y origin (px);
        // the SASS `.home-plan-popover { transform: translate(-50%, -50%) scale() }` does the
        // size-centering + zoom, so this never needs the popover's own width/height (which read 0
        // during beforetoggle) and never touches `transform` (CSS owns it for the zoom). On the
        // later `toggle` pass the popover is shown (real offsetWidth/Height), so we also clamp the
        // centre into the viewport — left/top aren't transitioned, so that correction is instant
        // and hidden under the opacity ramp-in.
        var centerPopoverOnCard = function (popover) {
            var trigger = document.querySelector('[popovertarget="' + popover.id + '"]');
            var card    = trigger ? trigger.closest('.home-plan') : null;
            if (!card) { return; }

            var rect = card.getBoundingClientRect();
            var cx   = rect.left + (rect.width / 2);
            var cy   = rect.top + (rect.height / 2);

            var popW = popover.offsetWidth;
            var popH = popover.offsetHeight;
            if (popW && popH) {
                var pad   = 8;
                var halfW = popW / 2;
                var halfH = popH / 2;
                if (cx < pad + halfW)                      { cx = pad + halfW; }
                if (cx > window.innerWidth - pad - halfW)  { cx = window.innerWidth - pad - halfW; }
                if (cy < pad + halfH)                      { cy = pad + halfH; }
                if (cy > window.innerHeight - pad - halfH) { cy = window.innerHeight - pad - halfH; }
            }

            popover.style.position = 'fixed';
            popover.style.inset    = 'auto';
            popover.style.bottom   = 'auto';
            popover.style.left     = cx + 'px';
            popover.style.top      = cy + 'px';
        };

        // Map-mark tooltips : anchored near their trigger (tooltip-style, via positionPopover).
        document.querySelectorAll('.map-mark-popover').forEach(function (popover) {
            popover.addEventListener('beforetoggle', function (e) {
                if (e.newState !== 'open') {
                    // Clear the locked layout on close so the next open recomputes against
                    // the current trigger position + viewport state.
                    delete popover.dataset.popoverBelow;
                    delete popover.dataset.popoverLeft;
                    delete popover.dataset.popoverArrow;
                    return;
                }
                var trigger = document.querySelector('[popovertarget="' + popover.id + '"]');
                if (trigger) { positionPopover(popover, trigger); }
            });
        });

        // Plan-details popovers : modal-like reveal centred ON THEIR CARD with a CSS zoom-in.
        // Centre on beforetoggle (pre-show → no position flash) and again on toggle (popover now
        // measurable → clamp into the viewport).
        document.querySelectorAll('.home-plan-popover').forEach(function (popover) {
            popover.addEventListener('beforetoggle', function (e) {
                if (e.newState === 'open') { centerPopoverOnCard(popover); }
            });
            popover.addEventListener('toggle', function (e) {
                if (e.newState === 'open') { centerPopoverOnCard(popover); }
            });
        });

        // Map-marks open on HOVER (desktop affordance) on top of the native click-to-toggle
        // (popovertarget) — touch users keep click since they have no hover. A short close delay
        // lets the pointer travel from the mark into the popover (to read/select its text) without
        // it vanishing ; entering the popover cancels the pending close.
        // prefers-reduced-motion : hover only moves FOCUS to the mark (no auto-open) — opening stays
        // user-initiated via click/Enter, so the popover never appears (animates in) unrequested.
        var reduceMotion     = window.matchMedia('(prefers-reduced-motion: reduce)');
        var hoverCloseTimers = {};

        var openOnHover = function (trigger) {
            var id = trigger.getAttribute('popovertarget');
            if (!id) { return; }
            var popover = document.getElementById(id);
            if (!popover || typeof popover.showPopover !== 'function') { return; }
            if (hoverCloseTimers[id]) { clearTimeout(hoverCloseTimers[id]); delete hoverCloseTimers[id]; }
            if (!popover.matches(':popover-open')) {
                try { popover.showPopover(); } catch (_) { /* already open / not connected */ }
            }
        };

        var closeOnHover = function (id) {
            var popover = document.getElementById(id);
            if (!popover || typeof popover.hidePopover !== 'function') { return; }
            hoverCloseTimers[id] = setTimeout(function () {
                delete hoverCloseTimers[id];
                if (popover.matches(':popover-open')) {
                    try { popover.hidePopover(); } catch (_) { /* noop */ }
                }
            }, 140);
        };

        document.querySelectorAll('.mapMark[popovertarget], .mapMarkExtra[popovertarget]').forEach(function (trigger) {
            var id      = trigger.getAttribute('popovertarget');
            var popover = id ? document.getElementById(id) : null;

            trigger.addEventListener('mouseenter', function () {
                if (reduceMotion.matches) {
                    // Reduced motion : hover only focuses the mark ; opening stays user-initiated.
                    trigger.focus();
                    return;
                }
                openOnHover(trigger);
            });
            trigger.addEventListener('mouseleave', function () {
                if (reduceMotion.matches) { return; }
                closeOnHover(id);
            });

            // Keep the popover open while the pointer is over it ; close on leave.
            if (popover) {
                popover.addEventListener('mouseenter', function () {
                    if (reduceMotion.matches) { return; }
                    if (hoverCloseTimers[id]) { clearTimeout(hoverCloseTimers[id]); delete hoverCloseTimers[id]; }
                });
                popover.addEventListener('mouseleave', function () {
                    if (reduceMotion.matches) { return; }
                    closeOnHover(id);
                });
            }
        });

        // Anchor-to-page on scroll : re-run positionPopover against the open popover's
        // trigger so the popover tracks the trigger as the user scrolls. rAF-throttled
        // to coalesce multiple scroll events per frame ; passive listener so we don't
        // block the scroll. Orientation + horizontal placement are read from dataset
        // (locked at open time) ; only the vertical coord recomputes against the
        // trigger's current viewport position.
        var scrollRafScheduled = false;
        // Only the map-mark tooltips track the trigger on scroll. The plan-details popovers are
        // modal-like (dimmed/blurred DS backdrop) and stay centred where they opened.
        var repositionOpenPopovers = function () {
            document.querySelectorAll('.map-mark-popover').forEach(function (popover) {
                if (popover.matches && popover.matches(':popover-open')) {
                    var trigger = document.querySelector('[popovertarget="' + popover.id + '"]');
                    if (trigger) {
                        positionPopover(popover, trigger, {
                            below  : popover.dataset.popoverBelow === '1',
                            left   : parseFloat(popover.dataset.popoverLeft),
                            arrowX : parseFloat(popover.dataset.popoverArrow)
                        });
                    }
                }
            });
        };
        window.addEventListener('scroll', function () {
            if (scrollRafScheduled) { return; }
            scrollRafScheduled = true;
            requestAnimationFrame(function () {
                scrollRafScheduled = false;
                repositionOpenPopovers();
            });
        }, { passive: true });

        // On resize : hide any open popover ; the user re-opens at the new viewport size and the
        // beforetoggle handler recomputes against the fresh trigger rect.
        window.addEventListener('resize', function () {
            document.querySelectorAll('.map-mark-popover, .home-plan-popover').forEach(function (popover) {
                if (popover.matches && popover.matches(':popover-open')) {
                    try { popover.hidePopover(); } catch (_) { /* noop */ }
                }
            });
        });

        // Sticky CTA dock (cf. plan §5.3 + §11.1 — refactor 2026-05-12).
        // The dock takes over as a fixed bar only in the MIDDLE of the page : once the hero
        // #btn-big-home-subscription has scrolled off, and as long as the #creez-votre-compte
        // registration section has NOT yet come into view. Reaching the real form makes the
        // floating CTA redundant, so the dock hides again.
        // Default observer (no rootMargin) fires when any part of the watched element crosses the
        // viewport boundary ; the dock state is derived from both observers' latest readings.
        var heroCta      = document.getElementById('btn-big-home-subscription');
        var registration = document.getElementById('creez-votre-compte');
        var dock         = document.querySelector('.home-cta-dock');
        if (heroCta && dock && typeof(IntersectionObserver) != 'undefined') {
            var heroVisible         = true;   // top of page : hero CTA on screen
            var registrationVisible = false;  // bottom section not yet reached
            var syncDock = function () {
                if (!heroVisible && !registrationVisible) {
                    document.body.classList.add('is-cta-stuck');
                    dock.removeAttribute('aria-hidden');
                    dock.removeAttribute('inert');
                } else {
                    document.body.classList.remove('is-cta-stuck');
                    dock.setAttribute('aria-hidden', 'true');
                    dock.setAttribute('inert', '');
                }
            };
            var heroIo = new IntersectionObserver(function (entries) {
                heroVisible = entries[0].isIntersecting;
                syncDock();
            }, { threshold: 0 });
            heroIo.observe(heroCta);
            if (registration) {
                var registrationIo = new IntersectionObserver(function (entries) {
                    registrationVisible = entries[0].isIntersecting;
                    syncDock();
                }, { threshold: 0 });
                registrationIo.observe(registration);
            }
        }

        // Expand home feature document — same technique as the FAQ <details> accordion:
        // a native height:auto animation via `interpolate-size` (CSS) where supported,
        // with a Web-Animations height fallback otherwise (mirrors `animateDetails`).
        // `.home-features-document-bottom` sits in normal flow at height:0 (ds-public.css),
        // so its lazy <img> loads as the section nears the viewport and the open has a real
        // target height — no JS image-load juggling needed. supportsInterpolateSize is
        // computed below (shared with the FAQ accordion).
        document.getElementById('home-features-expand').addEventListener('click', function (event) {
            event.preventDefault();

            var btn    = this;
            var bottom = document.getElementById('home-features-document-bottom');
            var marks  = document.getElementById('home-features-document-marks');

            // fade out + remove the button
            btn.style.transition = 'opacity 0.2s ease';
            btn.style.opacity = '0';
            setTimeout(function () { btn.style.display = 'none'; }, 200);

            // `.mark-list` defaults to display:none (ds-public.css, ≥800px) — fade it in
            // once the section has finished opening.
            var revealMarks = function () {
                marks.style.opacity = '0';
                marks.style.display = 'block';
                marks.offsetHeight;
                marks.style.transition = 'opacity 0.4s ease';
                marks.style.opacity = '1';
            };

            if (supportsInterpolateSize) {
                // CSS path: `.is-expanded` transitions height 0 → auto natively.
                bottom.addEventListener('transitionend', function onOpen(e) {
                    if (e.propertyName !== 'height') { return; }
                    bottom.removeEventListener('transitionend', onOpen);
                    revealMarks();
                });
                bottom.classList.add('is-expanded');
            } else {
                // Fallback: WAAPI height animation (same primitive as animateDetails).
                var endHeight = bottom.scrollHeight + 'px';
                var animation = bottom.animate(
                    { height: ['0px', endHeight] },
                    { duration: 600, easing: 'ease-in', fill: 'forwards' }
                );
                animation.onfinish = function () {
                    bottom.classList.add('is-expanded'); // height:auto holds it open
                    animation.cancel();                  // drop the WAAPI fill
                    revealMarks();
                };
            }
        });

        // Preload the (lazy) bottom document image ahead of the expand, so the open has a
        // real target height instantly. Flipping `loading` to "eager" starts the fetch
        // regardless of viewport position. Triggered as soon as the top document preview
        // scrolls into view, or when the user hovers/focuses the expand button — whichever
        // comes first. Idempotent + a no-op once the image is loaded.
        (function preloadBottomDocument() {
            var bottomImg = document.querySelector('#home-features-document-bottom img');
            if ( !bottomImg || bottomImg.complete ) { return; }

            var preloaded = false;
            var preload   = function () {
                if (preloaded) { return; }
                preloaded = true;
                bottomImg.loading = 'eager';
            };

            var expandWrap = document.getElementById('home-features-expand');
            if (expandWrap) {
                expandWrap.addEventListener('mouseenter', preload);
                expandWrap.addEventListener('focusin', preload); // keyboard focus on the link
            }

            var topDocument = document.querySelector('.home-features-document');
            if (topDocument && typeof(IntersectionObserver) != 'undefined') {
                var io = new IntersectionObserver(function (entries) {
                    if (entries[0].isIntersecting) {
                        preload();
                        io.disconnect();
                    }
                }, { rootMargin: '200px' });
                io.observe(topDocument);
            }
        })();

        // FAQ open/close height animation — JS polyfill (port of the design-system
        // `js/utilities/details-polyfill.js` WAAPI accordion).
        // Modern browsers animate the <details> open/close natively via the CSS
        // `::details-content` block-size transition + `interpolate-size: allow-keywords`
        // (ds-public.css). This Web-Animations fallback drives the height manually and
        // runs ONLY where `interpolate-size` is unsupported, so the two techniques never
        // double-animate (the CSS path zeroes `::details-content` height, which would
        // otherwise break this fallback's content measurement).
        var supportsInterpolateSize = ( typeof(window.CSS) != 'undefined' )
            && typeof(CSS.supports) == 'function'
            && CSS.supports('interpolate-size', 'allow-keywords');

        if ( !supportsInterpolateSize ) {
            document.querySelectorAll('.home-faq-entry').forEach(function (details) {
                animateDetails(details);
            });
        }

    }

    /**
     * animateDetails
     * WAAPI height-animation polyfill for a single <details> accordion entry.
     * Intercepts the summary click, suppresses the native instant toggle, and animates
     * the element's height between its summary-only and summary+content extents.
     *
     * @param {Element} details
     * */
    var animateDetails = function (details) {
        var summary = details.querySelector('summary');
        var content = summary ? summary.nextElementSibling : null;
        // Need a summary, a content sibling, and WAAPI support to run the fallback.
        if ( !summary || !content || typeof(details.animate) != 'function' ) { return; }

        var animation   = null;
        var isClosing   = false;
        var isExpanding = false;
        var DURATION    = 400;

        var onAnimationFinish = function (open) {
            details.open           = open;
            animation              = null;
            isClosing              = false;
            isExpanding            = false;
            // Drop the fixed height + overflow so the entry flows naturally again.
            details.style.height   = '';
            details.style.overflow = '';
        };

        var shrink = function () {
            isClosing       = true;
            var startHeight = details.offsetHeight + 'px';
            var endHeight   = summary.offsetHeight + 'px';
            if (animation) { animation.cancel(); }
            animation          = details.animate({ height: [startHeight, endHeight] }, { duration: DURATION, easing: 'ease-out' });
            animation.onfinish = function () { onAnimationFinish(false); };
            animation.oncancel = function () { isClosing = false; };
        };

        var expand = function () {
            isExpanding     = true;
            var startHeight = details.offsetHeight + 'px';
            var endHeight   = (summary.offsetHeight + content.offsetHeight) + 'px';
            if (animation) { animation.cancel(); }
            animation          = details.animate({ height: [startHeight, endHeight] }, { duration: DURATION, easing: 'ease-out' });
            animation.onfinish = function () { onAnimationFinish(true); };
            animation.oncancel = function () { isExpanding = false; };
        };

        var open = function () {
            // Lock the current height, force [open], then animate to the full height
            // on the next frame (so the content has laid out and can be measured).
            details.style.height = details.offsetHeight + 'px';
            details.open         = true;
            window.requestAnimationFrame(expand);
        };

        summary.addEventListener('click', function (e) {
            e.preventDefault();
            details.style.overflow = 'hidden';
            if (isClosing || !details.open) {
                open();
            } else if (isExpanding || details.open) {
                shrink();
            }
        });
    }

    /**
     * animateScrollTo
     * scroll to element
     *
     * @param {Element} element
     * */
    var animateScrollTo = function(element) {
        var distance = element.getBoundingClientRect().top + window.scrollY;
        window.scrollTo({
            top: distance,
            behavior: 'smooth'
        });
    }

    init()

})();
},window[originalContext]));
