/* ORGULLO MILLONARIO · Landing modular — React + Tailwind · Liquid Glass iOS 26
Réplica del sitio completo. Cada sección Y cada bloque (tarjetas/banners) se
prende/apaga desde el Panel SaaS (config.sections[].on + blocks[].on) y se
refleja EN VIVO vía OM-DB. */
const {useState,useEffect,useRef,useMemo,useSyncExternalStore,createContext,useContext}=React;
const useDB=()=>useSyncExternalStore(OMDB.subscribe,OMDB.get);
/* ---- icons ---- */
const I={
home: ,
cal:<> >,
ticket:<> >,
chat: ,
user:<> >,
wa: ,
ig:<> >,
yt:<> >,
tk: ,
x: ,
arrow: ,
lock:<> >,
play: ,
chev: ,
up: ,
};
const Ic=({n,s=18,c=""})=>{I[n]} ;
/* ---- crests (imagen si existe, si no badge con iniciales) ---- */
const CRESTS={river:'assets/river.png',boca:'assets/boca.png',barracas:'assets/barracas.png',argentinos:'assets/argentinos.png',racing:'assets/racing.png',talleres:'assets/talleres.png',gimnasia:'assets/gimnasia.png',rosario:'assets/rosario.png',flamengo:'assets/flamengo.png'};
function Crest({k,name,s=32,logo}){
const src=logo||CRESTS[k];
if(src)return ;
const ini=(name||'?').replace(/[^A-Za-zÁÉÍÓÚÑ ]/g,'').split(/\s+/).filter(Boolean).slice(0,2).map(w=>w[0]).join('').toUpperCase();
return {ini} ;
}
const FORMA={G:['#2fe07a','rgba(47,224,122,.16)'],P:['#ff5a63','rgba(255,90,99,.16)'],E:['#e0a24e','rgba(224,162,78,.16)']};
const FormaBadges=({str,s=24})=>
{str.split('').map((r,i)=>{const[c,bg]=FORMA[r]||FORMA.E;return {r} ;})}
;
/* ---- toast ---- */
const ToastCtx=createContext(()=>{});
function ToastHost({children}){
const [msg,setMsg]=useState(null);const t=useRef();
const toast=m=>{setMsg(m);clearTimeout(t.current);t.current=setTimeout(()=>setMsg(null),2400);};
return {children}
;
}
/* ---- countdown ---- */
function useCountdown(iso){const [d,setD]=useState(()=>calc(iso));useEffect(()=>{const t=setInterval(()=>setD(calc(iso)),1000);return()=>clearInterval(t);},[iso]);return d;}
function calc(iso){let ms=Math.max(0,new Date(iso).getTime()-Date.now());const p=n=>String(n).padStart(2,'0');const dd=Math.floor(ms/864e5);ms-=dd*864e5;const hh=Math.floor(ms/36e5);ms-=hh*36e5;const mm=Math.floor(ms/6e4);const ss=Math.floor((ms-mm*6e4)/1000);return {dd:p(dd),hh:p(hh),mm:p(mm),ss:p(ss)};}
function faltan(iso){let ms=new Date(iso)-Date.now();if(ms<0)return 'Jugado';const d=Math.floor(ms/864e5);const h=Math.floor((ms%864e5)/36e5);return `Faltan ${d}d ${h}h`;}
const Chip=({children,c='#ff2e3e'})=>{children} ;
const Eyebrow=({k,t,sub})=>(
);
const CountBoxes=({c,accent='#ffcf3e'})=>(
{[['DÍAS',c.dd],['HS',c.hh],['MIN',c.mm],['SEG',c.ss]].map(([l,v])=>(
{v} {l}
))}
);
const Bar=({pct,bg})=>
;
/* helper visibilidad — la modularización SOLO aplica si el panel la activó */
function useVis(){
const db=useDB();const modular=!!db.config.modular;const secs=db.config.sections||[];
const blockOn=(secId,blkId)=>{if(!modular)return true;const s=secs.find(x=>x.id===secId);if(!s)return true;const b=(s.blocks||[]).find(x=>x.id===blkId);return b?b.on:true;};
return {secs,blockOn,modular};
}
/* ============ SHIRT 360 ============ */
function Shirt360({front,back,s=150}){
const [ang,setAng]=useState(0);const drag=useRef(null);const auto=useRef(true);const resume=useRef();
useEffect(()=>{let last=performance.now(),raf;const loop=t=>{const dt=t-last;last=t;if(auto.current)setAng(a=>(a+dt*0.045)%360);raf=requestAnimationFrame(loop);};raf=requestAnimationFrame(loop);return()=>cancelAnimationFrame(raf);},[]);
const down=e=>{auto.current=false;clearTimeout(resume.current);drag.current={x:e.clientX,a:ang};e.currentTarget.setPointerCapture&&e.currentTarget.setPointerCapture(e.pointerId);};
const move=e=>{if(drag.current)setAng(drag.current.a+(e.clientX-drag.current.x)*0.8);};
const up=()=>{if(drag.current){drag.current=null;resume.current=setTimeout(()=>auto.current=true,2200);}};
return
↺ ARRASTRÁ PARA GIRAR 360°
;
}
/* ============ HERO ============ */
function Hero(){
const db=useDB();const p=db.partido;const c=useCountdown(p.fecha);const k=db.kpis;
return
FILIAL RIVER · CORRIENTES
{db.config.hero_titulo}
{k.socios} en vivo
|
{k.visitas} visitas
● PRÓXIMO PARTIDO
{p.local} LOCAL
VS
{p.visitante} VISITANTE
📅 25 JUL 🏆 {p.competencia} 📍 Monumental
;
}
/* ============ CENTRO DE DATOS ============ */
function Centro(){
const db=useDB();const {blockOn}=useVis();const d=db.datos;
const live=!!(window.OMDB&&OMDB.isLive&&OMDB.isLive());
const _xgMax=Math.max(d.xg.river,d.xg.rivalXg);
return
● {live?'EN VIVO':'DEMO'} · {d.liga}
{blockOn('centro','tabla')&&
🏆 TABLA · ZONA DE RIVER Liga Prof. 2026
# EQUIPO DG PTS
}
{blockOn('centro','forma')&&
📋 FORMA RIVER · ÚLTIMOS 5
■ Ganó ■ Empató ■ Perdió
}
{blockOn('centro','xg')&&
}
{blockOn('centro','ad')&&
ESPACIO PUBLICITARIO
300 × 250 · configurable desde el panel
}
;
}
/* ============ PROBABILIDADES 1X2 ============ */
function Prob(){
const db=useDB();const p=db.datos.pred;const pt=db.partido;
const max=Math.max(...p.scores.map(s=>s.p));
return
◈ PROBABILIDADES · PRÓXIMO PARTIDO {p.modelo}
River
VS 25/7 · 19:15
📍 Monumental
{pt.visitante}
RESULTADO FINAL (1X2)
Gana River {p.home}% Empate {p.draw}% Gana rival {p.away}%
AMBOS EQUIPOS MARCAN
Sí {p.bttsYes}% No {p.bttsNo}%
RESULTADOS MÁS PROBABLES
{p.scores.map((s,i)=>(
{s.s} {s.p}%
))}
Actualizado 04:01:19 · se refresca cada 60s
;
}
/* ============ CALENDARIO ============ */
const CAL=[
{dia:'25',mes:'JUL',rival:'Barracas Central',crest:'barracas',comp:'Liga Profesional · Jornada 1 · Monumental',cond:'LOCAL',iso:'2026-07-25T19:15:00-03:00'},
{dia:'29',mes:'JUL',rival:'Gimnasia La Plata',crest:'gimnasia',comp:'Liga Profesional · Jornada 2 · Visitante',cond:'VISITA',iso:'2026-07-29T19:15:00-03:00'},
{dia:'02',mes:'AGO',rival:'Rosario Central',crest:'rosario',comp:'Liga Profesional · Jornada 3 · Monumental',cond:'LOCAL',iso:'2026-08-02T19:15:00-03:00'},
{dia:'09',mes:'AGO',rival:'Tigre',crest:'tigre',comp:'Liga Profesional · Jornada 4 · Visitante',cond:'VISITA',iso:'2026-08-09T21:00:00-03:00'},
];
function Calendario(){
const {blockOn}=useVis();
return
● Sincronizado · API fútbol
{CAL.map((m,i)=>(
{m.dia} {m.mes}
VS
River vs {m.rival}🏆 {m.comp}
))}
{blockOn('calendario','sponsor')&&
SPONSOR OFICIAL · RIVER PLATE
}
;
}
/* ============ TICKETS & VIAJES ============ */
function Viajes(){
const db=useDB();const toast=useContext(ToastCtx);
return
{db.viajes.map((v,i)=>{const pct=Math.round((1-v.cupos/v.total)*100);const last=v.cupos<=20&&v.cupos>0;
return
VIAJE OFICIAL {v.nuevo&&NUEVO · WHATSAPP }
{v.destino}
VS {v.rival} · {v.comp}
🗓 {v.fecha} 🚌 {v.salida} 📍 {v.punto} 👥 {v.cupos} de {v.total} cupos libres
{last?'¡Últimos lugares!':'Cupos disponibles'}
{location.href=(window.OMAuth&&OMAuth.current())?'dashboard.html':'register.html';}} className="press glass rounded-2xl py-3 mt-4 text-[12px] font-bold text-mut hover:text-ink w-full">Reservá creando tu cuenta de socio ↓
;})}
;
}
/* ============ SORTEOS & PREMIOS ============ */
function Sorteos(){
const db=useDB();
return
{db.premios.map((pr,i)=>(
{pr.kicker} {pr.badge}
{pr.titulo}
{pr.dorso?
: }
{pr.precio}
{location.href=(window.OMAuth&&OMAuth.current())?'dashboard.html':'register.html';}} className="press glass rounded-2xl py-3 mt-3 text-[12px] font-bold text-mut hover:text-ink w-full">Participá creando tu cuenta ↓
))}
;
}
/* ============ ÚLTIMOS RESULTADOS ============ */
function Resultados(){
const db=useDB();const r=db.resultados;const [mes,setMes]=useState(r.activo);
return
🗓 TEMPORADA {r.anio}
{r.meses.map(([m,n])=>(
n&&setMes(m)} disabled={!n} className={`press flex-none font-mono text-[10px] tracking-[1px] px-3 py-2 rounded-xl border transition-colors ${mes===m?'text-white border-river bg-river/15':n?'text-mut border-white/10 hover:text-ink':'text-dim border-transparent opacity-40'}`}>{m} {n>0&&{n} } ))}
{r.mesLabel} {r.anio}
{r.lista.map((m,i)=>(
{m.comp} {m.fecha}
{m.local}
{m.gl!=null?
{m.gl} - {m.gv} {m.estado}
:
}
{m.visita}
))}
;
}
/* ============ ESTADÍSTICAS · SUPERCLÁSICO ============ */
function Stats(){
const db=useDB();const cl=db.clasico;const h=db.historial;const c=useCountdown(cl.fecha);
return
● ✕ EL CLÁSICO DE CLÁSICOS
RIVER VISITANTE
VS DOM 1 NOV
20:00 hs · Jornada 15
BOCA LOCAL · La Bombonera
RIVER
FORMA RECIENTE
BOCA
;
}
/* ============ PLANTILLA 2026 ============ */
function Plantilla(){
const db=useDB();const {blockOn}=useVis();const f=db.formacion;const h=db.historial;
const Face=({p,gk})=>(
{p.foto? :{(p.n||'?')[0]} }
{p.n}
);
const Row=({arr})=>{(arr||[]).map((p,i)=>)}
;
return
👥 PLANTILLA 2026 {f.esquema}
{location.href='dashboard.html';}} className="press rounded-2xl py-3 mt-4 w-full text-[13px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 12px 30px -8px rgba(224,30,47,.5)'}}>Ver plantilla completa →
{blockOn('plantilla','historial')&&
📋 HISTORIAL SUPERCLÁSICOS
{[[h.ganoRiver,'GANÓ RIVER','#2fe07a'],[h.empates,'EMPATES','#e0a24e'],[h.ganoBoca,'GANÓ BOCA','#2f72e0']].map(([n,l,c],i)=>(
{n} {l}
))}
{h.total} clásicos oficiales · último: {h.ultimo}
ÚLTIMOS CRUCES
{h.cruces.map(([f,s,r],i)=>{const[c,bg]=FORMA[r];return
{f} {s} {r}
;})}
}
{blockOn('plantilla','ad')&&
PUBLICIDAD DE SOCIO EXCLUSIVO
}
;
}
/* ============ SPONSORS ============ */
function Sponsors(){
const db=useDB();const {blockOn}=useVis();const TC={ORO:'#ffcf3e',PLATA:'#c0c0cb',BRONCE:'#d9915a'};
return
{blockOn('sponsors','gob')&&}
{[...(db.sponsors_locales||[]),...(db.sponsors_locales||[])].map((s,i)=>(
{s.tier}
{s.logo?
:
{s.nombre} }
))}
★ NUESTROS SPONSORS OFICIALES
;
}
/* ============ PRODE ============ */
function Prode(){
const db=useDB();const {blockOn}=useVis();const pr=db.prode;const pt=db.partido;const [vid,setVid]=useState(false);
return
Acertá y ganá hasta {pr.descuento} en tu próximo viaje>}/>
River vs {pt.visitante}
● PRÓXIMO · EN VIVO API
Registrate para jugar el Prode
Es una función exclusiva de socios. Creá tu cuenta y elegí un plan Plata, Oro o VIP para jugar cada partido y ganar descuentos reales.
location.href='register.html'} className="press rounded-2xl py-3 px-6 text-[13px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 12px 30px -8px rgba(224,30,47,.5)'}}>Registrate para jugar →
{blockOn('prode','video')&&
LO ÚLTIMO DE LA BANDA ● VIDEO DESTACADO
setVid(true)} className="press relative rounded-2xl overflow-hidden flex-1 min-h-[260px]" style={{background:'#111'}}>
{pr.video.titulo}
}
{vid&&setVid(false)}/>}
;
}
/* ============ REELS ============ */
function ytId(u){const m=(u||'').match(/(?:youtu\.be\/|v=|shorts\/|embed\/)([\w-]{11})/);return m?m[1]:null;}
function ttId(u){const m=(u||'').match(/video\/(\d+)/);return m?m[1]:null;}
function VideoModal({reel,onClose}){
const yt=reel.plataforma==='YOUTUBE'&&ytId(reel.url);const tt=reel.plataforma==='TIKTOK'&&ttId(reel.url);
return
e.stopPropagation()} className="glass-deep relative rounded-glass p-3 w-full max-w-[420px]" style={{animation:'sheetUp .35s cubic-bezier(.22,1,.36,1) both'}}>
{reel.titulo} ✕
;
}
function Reels(){
const db=useDB();const [open,setOpen]=useState(null);const reels=db.reels||[];
return
{reels.map((r,i)=>{const yt=r.plataforma==='YOUTUBE'&&ytId(r.url);const thumb=yt?`https://i.ytimg.com/vi/${yt}/hqdefault.jpg`:null;
return
setOpen(i)} className="press block w-full">
{thumb?
:
▶ }
{r.plataforma}
{r.titulo}
;})}
{open!==null&&reels[open]&&setOpen(null)}/>}
;
}
/* ============ TESTIMONIOS ============ */
function Testimonios(){
const db=useDB();
const ini=n=>n.split(/\s+/).slice(0,2).map(w=>w[0]).join('').toUpperCase();
return
{(db.testimonios||[]).map((t,i)=>(
“
🏆 {t.tag}
★★★★★
{t.texto}
{ini(t.nombre)}
{t.nombre} {t.lugar.toUpperCase()}
))}
;
}
/* ============ CONTACTO ============ */
function Contacto(){
const db=useDB();const [subj,setSubj]=useState('Reservar un viaje');const [nombre,setNombre]=useState('');const [msg,setMsg]=useState('');
const wa=db.config.whatsapp.replace(/[^0-9]/g,'');
const send=()=>{const txt=encodeURIComponent(`Hola Orgullo Millonario 👋 Asunto: ${subj}${nombre?'. Soy '+nombre:''}${msg?'. '+msg:'.'}`);window.open(`https://wa.me/${wa}?text=${txt}`,'_blank');};
const subjects=[['Reservar un viaje','ticket','#ff2e3e'],['Ser sponsor','home','#ffcf3e'],['Sorteos','home','#8b5cf6'],['Consulta general','chat','#2fe07a']];
const socials=[['ig','#ee2a7b','https://instagram.com/'+db.config.instagram],['wa','#25d366','https://wa.me/'+wa],['yt','#ff3b30','#'],['tk','#22d3ee','#'],['x','#f6f1f2','#']];
return
HABLEMOS
CONTACTO
¿Querés reservar un viaje, ser sponsor o tenés una consulta? Respondemos en menos de 24 hs.
{socials.map(([n,c,href])=>
)}
Términos y Condiciones de uso
;
}
/* ============ NAV / APP ============ */
const SECMAP={hero:Hero,centro:Centro,prob:Prob,calendario:Calendario,viajes:Viajes,sorteos:Sorteos,resultados:Resultados,stats:Stats,sponsors:Sponsors,prode:Prode,plantilla:Plantilla,reels:Reels,testimonios:Testimonios,contacto:Contacto};
const NAV=[['hero','home','Inicio'],['calendario','cal','Calendario'],['viajes','ticket','Viajes'],['testimonios','chat','Foro'],['cuenta','user','Mi Cuenta']];
function App(){
const db=useDB();const toast=useContext(ToastCtx);
const light=db.config.tema==='light';
useEffect(()=>{document.body.classList.toggle('theme-light',light);},[light]);
const [top,setTop]=useState(false);
useEffect(()=>{const f=()=>setTop(window.scrollY>600);window.addEventListener('scroll',f);return()=>window.removeEventListener('scroll',f);},[]);
const go=id=>{if(id==='cuenta'){location.href=(window.OMAuth&&OMAuth.current())?'dashboard.html':'login.html';return;}const el=document.getElementById('s-'+id);if(el)window.scrollTo({top:el.getBoundingClientRect().top+window.scrollY-96,behavior:'smooth'});};
const sections=(db.config.sections||[]).filter(x=>SECMAP[x.id]&&(!db.config.modular||x.on));
return <>
ORGULLO MILLONARIO FILIAL RIVER · CORRIENTES
{NAV.map(([id,ic,l])=>go(id)} className="press flex items-center gap-2 px-3.5 py-2 rounded-full text-[11px] font-semibold text-mut hover:text-ink hover:bg-white/[.07] transition-colors">{l} )}
{OMDB.set('config.tema',light?'dark':'light');toast(light?'Tema oscuro':'Tema claro');}} className="press w-9 h-9 rounded-full glass grid place-items-center text-gold2" title="Cambiar tema">{light?'☾':'☀'}
go('cuenta')} className="press hidden sm:flex items-center gap-2 px-4 py-2 rounded-full text-[11px] font-bold text-white" style={{background:'linear-gradient(160deg,#ff2e3e,#8d0917)',boxShadow:'0 8px 24px -6px rgba(224,30,47,.6)'}}>Mi Cuenta
{sections.map(x=>{const C=SECMAP[x.id];return
;})}
window.scrollTo({top:0,behavior:'smooth'})} className={`press fixed bottom-20 md:bottom-6 right-4 z-[70] w-11 h-11 rounded-full glass-deep grid place-items-center text-river2 transition-all ${top?'opacity-100':'opacity-0 pointer-events-none'}`}>
{NAV.map(([id,ic,l])=>go(id)} className="press flex flex-col items-center gap-1 px-2.5 py-1.5 rounded-2xl text-mut hover:text-ink">{l} )}
>;
}
ReactDOM.createRoot(document.getElementById('root')).render( );