/* URBANITY SALES SITE — data + signature-effect wiring (loads first) */
window.UK = window.UK || {};
window.UK.DS = window.DesignSystem_4a5304;

/* Demo WhatsApp number — replace with the real {{WHATSAPP_NUM}} before publishing. */
const WA = "34654949878";

window.UK.DATA = {
  wa: WA,
  asset: (p) => "../../assets/" + p,
  address: "Alameda Nestor Fonseca, Qd.3, Lts 19–31, Residencial Interlagos, Rio Verde/GO",
  mapsHref: "https://maps.google.com/?q=" + encodeURIComponent("Alameda Nestor Fonseca, Residencial Interlagos, Rio Verde, GO"),

  /* clean, brand-free renders (copied from the curated upload) */
  img: {
    hero: "imagery/hero-noturno.webp",        // night tower over the lake (9:16) — LCP
    aerea: "imagery/aerea-pool.webp",          // aerial pool balconies
    pool: "imagery/pool-sunset.webp",          // rooftop pool at sunset (lifestyle)
    living1: "imagery/living-1.webp",          // living + kitchen, warm
    living2: "imagery/living-2.webp",          // living, twilight balcony
    gourmet: "imagery/living-gourmet.webp",    // gourmet / dining, pool view
    conveniencia: "imagery/conveniencia.webp", // lavanderia OMO
  },

  /* EFFECT 1 — EVASION fan mosaic. 5 vertical strips, left→right.
     CENTER (index 2) = night tower = protagonist (z-10, LCP).
     object-position tuned to frame each render inside a narrow strip. */
  heroMosaic: [
    { img: "imagery/living-1.webp",       pos: "34% 52%", alt: "" },                 // living decorado
    { img: "imagery/pool-sunset.webp",    pos: "62% 56%", alt: "" },                 // pool resort, pôr do sol
    { img: "imagery/hero-noturno.webp",   pos: "33% 60%", center: true,              // torre noturna (protagonista)
      alt: "Fachada noturna do URBANITY Kasa Resort — a torre iluminada refletida no lago" },
    { img: "imagery/living-gourmet.webp", pos: "44% 50%", alt: "" },                 // varanda gourmet
    { img: "imagery/aerea-pool.webp",     pos: "28% 42%", alt: "" },                 // rooftop / varandas piscina
  ],

  stats: [
    { value: 33, label: "pavimentos" },
    { value: 270, label: "vagas" },
    { value: 5, label: "elevadores" },
    { value: 30, prefix: "+", label: "espaços de lazer" },
    { value: 5487, suffix: " m²", label: "terreno" },
    { value: 32674, suffix: " m²", label: "construído" },
  ],
  tipologias: [
    { key: "1q", title: "1 Quarto", tagline: "o studio que pensa por você", area: "50,29 m²",
      price: "584.733", range: null, img: "plantas/grade/planta-08.webp", cta: "Quero a planta do 1 quarto" },
    { key: "2q", title: "2 Quartos", tagline: "decorado físico pronto para visita", area: "62 a 66 m²",
      price: "708.237", range: "até R$ 765.804", img: "plantas/grade/planta-15.webp", cta: "Agendar visita ao decorado 2 quartos" },
    { key: "3q", title: "3 Quartos", tagline: "com opção de home office", area: "80 a 90 m²",
      price: "895.163", range: "até R$ 1.000.136", img: "plantas/grade/planta-20.webp", cta: "Agendar visita ao decorado 3 quartos" },
    { key: "penthouse", title: "Penthouse / Rooftop", tagline: "o topo, com rooftop privativo", area: "sob consulta",
      price: null, range: null, img: "plantas/grade/planta-10.webp", cta: "Falar sobre as penthouses" },
    { key: "loja", title: "Lojas / Mall", tagline: "ponto pronto para faturar", area: "191 a 208 m²",
      price: "2,55 mi", range: "até R$ 2,66 mi", img: "plantas/grade/planta-05.webp", cta: "Quero condições da loja" },
  ],
  parcela: "Entrada facilitada + parcelas até a entrega — condição da tabela junho/26 sob consulta.",
  livings: [
    { img: "imagery/living-1.webp", tip: "2 Quartos · 62–66 m²", price: "708.237", key: "2q" },
    { img: "imagery/living-gourmet.webp", tip: "3 Quartos · 80–90 m²", price: "895.163", key: "3q" },
    { img: "imagery/living-2.webp", tip: "Living integrado · varanda gourmet", price: null, key: "2q" },
    { img: "imagery/pool-sunset.webp", tip: "Rooftop · pôr do sol", price: null, key: "penthouse" },
  ],
  amenities: [
    { fam: "Resort & Bem-estar", label: "Pool borda infinita vista parque · 2 SPAs · Sauna · Relax/Beauty · Pool Kids", img: "imagery/pool-sunset.webp", span: "big" },
    { fam: "Rooftop", label: "Fitness vista parque · Gourmet · Varanda c/ piscina privativa", img: "imagery/aerea-pool.webp", span: "tall" },
    { fam: "Gourmet & Social", label: "Salão 45–68 · Pub Gourmet · House Grill · Lobby pé-direito duplo", img: "imagery/living-gourmet.webp" },
    { fam: "Kids & Família", label: "Brinquedoteca · Playground · Quadra Kids · Arena Beach · Pet Place", img: null },
    { fam: "Trabalho & Conveniência", label: "Coworking + conference · Studio Podcast · Mini Market · Lavanderia OMO · Bike Share", img: "imagery/conveniencia.webp", span: "wide" },
  ],
  plantas: [
    { f: "2q", img: "plantas/grade/planta-15.webp", cap: "2 Quartos · 62 m²" },
    { f: "3q", img: "plantas/grade/planta-20.webp", cap: "3 Quartos · 80 m²" },
    { f: "loja", img: "plantas/grade/planta-05.webp", cap: "Lojas e metragens" },
    { f: "2q", img: "plantas/grade/planta-18.webp", cap: "2 Quartos · variação" },
    { f: "3q", img: "plantas/grade/planta-24.webp", cap: "3 Quartos · variação" },
    { f: "garden", img: "plantas/grade/planta-28.webp", cap: "Garden" },
    { f: "penthouse", img: "plantas/grade/planta-10.webp", cap: "Penthouse" },
    { f: "2q", img: "plantas/grade/planta-12.webp", cap: "2 Quartos · planta" },
    { f: "1q", img: "plantas/grade/planta-08.webp", cap: "1 Quarto · 50 m²" },
  ],
  correcao: "Correção INCC-DI até o habite-se; após a entrega das chaves, IGP-M + 1%.",
};

/* ============================================================
   SIGNATURE-EFFECT ENGINE — scroll-driven, robust, degrades.
   ONE rAF loop reads scroll once per frame and drives every
   transform/opacity. Easing = cubic-bezier(0.16,1,0.3,1) (cubic
   ease-out). prefers-reduced-motion → engine stays off, CSS base
   shows the final, legible state. No CTA/price hidden behind motion.
   ============================================================ */
window.UK.fx = (function () {
  const mq = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)");
  const reduce = !!(mq && mq.matches);
  const clamp = (v, a, b) => (v < a ? a : v > b ? b : v);
  const easeOut = (t) => 1 - Math.pow(1 - clamp(t, 0, 1), 3); // ≈ cubic-bezier(0.16,1,0.3,1)
  const hasIO = "IntersectionObserver" in window;

  const drivers = [];
  let rafId = null, lastY = -1, lastW = -1;
  function frame() {
    const y = window.pageYOffset || document.documentElement.scrollTop || 0;
    const w = window.innerWidth;
    if (y !== lastY || w !== lastW) {
      lastY = y; lastW = w;
      for (let i = 0; i < drivers.length; i++) drivers[i]();
    }
    rafId = requestAnimationFrame(frame);
  }

  /* ---- G2 — reveal-on-scroll (base of the whole site) ---- */
  function initReveal() {
    const els = document.querySelectorAll("[data-reveal]");
    if (!hasIO) { els.forEach((e) => e.classList.add("is-in")); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("is-in"); io.unobserve(e.target); } });
    }, { rootMargin: "0px 0px -12% 0px", threshold: 0.12 });
    els.forEach((el, i) => { el.style.transitionDelay = Math.min(i % 6, 6) * 80 + "ms"; io.observe(el); });
  }

  /* ---- EFFECT 1 — HERO "MOSAICO" (EVASION fan): 5 strips desktop / 3 strips mobile ----
     Desktop: fan completo — outer strips lifted, center anchored.
     Mobile: 3 strips (center + 2 adjacentes) com fan + parallax individual.
     Ambos: entrada staggerada center-first, parallax scroll, wordmark drift.
     reduce → estado estático legível, sem loop. */
  function initHeroMosaic() {
    const hero = document.querySelector(".hero");
    if (!hero) return;
    const strips = [...hero.querySelectorAll(".hero__strip")];
    const copy = hero.querySelector(".hero__copy");
    const cue = hero.querySelector(".hero__cue");
    const word = hero.querySelector(".hero__wordmark");
    if (!strips.length) return;

    // Desktop: fan offset (px) + parallax speed (px por viewport de scroll)
    const CFG = [
      { base: -178, speed: 132 },  // 0 far left (hidden mobile)
      { base: -104, speed: 90 },   // 1 left-center → lateral esquerda mobile
      { base: 0,    speed: 40 },   // 2 CENTER — protagonista
      { base: -104, speed: 90 },   // 3 right-center → lateral direita mobile
      { base: -178, speed: 132 },  // 4 far right (hidden mobile)
    ];
    // Mobile: 3 strips — laterais com base -80px → cobrem 88svh + lift = quase full height
    const MOBILE_BASE  = [0,  -80, 0,  -80, 0 ];
    const MOBILE_SPEED = [46, 100, 42, 100, 46 ];

    const DELAY = [300, 150, 0, 150, 300]; // entrada staggerada, center-first
    const DUR = 680;

    const heroP = () => {
      const r = hero.getBoundingClientRect();
      return clamp(-r.top / Math.max(1, window.innerHeight), 0, 1);
    };

    // Reduced-motion: fan estático, sem animação.
    if (reduce) {
      const mobile = window.innerWidth < 1024;
      strips.forEach((s, i) => {
        const base = mobile ? MOBILE_BASE[i] : CFG[i].base;
        s.style.transform = `translate3d(0, ${base}px, 0)`;
        s.style.opacity = "1";
      });
      return;
    }

    hero.classList.add("hero--fx"); // CSS esconde strips (opacity 0) pré-entrada

    function render(ts) {
      const mobile = window.innerWidth < 1024;
      const p = heroP();
      for (let i = 0; i < strips.length; i++) {
        const c = CFG[i];
        let e = 1;
        if (entranceStart != null) e = easeOut((ts - entranceStart - DELAY[i]) / DUR);
        const base  = mobile ? MOBILE_BASE[i]  : c.base;
        const speed = mobile ? MOBILE_SPEED[i] : c.speed;
        const ty = base - easeOut(p) * speed + (1 - e) * 70;
        strips[i].style.transform = `translate3d(0, ${ty.toFixed(1)}px, 0)`;
        strips[i].style.opacity = e.toFixed(3);
      }
      // copy lifts + fades; wordmark drifts opposite for parallax depth
      if (copy) {
        copy.style.transform = `translate3d(0, ${(-p * 44).toFixed(1)}px, 0)`;
        copy.style.opacity = (1 - p * 1.2).toFixed(2);
      }
      if (word) word.style.transform = `translate(-50%, calc(-50% + ${(p * 60).toFixed(1)}px))`;
      if (cue) cue.style.opacity = (1 - p * 3).toFixed(2);
    }

    // Dedicated entrance loop (the shared rAF only ticks on scroll change).
    let entranceStart = performance.now();
    (function entrance(ts) {
      render(ts);
      if (ts - entranceStart < DELAY[0] + DUR + 60) requestAnimationFrame(entrance);
      else { entranceStart = null; render(performance.now()); }
    })(entranceStart);

    // Scroll-driven parallax (entrance already finished → e=1).
    drivers.push(() => render(performance.now()));
  }

  /* ---- EFFECT 2 — PRISMA: claim FLIP 3D, scroll-driven rotateX ---- */
  function initPrisma() {
    const prisma = document.querySelector(".prisma");
    if (!prisma) return;
    if (reduce || !hasIO) return; // CSS :not(.js-on) → faces stacked, legible
    prisma.classList.add("js-on");
    const faces = [...prisma.querySelectorAll(".prisma__face")];
    const n = faces.length;
    if (!n) return;
    const update = () => {
      const r = prisma.getBoundingClientRect();
      const total = Math.max(1, prisma.offsetHeight - window.innerHeight);
      const p = clamp(-r.top / total, 0, 1);
      const pos = p * (n - 1);                 // floating active-face index
      faces.forEach((f, i) => {
        const delta = pos - i;                 // <0 upcoming, >0 already passed
        const ad = Math.abs(delta);
        const rot = clamp(delta, -1.25, 1.25) * 80;   // degrees of rotateX
        f.style.opacity = clamp(1 - ad * 1.18, 0, 1).toFixed(2);
        f.style.transform = `translateY(-50%) rotateX(${(-rot).toFixed(1)}deg) translateZ(50px)`;
        f.style.pointerEvents = ad < 0.5 ? "auto" : "none";
      });
    };
    drivers.push(update); update();
  }

  /* ---- EFFECT 3 — MANIFESTO: blur-stagger word reveal, scroll-driven ---- */
  function initManifesto() {
    const m = document.querySelector(".manifesto");
    if (!m) return;
    if (reduce || !hasIO) return; // base = words already nítido
    m.classList.add("fx-on");
    const words = [...m.querySelectorAll(".w")];
    const N = words.length;
    if (!N) return;
    const update = () => {
      const r = m.getBoundingClientRect();
      const vh = window.innerHeight;
      const start = vh * 0.86, end = vh * 0.30;
      const p = clamp((start - r.top) / Math.max(1, start - end), 0, 1);
      const reveal = p * (N + 5);             // words revealed so far (with a small lead)
      for (let i = 0; i < N; i++) {
        const local = clamp(reveal - i, 0, 1);
        words[i].style.opacity = local.toFixed(2);
        words[i].style.filter = local >= 1 ? "none" : `blur(${((1 - local) * 14).toFixed(1)}px)`;
      }
    };
    drivers.push(update); update();
  }

  /* ---- EFFECT 4 — BENTO: per-cell stagger reveal (blur + lift) ---- */
  function initBento() {
    const bento = document.querySelector(".bento");
    if (!bento) return;
    const cells = [...bento.children];
    if (reduce || !hasIO) return; // base = cells visible
    bento.classList.add("fx-stagger");
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          const i = cells.indexOf(e.target);
          e.target.style.transitionDelay = Math.min(i, 8) * 70 + "ms";
          e.target.classList.add("in");
          io.unobserve(e.target);
        }
      });
    }, { rootMargin: "0px 0px -8% 0px", threshold: 0.18 });
    cells.forEach((c) => io.observe(c));
  }

  /* ---- EFFECT 5 — STICKY SCROLL-STACK: rise + scale + compress by z-index ---- */
  function initStack() {
    const stack = document.querySelector(".stack");
    if (!stack) return;
    const cards = [...stack.querySelectorAll(".stack__card")];
    if (!cards.length) return;
    if (reduce || !hasIO) return; // CSS :not(.js-on) → natural sticky list, legible
    stack.classList.add("js-on");
    const update = () => {
      const vh = window.innerHeight;
      const stickyTop = vh * 0.12;
      const span = Math.max(1, vh - stickyTop);
      for (let i = 0; i < cards.length; i++) {
        const inner = cards[i].querySelector(".stack__inner");
        if (!inner) continue;
        const r = cards[i].getBoundingClientRect();
        const e = easeOut((vh - r.top) / span);            // entrance: rises from below
        let settle = 0;
        if (i < cards.length - 1) {
          const nr = cards[i + 1].getBoundingClientRect();
          settle = easeOut((vh - nr.top) / span);          // next card stacking over this one
        }
        const scale = (0.86 + e * 0.14) * (1 - settle * 0.07);
        const ty = (1 - e) * 64 - settle * 16;
        inner.style.transform = `translate3d(0, ${ty.toFixed(1)}px, 0) scale(${scale.toFixed(3)})`;
        inner.style.opacity = (0.35 + e * 0.65).toFixed(2);
      }
    };
    drivers.push(update); update();
  }

  /* ---- G2 — sticky bottom CTA bar (DOM fallback; React also drives it) ---- */
  function initSticky() {
    const bar = document.querySelector(".sticky-bar");
    const hero = document.querySelector(".hero");
    if (!bar || !hero) return;
    if (!hasIO) { bar.classList.add("on"); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => bar.classList.toggle("on", !e.isIntersecting));
    }, { threshold: 0.1 });
    io.observe(hero);
  }

  let _ran = false;
  function initAll() {
    if (_ran) return true;
    if (!document.querySelector(".hero")) return false; // DOM not committed yet
    _ran = true;
    initReveal(); initHeroMosaic(); initPrisma(); initManifesto(); initBento(); initStack(); initSticky();
    if (drivers.length && !reduce && !rafId) {
      // Continuous rAF loop (skips work when the page is idle). Robust on iOS
      // and inside embedded previews where the 'scroll' event can be flaky.
      rafId = requestAnimationFrame(frame);
    }
    return true;
  }
  return { initAll };
})();
