/* global React, ReactDOM, TweaksPanel, useTweaks, TweakSection, TweakRadio, TweakToggle */

const { useEffect, useRef, useState } = React;

/* ============ LANG ============ */
const LANG = (() => {
  const l = (document.documentElement.lang || '').toLowerCase();
  if (l.startsWith('en')) return 'en';
  if (l.startsWith('es')) return 'es';
  return 'pt';
})();

/* ============ I18N ============ */
const I18N = {
  pt: {
    visitSite: 'Visitar site',
    products: {
      redraw:   { role: 'Software de IA para arquitetura', tag: '01 · Founding product', quote: 'Software de IA para arquitetura, focado em fazer renders realistas de verdade, com estudo e tecnologia de ponta, o Redraw tem se tornado o principal Hub de IAs para arquitetos, engenheiros e designers de toda a América Latina.' },
      memberai: { role: 'Áreas de membros para criadores de IA', tag: '02 · 2024', quote: 'Ao longo de grandes lançamentos no mercado de IA, percebemos um grande gap no mercado: pessoas tentando criar conteúdos para a IA, mas com dificuldade para vender. Foi aí que o Member AI surgiu — uma plataforma que possibilita que criadores com pouco conhecimento criem áreas de membros avançadas com seus produtos de IA e vendam isso para o mundo.' },
      wapfy:    { role: 'Agendamento via WhatsApp', tag: '03 · 2024', quote: 'Soluções nem sempre precisam ser complexas. Diversos comércios locais no Brasil trabalham com atendimento no WhatsApp, principalmente quem precisa marcar consultas — salões de beleza, barbeiros, dentistas e médicos. O Wapfy é um software de agendamento de WhatsApp que gerencia agendas de profissionais que não têm tempo para isso.' },
      limify:   { role: 'Orçamentos para projetos e obras', tag: '04 · 2025', quote: 'Seguindo o mesmo padrão do Redraw, o desenvolvimento do Limify veio a partir de entrevistas com os usuários — todos relatam a dificuldade para orçar projetos e obras. Foi aí que o Limify surgiu: um software que permite a profissionais gerenciar custos e orçar projetos com facilidade e lucratividade real.' },
    },
  },
  en: {
    visitSite: 'Visit site',
    products: {
      redraw:   { role: 'AI software for architecture', tag: '01 · Founding product', quote: 'AI software for architecture, focused on truly photorealistic renders backed by deep research and cutting-edge technology. Redraw has become the leading AI hub for architects, engineers, and designers across Latin America.' },
      memberai: { role: 'Member areas for AI creators', tag: '02 · 2024', quote: 'Through the wave of major launches in the AI market, we noticed a big gap: people trying to create content with AI, but struggling to sell it. That is where Member AI came in — a platform that lets creators with little technical knowledge build advanced member areas around their AI products and sell them to the world.' },
      wapfy:    { role: 'Scheduling via WhatsApp', tag: '03 · 2024', quote: 'Solutions do not always have to be complex. Many local businesses in Brazil run customer service through WhatsApp — especially those who book appointments: hair salons, barbers, dentists, and doctors. Wapfy is a WhatsApp scheduling software that manages calendars for professionals who do not have time to do it themselves.' },
      limify:   { role: 'Quotes for projects and construction', tag: '04 · 2025', quote: 'Following the same pattern as Redraw, Limify’s development came from user interviews — everyone reported the difficulty of pricing projects and construction work. That is how Limify was born: software that lets professionals manage costs and quote projects with ease and real profitability.' },
    },
  },
  es: {
    visitSite: 'Visitar sitio',
    products: {
      redraw:   { role: 'Software de IA para arquitectura', tag: '01 · Founding product', quote: 'Software de IA para arquitectura, enfocado en hacer renders realistas de verdad, con estudio y tecnología de punta. Redraw se ha convertido en el principal hub de IA para arquitectos, ingenieros y diseñadores de toda América Latina.' },
      memberai: { role: 'Áreas de miembros para creadores de IA', tag: '02 · 2024', quote: 'A lo largo de los grandes lanzamientos del mercado de IA, notamos un gran gap: personas tratando de crear contenido para IA, pero con dificultad para vender. Así nació Member AI — una plataforma que permite a creadores con poco conocimiento técnico crear áreas de miembros avanzadas con sus productos de IA y venderlos al mundo.' },
      wapfy:    { role: 'Agendamiento por WhatsApp', tag: '03 · 2024', quote: 'Las soluciones no siempre tienen que ser complejas. Muchos comercios locales en Brasil trabajan con atención por WhatsApp, especialmente quienes necesitan agendar citas — peluquerías, barberos, dentistas y médicos. Wapfy es un software de agendamiento por WhatsApp que gestiona las agendas de profesionales que no tienen tiempo para hacerlo.' },
      limify:   { role: 'Presupuestos para proyectos y obras', tag: '04 · 2025', quote: 'Siguiendo el mismo patrón de Redraw, el desarrollo de Limify surgió a partir de entrevistas con usuarios — todos relataron la dificultad de presupuestar proyectos y obras. Así nació Limify: un software que permite a los profesionales gestionar costos y presupuestar proyectos con facilidad y rentabilidad real.' },
    },
  },
};
const T = I18N[LANG];

/* ============ PRODUCTS DATA ============ */
const PRODUCTS = [
  { id: 'redraw',   name: 'Redraw',    logo: '/assets/brand/redraw-logo.svg',           logoTall: false,   href: 'https://redraw.pro/'   },
  { id: 'memberai', name: 'Member AI', logo: '/assets/brand/memberai-logo-cropped.png', logoSize: 'tall',  href: 'https://memberai.pro/' },
  { id: 'wapfy',    name: 'Wapfy',     logo: '/assets/brand/wapfy-white.svg',           logoTall: false,   href: 'https://wapfy.pro/'    },
  { id: 'limify',   name: 'Limify',    logo: '/assets/brand/limify-white.png',          logoSize: 'mid',   href: 'https://limify.pro/'   },
].map((p) => ({ ...p, ...T.products[p.id] }));

const TEAM_NAMES = [
  'Graziela Caroline',
  'Barbara Santos',
  'Felipe Mahlow',
  'Pedro Moura',
  'Rebeca Santos',
  'Maria Nogueira',
  'Nathaly Camargo',
  'Patrick Borges',
  'Henrique Barbosa',
  'Bruno Silva',
  'Victor Soares',
  'Guilherme Augusto',
  'Alberto Goulart',
  'Vitor Hugo',
  'Victor Souza',
  'Guilherme Oliveira',
  'Lucas Nogueira',
  'Arthur Menezes',
];

/* ============ PRODUCT CARD ============ */
function ProductCard({ p, index, total }) {
  const ref = useRef(null);
  function onMove(e) {
    const r = ref.current.getBoundingClientRect();
    ref.current.style.setProperty('--mx', `${e.clientX - r.left}px`);
    ref.current.style.setProperty('--my', `${e.clientY - r.top}px`);
  }
  return (
    <article className="product" ref={ref} onMouseMove={onMove}>
      <div className="product__top">
        <div className={`product__logo${p.logoSize ? ' product__logo--' + p.logoSize : ''}`}>
          <img src={p.logo} alt={`${p.name} logo`} />
        </div>
        <span className="product__tag">{p.tag}</span>
      </div>
      <p className="product__quote">"{p.quote}"</p>
      <div className="product__meta">
        <div>
          <div className="product__name">{p.name}</div>
          <div className="product__role">{p.role}</div>
        </div>
        <a href={p.href} target="_blank" rel="noopener" className="product__link" data-cursor="hover">
          {T.visitSite}
          <svg viewBox="0 0 12 12" fill="none">
            <path d="M2 10l8-8M10 2H4.5M10 2v5.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
          </svg>
        </a>
      </div>
    </article>
  );
}

/* ============ TWEAKS APP ============ */
function ThemeTweaks() {
  const [tweaks, setTweak] = useTweaks(window.__TWEAK_DEFAULTS);

  useEffect(() => {
    const html = document.documentElement;
    html.dataset.fontMode = tweaks.fontMode || 'default';
    html.dataset.giant = tweaks.giantHead ? 'on' : 'off';
  }, [tweaks]);

  return (
    <TweaksPanel title="Tweaks">
      <TweakSection title="Tipografia">
        <TweakRadio
          label="Fonte"
          value={tweaks.fontMode}
          onChange={(v) => setTweak('fontMode', v)}
          options={[
            { label: 'Montreal + Lexend', value: 'default' },
            { label: 'Lexend em tudo', value: 'lexend' },
            { label: 'Montreal em tudo', value: 'montreal-all' },
          ]}
        />
      </TweakSection>

      <TweakSection title="Hero">
        <TweakToggle
          label="THARK gigante atrás"
          value={tweaks.giantHead}
          onChange={(v) => setTweak('giantHead', v)}
        />
      </TweakSection>

    </TweaksPanel>
  );
}

/* ============ INJECT PRODUCTS ============ */
function injectProducts() {
  const track = document.getElementById('productsTrack');
  if (!track) return;
  const root = ReactDOM.createRoot(track);
  root.render(
    <>
      {PRODUCTS.map((p, i) => (
        <ProductCard key={p.id} p={p} index={i} total={PRODUCTS.length} />
      ))}
    </>
  );
}

/* ============ COUNT-UP ============ */
function formatCount(n, target) {
  if (target >= 1000) {
    return Math.round(n / 1000);
  }
  return String(n);
}
function animateCounter(el) {
  if (el.dataset.countStarted) return;
  el.dataset.countStarted = 'true';
  const target = parseInt(el.dataset.count, 10);
  const trailing = (el.dataset.suffix || '').replace(/\+/g, '');
  const dur = 2200;
  const t0 = performance.now();
  function tick(t) {
    const k = Math.min(1, (t - t0) / dur);
    const eased = 1 - Math.pow(1 - k, 3);
    const v = Math.floor(target * eased);
    el.innerHTML = '<span class="plus">+</span>' + formatCount(v, target) + (trailing ? '<span class="plus">' + trailing + '</span>' : '');
    if (k < 1) {
      requestAnimationFrame(tick);
    } else {
      el.innerHTML = '<span class="plus">+</span>' + formatCount(target, target) + (trailing ? '<span class="plus">' + trailing + '</span>' : '');
    }
  }
  requestAnimationFrame(tick);
}
function startCounters() {
  document.querySelectorAll('[data-count]').forEach((el) => {
    const trailing = (el.dataset.suffix || '').replace(/\+/g, '');
    el.innerHTML = '<span class="plus">+</span>0' + (trailing ? '<span class="plus">' + trailing + '</span>' : '');
  });
  // Counters are triggered by startReveal() when their parent .reveal enters viewport
}

/* ============ TD GLOBE (3D rotating sphere with name cards) ============ */
function injectTeamNames() {
  const section = document.querySelector('.td-globe-section');
  if (!section) return;
  const cardsLayer = section.querySelector('[data-globe-cards]');
  const globe = section.querySelector('.td-globe');
  if (!cardsLayer || !globe) return;
  cardsLayer.innerHTML = '';

  const points = [
    { lat: 38, lon: 8 },
    { lat: 25, lon: 42 },
    { lat: 52, lon: 86 },
    { lat: 16, lon: 128 },
    { lat: 34, lon: 172 },
    { lat: 4, lon: 214 },
    { lat: 48, lon: 254 },
    { lat: 18, lon: 292 },
    { lat: 58, lon: 326 },
    { lat: -2, lon: 22 },
    { lat: 28, lon: 64 },
    { lat: 44, lon: 112 },
    { lat: 10, lon: 158 },
    { lat: 50, lon: 236 },
    { lat: 22, lon: 340 },
    { lat: -24, lon: 96 },
    { lat: -12, lon: 198 },
    { lat: 36, lon: 308 },
  ];

  const cards = TEAM_NAMES.slice(0, points.length).map((name, index) => {
    const card = document.createElement('div');
    card.className = 'td-name-card';
    card.textContent = name;
    cardsLayer.appendChild(card);
    return { el: card, ...points[index] };
  });

  const speed = 360 / 26000;
  const tilt = (66 * Math.PI) / 180;
  const zRotate = (-4 * Math.PI) / 180;
  let isVisible = true;

  function render(time) {
    if (!isVisible) {
      requestAnimationFrame(render);
      return;
    }
    const size = cardsLayer.offsetWidth;
    const radius = size * 0.51;
    const centerX = size / 2;
    const centerY = size / 2;
    const angleOffset = -time * speed;
    globe.style.transform = `rotateX(66deg) rotateZ(-4deg) rotateY(${angleOffset}deg)`;

    cards.forEach((card) => {
      const lat = (card.lat * Math.PI) / 180;
      const lon = ((card.lon + angleOffset) * Math.PI) / 180;

      const x = Math.cos(lat) * Math.sin(lon) * radius;
      const y = -Math.sin(lat) * radius;
      const z = Math.cos(lat) * Math.cos(lon) * radius;

      const yTilt = y * Math.cos(tilt) - z * Math.sin(tilt);
      const zTilt = y * Math.sin(tilt) + z * Math.cos(tilt);

      const xRot = x * Math.cos(zRotate) - yTilt * Math.sin(zRotate);
      const yRot = x * Math.sin(zRotate) + yTilt * Math.cos(zRotate);

      const depth = (zTilt / radius + 1) / 2;
      const scale = 0.82 + depth * 0.18;
      const opacity = 0.25 + depth * 0.75;

      card.el.style.left = `${centerX + xRot}px`;
      card.el.style.top = `${centerY + yRot}px`;
      card.el.style.opacity = opacity.toFixed(2);
      card.el.style.zIndex = Math.round(depth * 100);
      card.el.style.transform = `translate(-50%, -50%) scale(${scale.toFixed(2)})`;
    });

    requestAnimationFrame(render);
  }

  const observer = new IntersectionObserver(
    ([entry]) => { isVisible = entry.isIntersecting; },
    { threshold: 0.05 }
  );
  observer.observe(section);
  requestAnimationFrame(render);
}

/* ============ MOTTO TYPING ============ */
function startMottoType() {
  document.querySelectorAll('.manifesto__motto--type').forEach((wrap) => {
    const target = wrap.querySelector('.motto-typed');
    if (!target) return;
    const fullText = target.dataset.typedText || target.textContent.trim();
    const dimFrom = parseInt(target.dataset.typedDimFrom || '0', 10);
    target.innerHTML = '<span class="motto-typed__bright"></span><span class="motto-typed__dim dim"></span><span class="motto-cursor" aria-hidden="true"></span>';
    const bright = target.querySelector('.motto-typed__bright');
    const dim = target.querySelector('.motto-typed__dim');

    let started = false;
    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started) {
          started = true;
          let i = 0;
          function step() {
            if (i > fullText.length) { wrap.classList.add('is-done'); return; }
            const head = fullText.slice(0, i);
            if (head.length <= dimFrom) {
              bright.textContent = head;
              dim.textContent = '';
            } else {
              bright.textContent = head.slice(0, dimFrom);
              dim.textContent = head.slice(dimFrom);
            }
            const c = fullText[i] || '';
            const delay = c === ' ' ? 60 : c === ',' ? 220 : c === '.' ? 320 : 80 + Math.random() * 60;
            i++;
            setTimeout(step, delay);
          }
          setTimeout(step, 280);
          obs.unobserve(wrap);
        }
      });
    }, { threshold: 0.4 });
    obs.observe(wrap);
  });
}

/* ============ REVEAL ON SCROLL ============ */
function startReveal() {
  const obs = new IntersectionObserver((entries) => {
    entries.forEach((e) => {
      if (e.isIntersecting) {
        e.target.classList.add('is-in');
        // Fire counter animations for any data-count children once section is visible
        const counters = e.target.querySelectorAll('[data-count]');
        if (counters.length) setTimeout(() => counters.forEach(animateCounter), 200);
        obs.unobserve(e.target);
      }
    });
  }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });

  setTimeout(() => {
    document.querySelectorAll('.reveal').forEach((el) => obs.observe(el));
  }, 150);
}

/* ============ CUSTOM CURSOR ============ */
function setupCursor() {
  const dot = document.getElementById('cursorDot');
  const ring = document.getElementById('cursorRing');
  if (!dot || !ring) return;
  if (window.matchMedia('(hover: none)').matches) {
    dot.style.display = 'none';
    ring.style.display = 'none';
    document.documentElement.style.cursor = 'auto';
    return;
  }
  let mx = window.innerWidth / 2, my = window.innerHeight / 2;
  let rx = mx, ry = my;
  window.addEventListener('mousemove', (e) => {
    mx = e.clientX; my = e.clientY;
    dot.style.transform = `translate(${mx}px, ${my}px) translate(-50%, -50%)`;
  });
  function tick() {
    rx += (mx - rx) * 0.18;
    ry += (my - ry) * 0.18;
    ring.style.transform = `translate(${rx}px, ${ry}px) translate(-50%, -50%)`;
    requestAnimationFrame(tick);
  }
  requestAnimationFrame(tick);

  // hover states
  const hoverSel = 'a, button, .product, .founder, .job, [data-cursor="hover"]';
  document.addEventListener('mouseover', (e) => {
    if (e.target.closest && e.target.closest(hoverSel)) {
      ring.classList.add('cursor--hover');
    }
  });
  document.addEventListener('mouseout', (e) => {
    if (e.target.closest && e.target.closest(hoverSel)) {
      ring.classList.remove('cursor--hover');
    }
  });
  document.addEventListener('mousedown', () => ring.classList.add('cursor--press'));
  document.addEventListener('mouseup', () => ring.classList.remove('cursor--press'));
}

/* ============ LANG TOGGLE ============ */
function setupLangToggle() {
  const btn = document.getElementById('langSwitch');
  if (!btn) return;
  const langs = ['PT', 'EN', 'ES'];
  let i = 0;
  btn.addEventListener('click', () => {
    i = (i + 1) % langs.length;
    btn.firstChild.textContent = langs[i] + ' ';
  });
}

/* ============ PRODUCTS HEADER (counter, active name, progress) ============ */
function startProductsHead() {
  const head = document.getElementById('productsHead');
  const pin = document.getElementById('productsPin');
  const track = document.getElementById('productsTrack');
  if (!head || !pin || !track) return;
  const cards = Array.from(track.querySelectorAll('.product'));
  if (!cards.length) return;

  // Mobile bypass — under 760px the carousel becomes a vertical stack via CSS.
  // We just render counters statically and skip the scroll-jacking carousel.
  const isMobile = () => window.matchMedia('(max-width: 760px)').matches;
  if (isMobile()) {
    const total = cards.length;
    const pcountTotal = document.getElementById('pcountTotal');
    const pcountNum = document.getElementById('pcountNum');
    const pactiveName = document.getElementById('pactiveName');
    const pprogFill = document.getElementById('pprogFill');
    const pprogTicks = document.getElementById('pprogTicks');
    if (pcountTotal) pcountTotal.textContent = String(total).padStart(2, '0');
    if (pcountNum) pcountNum.textContent = '01';
    if (pactiveName) pactiveName.textContent = (cards[0].querySelector('.product__name')?.textContent || '').trim();
    if (pprogFill) pprogFill.style.width = '100%';
    if (pprogTicks) {
      pprogTicks.innerHTML = '';
      for (let i = 0; i <= total; i++) pprogTicks.appendChild(document.createElement('span'));
    }
    // Reset transforms so cards lay out naturally
    cards.forEach((c) => {
      c.style.setProperty('--card-scale', '1');
      c.style.setProperty('--card-opacity', '1');
    });
    track.style.transform = 'none';
    return;
  }

  const total = cards.length;
  const pcountTotal = document.getElementById('pcountTotal');
  const pcountNum = document.getElementById('pcountNum');
  const pactiveName = document.getElementById('pactiveName');
  const pprogFill = document.getElementById('pprogFill');
  const pprogTicks = document.getElementById('pprogTicks');
  if (pcountTotal) pcountTotal.textContent = String(total).padStart(2, '0');
  if (pprogTicks) {
    pprogTicks.innerHTML = '';
    for (let i = 0; i <= total; i++) pprogTicks.appendChild(document.createElement('span'));
  }

  const names = cards.map((c) => {
    const n = c.querySelector('.product__name');
    return n ? n.textContent.trim() : '';
  });

  let activeIdx = -1;
  let ticking = false;

  function update() {
    ticking = false;
    const pinRect = pin.getBoundingClientRect();
    const pinTop = pinRect.top;
    const pinHeight = pinRect.height;
    const vh = window.innerHeight;

    // Scrollable range = pinHeight - vh (since the inner stage is sticky)
    const range = Math.max(1, pinHeight - vh);
    const scrolled = Math.max(0, Math.min(range, -pinTop));
    const rawP = scrolled / range; // 0..1 across the pin
    // Map carousel progress so that the last card is fully centered
    // by the time we're 70% through the pin — the remaining 30% is dwell time
    // on the final card before the next section starts scrolling in.
    const p = Math.max(0, Math.min(1, rawP / 0.70));

    // Translate the track horizontally based on progress.
    // We want the active card centered. Compute travel = (total-1) segment widths.
    const segments = total - 1;
    const segP = p * segments; // continuous position 0..segments
    cards.forEach((card, i) => {
      const dist = Math.abs(segP - i);
      const scale = Math.max(0.8, 1 - dist * 0.12);
      const opacity = Math.max(0.25, 1 - dist * 0.55);
      card.style.setProperty('--card-scale', scale.toFixed(3));
      card.style.setProperty('--card-opacity', opacity.toFixed(3));
      card.classList.toggle('is-active', Math.round(segP) === i);
    });

    // Translate the track. Each card occupies one "slot"; the slot width is
    // determined at render time. Compute it dynamically from the first card.
    // IMPORTANT: use offsetWidth, not getBoundingClientRect, because cards have
    // a CSS transform: scale(...) — getBoundingClientRect would return the
    // scaled width and break the centering math.
    const firstCard = cards[0];
    const cardW = firstCard.offsetWidth;
    const gap = parseFloat(getComputedStyle(track).gap) || 0;
    const slot = cardW + gap;
    track.style.transform = `translate3d(${-segP * slot}px, 0, 0)`;

    if (pprogFill) pprogFill.style.width = (p * 100).toFixed(2) + '%';

    // Active index = nearest card to segP
    const idx = Math.max(0, Math.min(total - 1, Math.round(segP)));
    if (idx !== activeIdx) {
      activeIdx = idx;
      if (pcountNum) pcountNum.textContent = String(idx + 1).padStart(2, '0');
      if (pactiveName) {
        pactiveName.classList.add('swapping');
        setTimeout(() => {
          pactiveName.textContent = names[idx] || '';
          pactiveName.classList.remove('swapping');
        }, 180);
      }
    }
  }
  function onScroll() {
    if (!ticking) {
      requestAnimationFrame(update);
      ticking = true;
    }
  }
  window.addEventListener('scroll', onScroll, { passive: true });
  window.addEventListener('resize', onScroll);
  update();
}

/* ============ SMOOTH SCROLL ============ */
function setupSmoothScroll() {
  document.querySelectorAll('a[href^="#"]').forEach((a) => {
    a.addEventListener('click', (e) => {
      const id = a.getAttribute('href').slice(1);
      if (!id) return;
      const tgt = document.getElementById(id);
      if (tgt) {
        e.preventDefault();
        window.scrollTo({ top: tgt.offsetTop - 80, behavior: 'smooth' });
      }
    });
  });
}

/* ============ HERO SCROLL ANIMATION (todayin-style) ============ */
function setupHeroScroll() {
  const hero = document.querySelector('.hero');
  if (!hero) return;
  const heroCopy = hero.querySelector('.hero__copy');
  const heroGiant = hero.querySelector('.hero__giant');
  let ticking = false;

  function update() {
    ticking = false;
    const rect = hero.getBoundingClientRect();
    const h = rect.height;
    // progress: 0 at top, 1 when hero has scrolled fully out
    const scrolled = -rect.top;
    const p = Math.max(0, Math.min(1, scrolled / h));
    // Hero copy: fade out and drift up
    if (heroCopy) {
      heroCopy.style.opacity = (1 - p * 1.4).toFixed(3);
      heroCopy.style.transform = `translateY(${(-p * 60).toFixed(1)}px)`;
    }
    // Giant text: parallax up + scale slightly
    if (heroGiant) {
      const scale = 1 + p * 0.18;
      heroGiant.style.transform = `translateY(${(-p * 120).toFixed(1)}px) scale(${scale.toFixed(3)})`;
      heroGiant.style.opacity = (1 - p * 0.5).toFixed(3);
    }
  }
  function onScroll() {
    if (!ticking) {
      requestAnimationFrame(update);
      ticking = true;
    }
  }
  window.addEventListener('scroll', onScroll, { passive: true });
  window.addEventListener('resize', onScroll);
  update();
}

/* ============ MOTTO PARTICLES ============ */
function startMottoParticles() {
  const canvas = document.getElementById('mottoParticles');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  const container = canvas.parentElement;

  let w = 0, h = 0;
  let particles = [];
  let mouse = { x: -9999, y: -9999, active: false };
  let raf = null;

  function resize() {
    const r = container.getBoundingClientRect();
    w = r.width;
    h = r.height;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    canvas.style.width = w + 'px';
    canvas.style.height = h + 'px';
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    initParticles();
  }

  function initParticles() {
    const density = 0.00022; // particles per square px — denser
    const count = Math.max(70, Math.min(220, Math.floor(w * h * density)));
    particles = [];
    for (let i = 0; i < count; i++) {
      particles.push({
        x: Math.random() * w,
        y: Math.random() * h,
        baseX: 0, baseY: 0,
        vx: (Math.random() - 0.5) * 0.25,
        vy: (Math.random() - 0.5) * 0.25,
        r: Math.random() * 1.6 + 0.5,
        baseAlpha: Math.random() * 0.4 + 0.2,
      });
      particles[i].baseX = particles[i].x;
      particles[i].baseY = particles[i].y;
    }
  }

  function tick() {
    ctx.clearRect(0, 0, w, h);

    const influenceRadius = 240;
    for (const p of particles) {
      // gentle drift
      p.x += p.vx;
      p.y += p.vy;

      // soft return to origin so they don't wander off
      p.x += (p.baseX - p.x) * 0.004;
      p.y += (p.baseY - p.y) * 0.004;

      // mouse force — stronger pull toward cursor with falloff
      if (mouse.active) {
        const dx = mouse.x - p.x;
        const dy = mouse.y - p.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < influenceRadius && dist > 0.01) {
          const t = 1 - dist / influenceRadius;
          // ease-in cubed for dramatic close-range pull
          const force = t * t * 2.4;
          p.vx += (dx / dist) * force * 0.06;
          p.vy += (dy / dist) * force * 0.06;
        }
      }

      // damping so they settle
      p.vx *= 0.94;
      p.vy *= 0.94;

      // wrap edges with margin
      if (p.x < -10) p.x = w + 10;
      if (p.x > w + 10) p.x = -10;
      if (p.y < -10) p.y = h + 10;
      if (p.y > h + 10) p.y = -10;

      // particles get brighter near cursor
      let alpha = p.baseAlpha;
      if (mouse.active) {
        const dx = mouse.x - p.x;
        const dy = mouse.y - p.y;
        const d = Math.sqrt(dx * dx + dy * dy);
        if (d < influenceRadius) {
          alpha = Math.min(1, p.baseAlpha + (1 - d / influenceRadius) * 0.6);
        }
      }

      ctx.beginPath();
      ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${alpha})`;
      ctx.fill();
    }

    // connect nearby particles + lines toward mouse
    const linkDist = 110;
    for (let i = 0; i < particles.length; i++) {
      const a = particles[i];
      for (let j = i + 1; j < particles.length; j++) {
        const b = particles[j];
        const dx = a.x - b.x;
        const dy = a.y - b.y;
        const d = Math.sqrt(dx * dx + dy * dy);
        if (d < linkDist) {
          const alpha = (1 - d / linkDist) * 0.12;
          ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
          ctx.lineWidth = 0.5;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y);
          ctx.lineTo(b.x, b.y);
          ctx.stroke();
        }
      }
      // mouse threads — stronger and more visible
      if (mouse.active) {
        const dx = a.x - mouse.x;
        const dy = a.y - mouse.y;
        const d = Math.sqrt(dx * dx + dy * dy);
        if (d < influenceRadius) {
          const alpha = (1 - d / influenceRadius) * 0.45;
          ctx.strokeStyle = `rgba(255,110,80,${alpha})`;
          ctx.lineWidth = 0.8;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y);
          ctx.lineTo(mouse.x, mouse.y);
          ctx.stroke();
        }
      }
    }

    // glowing core at cursor
    if (mouse.active) {
      const grad = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, 80);
      grad.addColorStop(0, 'rgba(255,110,80,0.25)');
      grad.addColorStop(1, 'rgba(255,110,80,0)');
      ctx.fillStyle = grad;
      ctx.beginPath();
      ctx.arc(mouse.x, mouse.y, 80, 0, Math.PI * 2);
      ctx.fill();
    }

    raf = requestAnimationFrame(tick);
  }

  function onMove(e) {
    const r = container.getBoundingClientRect();
    mouse.x = e.clientX - r.left;
    mouse.y = e.clientY - r.top;
    mouse.active = true;
  }
  function onLeave() {
    mouse.active = false;
    mouse.x = -9999;
    mouse.y = -9999;
  }

  // Only run when in viewport
  const io = new IntersectionObserver((entries) => {
    for (const e of entries) {
      if (e.isIntersecting) {
        if (!raf) tick();
      } else {
        if (raf) { cancelAnimationFrame(raf); raf = null; }
      }
    }
  }, { threshold: 0 });
  io.observe(container);

  resize();
  window.addEventListener('resize', resize);
  container.addEventListener('mousemove', onMove);
  container.addEventListener('mouseleave', onLeave);
  // also follow page-wide mouse so particles react before you hover the block
  window.addEventListener('mousemove', (e) => {
    const r = container.getBoundingClientRect();
    const x = e.clientX - r.left;
    const y = e.clientY - r.top;
    if (x >= -60 && x <= w + 60 && y >= -60 && y <= h + 60) {
      mouse.x = x;
      mouse.y = y;
      mouse.active = true;
    } else {
      mouse.active = false;
    }
  });
}

/* ============ MOUNT ============ */
const tweaksRoot = document.createElement('div');
document.body.appendChild(tweaksRoot);
ReactDOM.createRoot(tweaksRoot).render(<ThemeTweaks />);

injectProducts();
injectTeamNames();
startCounters();
startMottoType();
startReveal();
setupLangToggle();
setupSmoothScroll();
setupCursor();
setupHeroScroll();
setTimeout(startProductsHead, 50);
startMottoParticles();
