/* ================================================== STOCK, Institucional page interactive components Editorial/industrial language, distinct from Produto. Depends on React (global) + internal.jsx (Counter opt.) Exposes: useInstFx, EdNums, Timeline, DifRows, SegMarquee ================================================== */ const { useState: iUseState, useEffect: iUseEffect, useRef: iUseRef } = React; /* ---------- scroll fx: wipe/rise reveals + strip parallax + timeline ---------- */ function useInstFx() { iUseEffect(() => { /* reveals */ const els = document.querySelectorAll('.wipe, .rise'); const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('on'); io.unobserve(e.target); } }); }, { threshold: 0.18 }); els.forEach(el => io.observe(el)); /* strip parallax */ const strips = document.querySelectorAll('.ed-strip img'); let raf = null; const onScroll = () => { if (raf) return; raf = requestAnimationFrame(() => { raf = null; const vh = window.innerHeight; strips.forEach(img => { const r = img.parentElement.getBoundingClientRect(); const p = (r.top + r.height / 2 - vh / 2) / vh; // -0.5..0.5-ish img.style.transform = `translateY(${p * -40}px)`; }); /* timeline fill + dots */ const tl = document.querySelector('.tl'); if (tl) { const fill = tl.querySelector('.tl-fill'); const r = tl.getBoundingClientRect(); const passed = Math.min(Math.max((vh * 0.55 - r.top) / r.height, 0), 1); if (fill) fill.style.height = `${passed * 100}%`; tl.querySelectorAll('.tl-step').forEach(step => { const sr = step.getBoundingClientRect(); step.classList.toggle('on', sr.top + sr.height / 2 < vh * 0.62); }); } }); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => { window.removeEventListener('scroll', onScroll); io.disconnect(); }; }, []); } /* ---------- giant numbers with count-up ---------- */ function EdNum({ to, suffix, label, delay }) { const ref = iUseRef(null); const [v, setV] = iUseState(0); iUseEffect(() => { const el = ref.current; const io = new IntersectionObserver(([e]) => { if (!e.isIntersecting) return; io.disconnect(); const t0 = performance.now(); const dur = 1600; const tick = (t) => { const p = Math.min((t - t0) / dur, 1); setV(Math.round(to * (1 - Math.pow(1 - p, 3)))); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to]); return (
{v.toLocaleString('pt-BR')}{suffix}
{label}
); } function EdNums({ items }) { return (
{items.map((n, i) => )}
); } /* ---------- vertical timeline (Projeto / Fabricação / Instalação) ---------- */ function Timeline({ steps }) { return (
Como trabalhamos

Do desenho à instalação

Uma equipe só cuida de todo o processo, sem terceirizar etapas, sem repassar responsabilidade.

{steps.map((s, i) => (
{s.t}
{String(i + 1).padStart(2, '0')}

{s.t}

{s.d}

))}
); } /* ---------- diferenciais: expanding rows ---------- */ function DifRows({ items }) { return (
Por que a Stock

Nossos diferenciais

{items.map((d, i) => (
{String(i + 1).padStart(2, '0')} {d.t}

{d.d}

))}
); } /* ---------- segments: double marquee ---------- */ function SegMarquee({ items }) { const half = Math.ceil(items.length / 2); const rows = [items.slice(0, half), items.slice(half)]; const Chip = ({ s }) => ( {s} ); return (
Onde atuamos

Segmentos atendidos

{rows.map((row, ri) => (
{[...row, ...row, ...row, ...row].map((s, i) => )}
))}
); } Object.assign(window, { useInstFx, EdNums, Timeline, DifRows, SegMarquee });