/* 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',
  'Ramos Janones',
  'Michael Jordan',
  'Vitor Hugo',
  'Victor Souza',
];

/* ============ 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) {
    const k = Math.round(n / 1000);
    return k;
  }
  return String(n);
}
function startCounters() {
  document.querySelectorAll('[data-count]').forEach((el) => {
    const target = parseInt(el.dataset.count, 10);
    const suffix = el.dataset.suffix || '';
    // Strip "+" from suffix since we prepend it
    const trailing = suffix.replace(/\+/g, '');
    el.innerHTML = '<span class="plus">+</span>0' + (trailing ? '<span class="plus">' + trailing + '</span>' : '');
    let started = false;
    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started) {
          started = true;
          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 {
              // final formatting
              if (target >= 1000) {
                el.innerHTML = '<span class="plus">+</span>300<span class="plus">k</span>';
              } else {
                el.innerHTML = '<span class="plus">+</span>' + target + (trailing ? '<span class="plus">' + trailing + '</span>' : '');
              }
            }
          }
          requestAnimationFrame(tick);
          obs.unobserve(el);
        }
      });
    }, { threshold: 0.4 });
    obs.observe(el);
  });
}

/* ============ 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 },
  ];

  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');
        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();
