// Eu Faço Parte — Sections part 2: O que fazemos / Apoio a projetos / Iniciativas / Stats / Mapa / Ações
const ApoioProjetos = () => (
01 · {t('apoio.eyebrow')}
{ const ph = document.createElement('div'); ph.className = 'ph oque-ph'; ph.dataset.label = '— Projetos · iniciativas locais'; e.currentTarget.replaceWith(ph); }}
/>
{t('apoio.cap1')}
{ const ph = document.createElement('div'); ph.className = 'ph oque-ph'; ph.dataset.label = '— Projetos · peneira esportiva'; e.currentTarget.replaceWith(ph); }}
/>
{t('apoio.cap2')}
);
// ---- Initiatives grid ----
// Nomes traduzidos inline (não no dicionário i18n.jsx, mantém auto-suficiente)
const initiatives = [
{ names: { pt: 'Surf Sagi', en: 'Surf Sagi', it: 'Surf Sagi', de: 'Surf Sagi' },
slug: 'surf-sagi', file: 'projeto-surf-sagi.html', img: 'images/iniciativa-surf-sagi.jpg' },
{ names: { pt: 'Artesanato & Brinquedos', en: 'Crafts & Toys', it: 'Artigianato & Giocattoli', de: 'Kunsthandwerk & Spielzeug' },
slug: 'artesanato', file: 'projeto-artesanato.html', img: 'images/iniciativa-artesanato.jpg' },
{ names: { pt: 'Sagi Vidas', en: 'Sagi Lives', it: 'Sagi Vidas', de: 'Sagi Vidas' },
slug: 'sagi-vidas', file: 'projeto-sagi-vidas.html', img: 'images/iniciativa-sagi-vidas.jpg' },
{ names: { pt: 'Sonhos em Campo', en: 'Dreams on the Field', it: 'Sogni in Campo', de: 'Träume auf dem Feld' },
slug: 'sonhos-campo', file: 'projeto-sonhos-campo.html', img: 'images/iniciativa-sonhos-campo.png' },
{ names: { pt: 'Juntos nós Cuidamos', en: 'Together We Care', it: 'Insieme ci prendiamo cura', de: 'Gemeinsam sorgen wir' },
slug: 'juntos', file: 'projeto-juntos.html', img: 'images/iniciativa-juntos.png' },
{ names: { pt: 'Abrigo ANCA & Educação Profissional', en: 'ANCA Shelter & Vocational Education', it: 'Rifugio ANCA & Formazione professionale', de: 'ANCA-Unterkunft & Berufsausbildung' },
slug: 'anca', file: 'projeto-anca.html', img: 'images/iniciativa-anca.jpg' },
{ names: { pt: 'Roupas & Memórias', en: 'Clothes & Memories', it: 'Vestiti & Memorie', de: 'Kleidung & Erinnerungen' },
slug: 'roupas', file: 'projeto-roupas.html', img: 'images/iniciativa-roupas.jpg' },
{ names: { pt: 'Dia das Crianças para 300 jovens', en: "Children's Day for 300 young people", it: 'Giornata dei Bambini per 300 giovani', de: 'Kindertag für 300 junge Menschen' },
slug: 'dia-criancas', file: 'projeto-dia-criancas.html', img: 'https://eufacoparte.org/uploads/2024/05/Imagem-do-WhatsApp-de-2024-03-25-as-09.06.40_924bb40f.jpg' },
{ names: { pt: 'O direito de celebrar', en: 'The right to celebrate', it: 'Il diritto di festeggiare', de: 'Das Recht zu feiern' },
slug: 'celebrar', file: 'projeto-celebrar.html', img: 'https://eufacoparte.org/uploads/2024/05/Imagem-do-WhatsApp-de-2024-05-17-as-15.50.26_13059e0d-1.jpg' },
{ names: { pt: 'Memórias Afetivas e Saudáveis na Cozinha', en: 'Healthy Memories in the Kitchen', it: 'Memorie Affettive in Cucina', de: 'Liebevolle Erinnerungen in der Küche' },
slug: 'cozinha', file: 'projeto-cozinha.html', img: 'https://eufacoparte.org/uploads/2024/05/Imagem-do-WhatsApp-de-2024-04-21-as-16.35.53_ad5169b4.jpg' },
];
const Initiatives = () => {
const VISIBLE = 4, GAP = 20, DUR = 520;
const total = initiatives.length;
const LEAD = VISIBLE;
const extended = [
...initiatives.slice(-LEAD),
...initiatives,
...initiatives.slice(0, LEAD),
];
const [idx, setIdx] = React.useState(LEAD);
const [anim, setAnim] = React.useState(false);
const [containerW, setContainerW] = React.useState(0);
const [paused, setPaused] = React.useState(false);
const wrapRef = React.useRef(null);
const timerRef = React.useRef(null);
React.useLayoutEffect(() => {
if (!wrapRef.current) return;
const ro = new ResizeObserver(([e]) => setContainerW(e.contentRect.width));
ro.observe(wrapRef.current);
setContainerW(wrapRef.current.offsetWidth);
return () => ro.disconnect();
}, []);
const cardW = containerW ? (containerW - GAP * (VISIBLE - 1)) / VISIBLE : 0;
const step = cardW + GAP;
const goto = (newIdx, animate) => { setAnim(animate); setIdx(newIdx); };
const advance = React.useCallback(() => {
setAnim(true);
setIdx(prev => {
const next = prev + 1;
if (next >= LEAD + total) setTimeout(() => goto(LEAD, false), DUR + 16);
return next;
});
}, [LEAD, total, DUR]);
const retreat = () => {
setAnim(true);
setIdx(prev => {
const next = prev - 1;
if (next < LEAD) setTimeout(() => goto(LEAD + total - 1, false), DUR + 16);
return next;
});
};
React.useEffect(() => {
if (paused) return;
timerRef.current = setInterval(advance, 3500);
return () => clearInterval(timerRef.current);
}, [advance, paused]);
const translateX = -idx * step;
const currentDot = ((idx - LEAD) % total + total) % total;
return (
01.1 · {t('iniciativas.eyebrow')}
setPaused(true)}
onMouseLeave={() => setPaused(false)}
>
{initiatives.map((_, i) => {
const name = initiatives[i].names[window.LANG] || initiatives[i].names.en || initiatives[i].names.pt;
return (
);
};
// ---- Stats count-up ----
const CountUp = ({ numStr, duration = 1800 }) => {
const target = parseInt(numStr.replace(/\./g, ''), 10);
const [val, setVal] = React.useState(0);
const started = React.useRef(false);
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(([entry]) => {
if (!entry.isIntersecting || started.current) return;
started.current = true;
const t0 = performance.now();
const tick = (now) => {
const p = Math.min((now - t0) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3);
setVal(Math.round(eased * target));
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}, { threshold: 0.4 });
obs.observe(el);
return () => obs.disconnect();
}, [target, duration]);
const localeMap = { pt: 'pt-BR', en: 'en-GB', it: 'it-IT', de: 'de-CH' };
return {val.toLocaleString(localeMap[window.LANG] || 'en-GB')};
};
// ---- Stats ----
const Stats = () => {
const stats = [
{ pre: t('stats.s1_pre'), num: '1.200', label: t('stats.s1_label'), body: t('stats.s1_body') },
{ pre: t('stats.s2_pre'), num: '12', label: t('stats.s2_label'), body: t('stats.s2_body') },
{ pre: t('stats.s3_pre'), num: '12', label: t('stats.s3_label'), body: t('stats.s3_body') },
];
return (
02 · {t('stats.eyebrow')}
{stats.map((s, i) => (
{s.pre}
{s.label}
{s.body}
))}
✻
{t('stats.ig')}
→
);
};
// ---- Mapa do Brasil ----
const BRASIL_ACTIVE = new Set(['DF', 'RN', 'RJ', 'GO', 'MG']);
const GEOJSON_URL = 'https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/brazil-states.geojson';
const STATE_TO_SIGLA = {
'Acre':'AC','Alagoas':'AL','Amapá':'AP','Amazonas':'AM','Bahia':'BA',
'Ceará':'CE','Espírito Santo':'ES','Maranhão':'MA','Mato Grosso':'MT',
'Mato Grosso do Sul':'MS','Pará':'PA','Paraíba':'PB','Paraná':'PR',
'Pernambuco':'PE','Piauí':'PI','Rio Grande do Sul':'RS','Rondônia':'RO',
'Roraima':'RR','Santa Catarina':'SC','São Paulo':'SP','Sergipe':'SE','Tocantins':'TO',
};
const BrasilMapSVG = ({ chosen }) => {
const svgRef = React.useRef(null);
const geoRef = React.useRef(null);
const applyFills = (chosenSigla) => {
if (!svgRef.current || typeof d3 === 'undefined') return;
d3.select(svgRef.current).selectAll('path').attr('fill', d => {
const s = d.properties.sigla;
if (BRASIL_ACTIVE.has(s)) return 'var(--accent)';
if (chosenSigla && s === chosenSigla) return 'oklch(0.42 0.13 145)';
return 'var(--rule-strong)';
});
};
React.useEffect(() => {
if (!svgRef.current || typeof d3 === 'undefined') return;
const W = 400, H = 460;
fetch(GEOJSON_URL)
.then(r => r.json())
.then(geo => {
geoRef.current = geo;
const projection = d3.geoMercator().fitSize([W, H], geo);
const pathGen = d3.geoPath().projection(projection);
const svg = d3.select(svgRef.current);
svg.selectAll('*').remove();
svg.selectAll('path')
.data(geo.features)
.join('path')
.attr('d', pathGen)
.attr('stroke', 'var(--paper)')
.attr('stroke-width', 0.8);
applyFills(chosen ? STATE_TO_SIGLA[chosen] : null);
});
}, []);
React.useEffect(() => {
if (geoRef.current) applyFills(chosen ? STATE_TO_SIGLA[chosen] : null);
}, [chosen]);
return ;
};
const MAPA_INACTIVE_STATES = [
'Acre','Alagoas','Amapá','Amazonas','Bahia','Ceará',
'Espírito Santo','Maranhão','Mato Grosso','Mato Grosso do Sul',
'Pará','Paraíba','Paraná','Pernambuco','Piauí',
'Rio Grande do Sul','Rondônia','Roraima','Santa Catarina',
'São Paulo','Sergipe','Tocantins',
];
const BrasilMap = () => {
const [picking, setPicking] = React.useState(false);
const [chosen, setChosen] = React.useState(null);
const select = (state) => { setChosen(state); setPicking(false); };
return (
01.2 · {t('mapa.eyebrow')}
{t('mapa.legend_active')}
{t('mapa.legend_inactive')}
{chosen ? (
{t('mapa.coming_to')} {chosen} {t('mapa.heart')}
) : (
<>
{picking && (
{MAPA_INACTIVE_STATES.map(s => (
))}
)}
>
)}
{['Distrito Federal', 'Rio Grande do Norte', 'Rio de Janeiro', 'Goiás', 'Minas Gerais'].map(s => (
- {s}
))}
);
};
// ── Ações Recentes & Arrecadações ────────────────────────────
const AcaoCard = ({ acao }) => (
{ e.currentTarget.style.boxShadow = '0 8px 28px oklch(0.3 0.02 70 / 0.1)'; e.currentTarget.style.transform = 'translateY(-2px)'; }}
onMouseLeave={e => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }}
>

e.currentTarget.style.transform = 'scale(1.05)'}
onMouseLeave={e => e.currentTarget.style.transform = 'scale(1)'}
onError={e => { e.currentTarget.closest('div').style.background = 'var(--paper-3)'; e.currentTarget.style.display = 'none'; }}
/>
{acao.badge}
{acao.category}
{acao.title}
{acao.desc}
✦ {acao.impact}
{t('acoes.progress')}
100% ✓
);
const ACOES_VISIBLE = 4;
const ACOES_GAP = 18;
const ACOES_DUR = 480;
const AcoesRecentes = () => {
const ACOES = [
{ badge: t('acoes.a1_badge'), category: t('acoes.a1_cat'), title: t('acoes.a1_title'), desc: t('acoes.a1_desc'), impact: t('acoes.a1_impact'), img: 'images/acao-rede-pais-atipicos-2026.jpg' },
{ badge: t('acoes.a2_badge'), category: t('acoes.a2_cat'), title: t('acoes.a2_title'), desc: t('acoes.a2_desc'), impact: t('acoes.a2_impact'), img: 'images/acao-dia-das-maes-2026.jpg' },
{ badge: t('acoes.a3_badge'), category: t('acoes.a3_cat'), title: t('acoes.a3_title'), desc: t('acoes.a3_desc'), impact: t('acoes.a3_impact'), img: 'images/acao-pascoa-solidaria-2026.jpg' },
{ badge: t('acoes.a4_badge'), category: t('acoes.a4_cat'), title: t('acoes.a4_title'), desc: t('acoes.a4_desc'), impact: t('acoes.a4_impact'), img: 'https://eufacoparte.org/uploads/2025/09/peneira_social_atletas_eu_faco_parte_movimento_do_esporte.png' },
{ badge: t('acoes.a5_badge'), category: t('acoes.a5_cat'), title: t('acoes.a5_title'), desc: t('acoes.a5_desc'), impact: t('acoes.a5_impact'), img: 'https://eufacoparte.org/uploads/2025/01/3.png' },
];
const [idx, setIdx] = React.useState(0);
const [anim, setAnim] = React.useState(true);
const wrapRef = React.useRef(null);
const [cardW, setCardW] = React.useState(0);
const n = ACOES.length;
const track = [...ACOES, ...ACOES.slice(0, ACOES_VISIBLE)];
React.useEffect(() => {
const update = () => {
if (!wrapRef.current) return;
const w = wrapRef.current.offsetWidth;
setCardW((w - ACOES_GAP * (ACOES_VISIBLE - 1)) / ACOES_VISIBLE);
};
update();
const ro = new ResizeObserver(update);
if (wrapRef.current) ro.observe(wrapRef.current);
return () => ro.disconnect();
}, []);
React.useEffect(() => {
const iv = setInterval(() => {
setAnim(true);
setIdx(prev => {
const next = prev + 1;
if (next >= n) setTimeout(() => { setAnim(false); setIdx(0); }, ACOES_DUR + 16);
return next;
});
}, 10000);
return () => clearInterval(iv);
}, [n]);
return (
{track.map((a, i) => (
))}
);
};
window.ApoioProjetos = ApoioProjetos;
window.Initiatives = Initiatives;
window.Stats = Stats;
window.BrasilMap = BrasilMap;
window.AcoesRecentes = AcoesRecentes;