/* Running Club — Album. Contenu piloté par config.json. */ const THEME = { bg: '#F6F1E6', text: '#1a1714', accent: '#F2B33D', card: '#fff' }; /* ---------- helpers ---------- */ function bToast(msg) { const el = document.createElement('div'); el.textContent = msg; el.style.cssText = 'position:fixed;left:50%;bottom:32px;transform:translateX(-50%);z-index:99999;' + 'background:#1a1714;color:#fff;padding:10px 18px;border-radius:999px;font:600 14px Archivo,sans-serif;' + 'box-shadow:0 8px 30px rgba(0,0,0,.3);opacity:0;transition:opacity .2s,transform .2s;pointer-events:none'; document.body.appendChild(el); requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(-50%) translateY(-6px)'; }); setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 250); }, 1600); } async function shareMedia(m) { const data = { title: 'Running Club', text: m.note || 'Sortie du Running Club' }; try { if (navigator.share) { await navigator.share(data); return; } } catch (e) {} try { await navigator.clipboard.writeText(data.text); } catch (e) {} bToast('Lien copié dans le presse-papier'); } function downloadMedia(m) { if (m.fichier) { const a = document.createElement('a'); a.href = 'media/' + m.fichier; a.download = m.fichier.split('/').pop(); a.click(); bToast('Téléchargement…'); return; } // placeholder → export canvas const pal = window.CLUB.SCENES[m.scene] || ['#2b2b2f', '#9a9aa2']; const W = 1080, H = 1080, c = document.createElement('canvas'); c.width = W; c.height = H; const ctx = c.getContext('2d'); const g = ctx.createLinearGradient(0, 0, W, H); g.addColorStop(0, pal[1]); g.addColorStop(1, pal[0]); ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); ctx.fillStyle = 'rgba(0,0,0,.28)'; ctx.fillRect(0, H - 240, W, 240); ctx.fillStyle = '#fff'; ctx.font = '700 40px Archivo, sans-serif'; const words = (m.note || '').split(' '); let line = '', y = H - 160; for (const w of words) { if (ctx.measureText(line + w + ' ').width > W - 140 && line) { ctx.fillText(line.trim(), 70, y); line = w + ' '; y += 50; } else line += w + ' '; } ctx.fillText(line.trim(), 70, y); ctx.font = '600 26px "Space Mono", monospace'; ctx.fillStyle = 'rgba(255,255,255,.7)'; ctx.fillText('RUNNING CLUB', 70, 70); c.toBlob(b => { const u = URL.createObjectURL(b); const a = document.createElement('a'); a.href = u; a.download = 'media-' + (m.id || 'media') + '.png'; a.click(); setTimeout(() => URL.revokeObjectURL(u), 1000); }); bToast('Téléchargement du média…'); } /* ---------- config loading ---------- */ function normalize(cfg) { const sorties = (cfg.sorties || []).map((s, si) => { const id = s.id || ('s' + si); const media = (s.medias || s.media || []).map((m, mi) => ({ ...m, id: id + '-' + mi, type: m.type || 'photo' })); return { ...s, id, titre: s.titre || s.title || 'Sortie', media }; }); return { club: cfg.club || {}, sorties }; } function useConfig() { const [state, setState] = React.useState({ loading: true }); React.useEffect(() => { fetch('config.json', { cache: 'no-store' }) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(j => setState({ loading: false, data: normalize(j) })) .catch(e => setState({ loading: false, error: String(e) })); }, []); return state; } /* ---------- lightbox ---------- */ function Lightbox({ items, index, onClose, onIndex }) { const m = items[index]; const A = window.CLUB.ANTENNES[m._ctx.antenne] || { label: m._ctx.antenne, hue: 45 }; const go = (d) => { const n = index + d; if (n >= 0 && n < items.length) onIndex(n); }; React.useEffect(() => { const k = (e) => { if (e.key === 'Escape') onClose(); if (e.key === 'ArrowRight') go(1); if (e.key === 'ArrowLeft') go(-1); }; window.addEventListener('keydown', k); return () => window.removeEventListener('keydown', k); }, [index, items]); return (
{index + 1} / {items.length}
{index > 0 && } {index < items.length - 1 && }
{A.label} {window.CLUB.fmtDate(m._ctx.date)}

{m.note}

{m._ctx.distance} km {m._ctx.allure}/km {m._ctx.denivele != null && {m._ctx.denivele} m D+}
); } function navArrow(side) { return { position: 'absolute', [side]: 8, top: '50%', transform: 'translateY(-50%)', width: 40, height: 40, borderRadius: '50%', border: 'none', cursor: 'pointer', background: 'rgba(0,0,0,.4)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center' }; } function actionBtn(bg, color) { return { flex: 1, padding: '12px 0', borderRadius: 11, border: 'none', cursor: 'pointer', background: bg, color, font: '700 14px Archivo, sans-serif', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7 }; } /* ---------- main app ---------- */ function ClassiqueApp() { const cfg = useConfig(); const [filter, setFilter] = usePersisted('club_filter', 'all'); const [view, setView] = React.useState(null); const A = window.CLUB.ANTENNES; if (cfg.loading) return
Chargement de l’album…
; if (cfg.error) return (
Impossible de charger config.json {cfg.error}
); const { club, sorties } = cfg.data; const shown = sorties.filter(s => filter === 'all' || s.antenne === filter); const totalKm = Math.round(sorties.reduce((a, s) => a + (Number(s.distance) || 0), 0)); const chips = [{ id: 'all', label: 'Tout' }, ...Object.values(A).map(a => ({ id: a.id, label: a.label }))]; return (
RUNNING · CLUB
{club.slogan || 'L’album des sorties'}
{[[ 'runner', totalKm, 'km cumulés' ], [ 'flame', sorties.length, 'sorties' ]].map(([ic, n, l]) => (
{n}
{l}
))}
{chips.map(c => ( ))}
{shown.length === 0 &&
Aucune sortie pour cette antenne.
} {shown.map(s => { const items = s.media.map((m, i) => ({ ...m, _ctx: s, _i: i })); const a = A[s.antenne] || { label: s.antenne, hue: 45 }; return (

{s.titre}

{window.CLUB.fmtDateShort(s.date)}· {s.distance} km·{s.allure}/km
{a.label}
{items.map((m, i) => ( ))}
); })}
{view && setView(null)} onIndex={(n) => setView({ ...view, index: n })} />}
); } const center = { display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '60vh', font: '600 15px Archivo, sans-serif', color: '#1a1714' }; window.ClassiqueApp = ClassiqueApp;