/**
 * Motuvai — the behaviours, one per block.
 *
 *  1. Reveals            blocks fade in and sharpen as they enter the screen
 *  2. Scroll progress    one --p per element, read by the stylesheet
 *  3. Navigation         burger panel and the "Company" disclosure
 *  4. Buttons            the label split into characters that roll up
 *  5. Rails              arrows, position announcement, blurred neighbours
 *  6. Questions          animated height, one panel open at a time
 *  7. Video              downloaded on click, never before
 *  8. Forms              validated here, sent nowhere
 *  9. Rating             counts up to its value once
 *
 * Nothing here is required to read the page: every block works without it,
 * only less gracefully.
 */

(function () {
  "use strict";

  var doucement = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  /* ==========================================================================
     1. Reveals
     The threshold is measured on the SCREEN, not on the element: a percentage
     of the element would leave anything taller than the viewport invisible
     forever. Blocks start appearing 15% above the bottom edge.
     ========================================================================== */

  var aReveler = document.querySelectorAll("[data-reveal]");

  if (!("IntersectionObserver" in window)) {
    aReveler.forEach(function (el) {
      el.classList.add("is-in");
    });
  } else {
    var guetteur = new IntersectionObserver(
      function (entrees) {
        entrees.forEach(function (entree) {
          if (!entree.isIntersecting) return;
          entree.target.classList.add("is-in");
          guetteur.unobserve(entree.target);
        });
      },
      { rootMargin: "0px 0px -15% 0px", threshold: 0 }
    );
    aReveler.forEach(function (el) {
      guetteur.observe(el);
    });

    /* Safety net: whatever is still hidden when the page bottom is reached
       gets shown. A short page, or a block sitting in the last 15%, would
       otherwise never trigger. */
    window.addEventListener(
      "scroll",
      function () {
        var restant = document.documentElement.scrollHeight - window.scrollY - window.innerHeight;
        if (restant > 4) return;
        aReveler.forEach(function (el) {
          el.classList.add("is-in");
        });
      },
      { passive: true }
    );
  }

  /* ==========================================================================
     2. Scroll progress
     One number per element, from 0 when its top touches the bottom of the
     screen to 1 when its bottom leaves the top. The stylesheet turns that
     number into a parallax offset or a straightening rotation — this file
     never decides what a movement looks like.
     ========================================================================== */

  var suivis = [];

  document.querySelectorAll("[data-parallax]").forEach(function (el) {
    suivis.push({ el: el, boite: el.parentElement, part: 1 });
  });

  document.querySelectorAll("[data-row]").forEach(function (el, i) {
    /* The first row is already in place when the section arrives: the
       original only animates the three that follow. */
    if (i === 0) return;
    suivis.push({ el: el, boite: el, part: 0.3 });
  });

  function progression(boite) {
    var r = boite.getBoundingClientRect();
    var course = window.innerHeight + r.height;
    if (course <= 0) return 1;
    var p = (window.innerHeight - r.top) / course;
    return p < 0 ? 0 : p > 1 ? 1 : p;
  }

  var enAttente = false;

  function rafraichir() {
    enAttente = false;
    suivis.forEach(function (s) {
      var p = progression(s.boite);
      if (s.part < 1) p = Math.min(1, p / s.part);
      s.el.style.setProperty("--p", p.toFixed(4));
    });
  }

  if (suivis.length && !doucement) {
    var demander = function () {
      if (enAttente) return;
      enAttente = true;
      requestAnimationFrame(rafraichir);
    };
    window.addEventListener("scroll", demander, { passive: true });
    window.addEventListener("resize", demander);
    rafraichir();
  }

  /* ==========================================================================
     3. Navigation
     ========================================================================== */

  var burger = document.querySelector(".nav__burger");
  var panneau = document.getElementById("nav-panel");
  var voile = document.querySelector(".nav__scrim");

  function ouvrirMenu(ouvert) {
    burger.setAttribute("aria-expanded", String(ouvert));
    panneau.classList.toggle("is-open", ouvert);
    voile.hidden = !ouvert;
  }

  if (burger && panneau && voile) {
    burger.addEventListener("click", function () {
      ouvrirMenu(burger.getAttribute("aria-expanded") !== "true");
    });
    voile.addEventListener("click", function () {
      ouvrirMenu(false);
    });
    panneau.addEventListener("click", function (e) {
      if (e.target.closest("a")) ouvrirMenu(false);
    });
    document.addEventListener("keydown", function (e) {
      if (e.key === "Escape") ouvrirMenu(false);
    });
  }

  /* "Company" opens on hover on the desktop bar and on click everywhere.
     Two delays on hover: a short one to open, a longer one to close, which is
     what lets the pointer cross the gap between the link and the panel. */
  document.querySelectorAll(".nav__disclose").forEach(function (bouton) {
    var parent = bouton.closest(".nav__item");
    var minuteur = null;

    function montrer(ouvert) {
      clearTimeout(minuteur);
      bouton.setAttribute("aria-expanded", String(ouvert));
    }

    bouton.addEventListener("click", function () {
      montrer(bouton.getAttribute("aria-expanded") !== "true");
    });

    parent.addEventListener("pointerenter", function (e) {
      if (e.pointerType === "touch" || window.innerWidth < 992) return;
      clearTimeout(minuteur);
      minuteur = setTimeout(function () {
        montrer(true);
      }, 80);
    });

    parent.addEventListener("pointerleave", function () {
      if (window.innerWidth < 992) return;
      clearTimeout(minuteur);
      minuteur = setTimeout(function () {
        montrer(false);
      }, 260);
    });

    parent.addEventListener("focusout", function (e) {
      if (!parent.contains(e.relatedTarget)) montrer(false);
    });
  });

  /* ==========================================================================
     4. Buttons
     The label is cut into characters and doubled: the copy sits one line
     below, so hovering rolls the whole word up by exactly its own height.
     Each character leaves a moment after the one before it.
     ========================================================================== */

  var ROULEMENT = 100; /* ms spread over the whole word, as in the original */

  document.querySelectorAll("[data-btn-text]").forEach(function (cible) {
    var mots = cible.textContent.trim();
    if (!mots) return;
    var lettres = Array.from(mots);
    var pas = (ROULEMENT / Math.max(lettres.length, 1)).toFixed(2) + "ms";

    cible.textContent = "";
    [0, 1].forEach(function (rang) {
      var ligne = document.createElement("span");
      ligne.className = "btn__roll";
      ligne.setAttribute("aria-hidden", rang === 1 ? "true" : "false");
      lettres.forEach(function (lettre, i) {
        var brique = document.createElement("span");
        brique.className = "btn__char";
        brique.textContent = lettre;
        brique.style.setProperty("--i", String(i));
        brique.style.setProperty("--stagger", pas);
        ligne.appendChild(brique);
      });
      cible.appendChild(ligne);
    });
  });

  /* ==========================================================================
     5. Rails
     The rail scrolls, swipes and takes focus on its own — that part is CSS
     scroll-snap. What follows is the arrows, the spoken position, and the
     blur on the cards away from the centre.
     ========================================================================== */

  document.querySelectorAll("[data-rail]").forEach(function (rail) {
    var piste = rail.querySelector("[data-rail-track]");
    var diapos = Array.prototype.slice.call(piste.children);
    var precedent = rail.querySelector("[data-rail-prev]");
    var suivant = rail.querySelector("[data-rail-next]");
    var etat = rail.querySelector("[data-rail-status]");
    var nom = rail.classList.contains("rail--quotes") ? "Story" : "Room";

    function largeurPas() {
      if (diapos.length < 2) return piste.clientWidth;
      return diapos[1].offsetLeft - diapos[0].offsetLeft;
    }

    function courante() {
      var centre = piste.scrollLeft + piste.clientWidth / 2;
      var meilleure = 0;
      var ecart = Infinity;
      diapos.forEach(function (d, i) {
        var e = Math.abs(d.offsetLeft + d.offsetWidth / 2 - centre);
        if (e < ecart) {
          ecart = e;
          meilleure = i;
        }
      });
      return meilleure;
    }

    function raconter() {
      var i = courante();
      diapos.forEach(function (d, j) {
        d.classList.toggle("is-far", j !== i);
      });
      if (etat) etat.textContent = nom + " " + (i + 1) + " of " + diapos.length;
      var fin = piste.scrollWidth - piste.clientWidth - 1;
      if (precedent) precedent.disabled = piste.scrollLeft <= 1;
      if (suivant) suivant.disabled = piste.scrollLeft >= fin;
    }

    function aller(sens) {
      piste.scrollBy({ left: sens * largeurPas(), behavior: doucement ? "auto" : "smooth" });
    }

    if (precedent) precedent.addEventListener("click", function () { aller(-1); });
    if (suivant) suivant.addEventListener("click", function () { aller(1); });

    var attente = null;
    piste.addEventListener(
      "scroll",
      function () {
        clearTimeout(attente);
        attente = setTimeout(raconter, 80);
      },
      { passive: true }
    );
    window.addEventListener("resize", raconter);
    raconter();
  });

  /* ==========================================================================
     6. Questions
     <details> already opens and closes on its own; this only gives it a
     height. Closing does not remove the panel from the render tree — the
     browser hides it with content-visibility — so the animation has to be
     restarted by hand each time rather than left to @starting-style.
     ========================================================================== */

  var faq = document.querySelector("[data-faq]");

  if (faq && !doucement) {
    faq.querySelectorAll("details").forEach(function (bloc) {
      var panneauFaq = bloc.querySelector(".faq__a");
      var enCours = null;

      function animer(vers, apres) {
        if (enCours) enCours.cancel();
        var depart = vers === "ouvre" ? 0 : panneauFaq.scrollHeight;
        var arrivee = vers === "ouvre" ? panneauFaq.scrollHeight : 0;
        enCours = panneauFaq.animate(
          [
            { height: depart + "px", opacity: vers === "ouvre" ? 0 : 1 },
            { height: arrivee + "px", opacity: vers === "ouvre" ? 1 : 0 },
          ],
          { duration: vers === "ouvre" ? 400 : 300, easing: "cubic-bezier(0.075, 0.82, 0.165, 1)" }
        );
        enCours.onfinish = function () {
          enCours = null;
          if (apres) apres();
        };
      }

      bloc.querySelector("summary").addEventListener("click", function (e) {
        e.preventDefault();

        if (bloc.open) {
          animer("ferme", function () {
            bloc.open = false;
          });
          return;
        }

        /* One open at a time, like the original. */
        faq.querySelectorAll("details[open]").forEach(function (autre) {
          if (autre !== bloc) autre.open = false;
        });
        bloc.open = true;
        animer("ouvre");
      });
    });
  }

  /* ==========================================================================
     7. Video
     The poster is in the page; the file is only fetched when someone asks
     for it. 400 KB nobody watches is 400 KB too many.
     ========================================================================== */

  document.querySelectorAll("[data-video]").forEach(function (bouton) {
    bouton.addEventListener("click", function () {
      var cadre = bouton.closest(".tile").querySelector(".tile__media");
      var film = document.createElement("video");
      film.className = "tile__video";
      film.src = bouton.getAttribute("data-video");
      film.muted = true;
      film.loop = true;
      film.playsInline = true;
      film.controls = false;
      film.setAttribute("aria-label", "Coconut grove, looping footage");
      cadre.replaceChild(film, cadre.querySelector("img"));
      bouton.remove();
      film.play();
    });
  });

  /* ==========================================================================
     8. Forms
     A static template has no booking engine and no mailing list. Both forms
     validate with the browser's own rules, then say so.
     ========================================================================== */

  [
    [document.querySelector("[data-book-form]"), document.querySelector("[data-book-done]")],
    [document.querySelector("[data-news-form]"), document.querySelector("[data-news-done]")],
  ].forEach(function (paire) {
    var formulaire = paire[0];
    var reponse = paire[1];
    if (!formulaire || !reponse) return;

    formulaire.addEventListener("submit", function (e) {
      e.preventDefault();
      if (!formulaire.checkValidity()) {
        formulaire.reportValidity();
        return;
      }
      reponse.hidden = false;
      var jumeau = reponse.previousElementSibling;
      if (jumeau && jumeau.classList.contains("news__note")) jumeau.hidden = true;
      formulaire.reset();
    });
  });

  /* ==========================================================================
     9. Rating
     ========================================================================== */

  var compteur = document.querySelector("[data-count-up]");

  if (compteur && "IntersectionObserver" in window && !doucement) {
    var cible = parseFloat(compteur.textContent);
    var decimales = (compteur.textContent.split(".")[1] || "").length;
    compteur.textContent = (0).toFixed(decimales);

    var oeil = new IntersectionObserver(
      function (entrees) {
        if (!entrees[0].isIntersecting) return;
        oeil.disconnect();
        var debut = null;
        var duree = 2000;
        (function pas(temps) {
          if (debut === null) debut = temps;
          var t = Math.min(1, (temps - debut) / duree);
          var adouci = 1 - Math.pow(1 - t, 2); /* power2.out, as in the original */
          compteur.textContent = (cible * adouci).toFixed(decimales);
          if (t < 1) requestAnimationFrame(pas);
        })(performance.now());
      },
      { rootMargin: "0px 0px -20% 0px" }
    );
    oeil.observe(compteur);
  }
})();
