const { useState, useEffect, useRef, useCallback } = React; const TV_INACT_MS_3 = 30000; function useTvMode3() { const [isTv] = useState(() => { try { return new URLSearchParams(window.location.search).has('tv'); } catch (e) { return false; } }); return isTv; } function useInactivity3(active, onTimeout, ms = TV_INACT_MS_3) { const [progress, setProgress] = useState(1); const last = useRef(Date.now()); useEffect(() => { if (!active) { setProgress(1); return; } let raf = 0; const reset = () => { last.current = Date.now(); }; const events = ['touchstart', 'click', 'mousemove', 'keydown', 'wheel']; events.forEach(e => window.addEventListener(e, reset, { passive: true })); const tick = () => { const elapsed = Date.now() - last.current; setProgress(Math.max(0, 1 - elapsed / ms)); if (elapsed >= ms) onTimeout(); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { cancelAnimationFrame(raf); events.forEach(e => window.removeEventListener(e, reset)); }; }, [active, ms, onTimeout]); return progress; } function App3() { const [data, setData] = useState(null); const [err, setErr] = useState(null); const isTv = useTvMode3(); const [route, setRoute] = useState(() => ({ screen: isTv ? 'veille' : 'etapes', etapeIndex: null, entreprise: null, activeFilter: null, })); const routeRef = useRef(route); useEffect(() => { routeRef.current = route; }, [route]); useEffect(() => { fetch('data/aluminium.json').then(r => r.json()).then(setData).catch(e => setErr(String(e))); }, []); // Initialise la première entrée d'historique useEffect(() => { if (isTv) return; try { history.replaceState({ screen: 'etapes', etapeIndex: null, entreprise: null, activeFilter: null }, ''); } catch(e) {} }, []); // Retour / avance navigateur useEffect(() => { if (isTv) return; const onPop = (e) => { const s = e.state; if (s?.screen) setRoute(s); else setRoute({ screen: 'etapes', etapeIndex: null, entreprise: null }); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, [isTv]); const push = useCallback((newRoute, hash) => { if (!isTv) try { history.pushState(newRoute, '', hash || '#'); } catch(e) {} setRoute(newRoute); }, [isTv]); const goVeille = useCallback(() => setRoute({ screen: 'veille', etapeIndex: null, entreprise: null }), []); const goEtapes = useCallback(() => push({ screen: 'etapes', etapeIndex: null, entreprise: null }, '#'), [push]); const pickEtape = (i) => push({ screen: 'detail', etapeIndex: i, entreprise: null, activeFilter: null }, '#etape-' + i); const onFilterChange = (filterId) => push({ ...routeRef.current, activeFilter: filterId }, '#etape-' + routeRef.current.etapeIndex + '-f'); const pickEnt = (e) => push({ ...routeRef.current, entreprise: e }, '#ent'); const closeEnt = () => push({ ...routeRef.current, entreprise: null }, routeRef.current.etapeIndex != null ? '#etape-' + routeRef.current.etapeIndex : '#'); const inactivityActive = isTv && route.screen !== 'veille'; const progress = useInactivity3(inactivityActive, goVeille); if (err) return

Erreur

{err}

; if (!data) return
Chargement…
; if (route.screen === 'veille') return ; const currentEtape = route.etapeIndex != null ? data.etapes[route.etapeIndex] : null; const isTransversal = route.etapeIndex != null && route.etapeIndex >= 11; // Étapes fusionnées : 4↔5 (Transformation intermédiaire), 6↔7 (Fabrication pièces) const MERGED = { 4: 5, 5: 4, 6: 7, 7: 6 }; const MERGED_NAMES = { 4: 'Transformation intermédiaire', 5: 'Transformation intermédiaire', 6: 'Fabrication (pièces)', 7: 'Fabrication (pièces)' }; const isMerged = !isTransversal && route.etapeIndex != null && route.etapeIndex in MERGED; const mergedNav = isMerged ? (() => { const idx = route.etapeIndex; const paired = MERGED[idx]; const aluIdx = idx % 2 === 1 ? idx : paired; // impair = pour alumineries const genIdx = idx % 2 === 0 ? idx : paired; // pair = général return { active: idx % 2 === 1 ? 0 : 1, // 0 = pour alumineries, 1 = général mergedName: MERGED_NAMES[idx], onPick: (i) => pickEtape(i === 0 ? aluIdx : genIdx), }; })() : null; const crumbName = route.screen !== 'etapes' && currentEtape ? (mergedNav ? mergedNav.mergedName : currentEtape.nom) : ''; return (
{route.screen === 'etapes' ? 'Circuit · Vue d\'ensemble' : crumbName}
{route.screen === 'etapes' && ( pickEtape(11 + idx)} /> )} {route.screen === 'detail' && currentEtape && ( pickEtape(11 + i), } : null} mergedNav={mergedNav} /> )} {route.entreprise && ( )}
{isTv && ( ); } const root3 = ReactDOM.createRoot(document.getElementById('root')); root3.render();