// hub-components.jsx β€” Shared constants, algorithms, SpiderChart, RevealAnimation const ATTRS = [ { key: 'hiz', label: 'HΔ±z', icon: '⚑' }, { key: 'sut', label: 'Şut', icon: '🎯' }, { key: 'pas', label: 'Pas', icon: 'πŸ”€' }, { key: 'dripling', label: 'Dripling', icon: 'πŸŒ€' }, { key: 'defans', label: 'Defans', icon: 'πŸ›‘' }, { key: 'fizik', label: 'Fizik', icon: 'πŸ’ͺ' }, ]; const POSITIONS = ['KALECΔ°', 'DEFANS', 'ORTA SAHA', 'KANAT', 'FORVET']; const POS_SHORT = { 'KALECΔ°':'GK', 'DEFANS':'DEF', 'ORTA SAHA':'MID', 'KANAT':'WNG', 'FORVET':'FWD' }; const DEFAULT_SCORES = { hiz:5, sut:5, pas:5, dripling:5, defans:5, fizik:5 }; function calcOverall(scores) { if (!scores) return 0; return parseFloat((ATTRS.reduce((s, a) => s + (scores[a.key] || 0), 0) / ATTRS.length).toFixed(1)); } function getTitle(scores) { const { hiz, sut, pas, dripling, defans, fizik } = scores; const avg = calcOverall(scores); if (hiz >= 8.5 && dripling >= 8) return { title:'FİŞEK', sub:'Lightning Winger', color:'#FFD700' }; if (defans >= 8.5 && fizik >= 8) return { title:'TANK', sub:'Defensive Wall', color:'#CC6600' }; if (pas >= 8.5 && hiz >= 7) return { title:'MAESTRO', sub:'Playmaker', color:'#9B59B6' }; if (sut >= 8.5 && hiz >= 7) return { title:'AVCI', sub:'Clinical Finisher', color:'#E74C3C' }; if (avg >= 8) return { title:'EFSANE', sub:'Complete Player', color:'#39FF14' }; if (fizik >= 8.5) return { title:'DUVAR', sub:'Physical Beast', color:'#7F8C8D' }; if (hiz >= 8.5) return { title:'FIRTINA', sub:'Speed Demon', color:'#3498DB' }; if (pas >= 8) return { title:'VΔ°ZYONER', sub:'Vision Master', color:'#1ABC9C' }; if (avg >= 7) return { title:'KΔ°LΔ°T', sub:'Key Player', color:'#F39C12' }; if (avg >= 5.5) return { title:'KAPTAN', sub:'Solid Performer', color:'#BDC3C7' }; return { title:'TOPAR', sub:'Room to Grow', color:'#555' }; } // ─── SPIDER CHART ──────────────────────────────────────────────────── function SpiderChart({ scores, size = 200, animated = false, accentColor = '#39FF14' }) { const cx = size/2, cy = size/2, r = size*0.36, n = ATTRS.length; const polar = (val, i, rad) => { const a = (Math.PI*2*i/n) - Math.PI/2; const d = (val/10)*rad; return { x: cx + d*Math.cos(a), y: cy + d*Math.sin(a) }; }; const polyPts = (vals, rad) => ATTRS.map((a,i) => { const v = vals[a.key] ?? 5; const p = polar(v, i, rad); return `${p.x},${p.y}`; }).join(' '); return ( {[2,4,6,8,10].map(lvl => ( [a.key,lvl])), r)} fill="none" stroke={lvl===10?'#333':'#1e1e1e'} strokeWidth={lvl===10?1:0.5} /> ))} {ATTRS.map((a,i) => { const p=polar(10,i,r); return ; })} {ATTRS.map((a,i) => { const p = polar(scores[a.key]??5, i, r); return ; })} {ATTRS.map((a,i) => { const p = polar(12, i, r); const dx = p.x-cx; return 0?'start':'end'} dominantBaseline="middle" fontSize="9" fontFamily="'Space Grotesk',sans-serif" fontWeight="600" fill="#666" letterSpacing="0.5"> {a.label.toUpperCase()} ; })} ); } // ─── MERGE REVEAL ──────────────────────────────────────────────────── function RevealAnimation({ player, votes, onDone }) { const [phase, setPhase] = React.useState('orbs'); React.useEffect(() => { const t1 = setTimeout(() => setPhase('flash'), 2000); const t2 = setTimeout(() => setPhase('card'), 2700); return () => { clearTimeout(t1); clearTimeout(t2); }; }, []); const avg = ATTRS.reduce((acc,a) => ({ ...acc, [a.key]: votes.reduce((s,v)=>s+(v[a.key]||0),0)/votes.length }), {}); const titleData = getTitle(avg); const overall = calcOverall(avg); if (phase === 'orbs') { return (
{votes.map((v,i) => { const ang = (Math.PI*2*i/votes.length) - Math.PI/2; const rad = 90 + (i%3)*20; const ox = Math.cos(ang)*rad, oy = Math.sin(ang)*rad; return (
{ if(el){ el.style.setProperty('--ox',`${ox}px`); el.style.setProperty('--oy',`${oy}px`); }}} > {calcOverall(v).toFixed(1)}
); })} BİRLEŞİYOR
); } if (phase === 'flash') { return
HESAPLANIYOR
; } // Card return (
OYUNCU KARTI · HALIŞAHA HUB
{player.name}
{(player.positions||[player.position]).filter(Boolean).join(' Β· ')}
=8?'var(--green)':overall>=6?'#f0c040':'#ff6666',lineHeight:1}}>{overall}
{votes.length} OY
UNVAN
{titleData.title}
{titleData.sub}
{ATTRS.map(a => (
{a.label.toUpperCase()}
=8?titleData.color:'var(--text)'}}>{avg[a.key].toFixed(1)}
))}
{new Date().toLocaleDateString('tr-TR')} Β· {votes.length} KATILIMCI
); } // ─── VOTER SCREEN ──────────────────────────────────────────────────── function VoterScreen({ player, onSubmit, votingOpen }) { const [scores, setScores] = React.useState({...DEFAULT_SCORES}); const [submitted, setSubmitted] = React.useState(false); const [submitting, setSubmitting] = React.useState(false); const handleSubmit = async () => { if (submitting||submitted) return; setSubmitting(true); await new Promise(r=>setTimeout(r,700)); onSubmit(scores); setSubmitted(true); setSubmitting(false); }; if (!votingOpen) return (
OYLAMA KAPALI
Bu link geΓ§ersiz veya sona erdi.
); if (submitted) return (
βœ…
OY KAYDEDΔ°LDΔ°
Admin sonuçları açıkladığında
oyuncu kartı oluşturulacak.
BEKLE...
); const avg = Object.values(scores).reduce((a,b)=>a+b,0)/6; return (
⚽
OYUNCU DEĞERLENDİRMESİ
{player.name}
{(player.positions||[player.position]).filter(Boolean).join(' Β· ')}
ORT: {avg.toFixed(1)}
{ATTRS.map(attr => { const val = scores[attr.key]; const pct = ((val-1)/9)*100; return (
{attr.icon} {attr.label.toUpperCase()} {val}
setScores(s=>({...s,[attr.key]:parseInt(e.target.value)}))} style={{width:'100%',marginTop:-4,opacity:0,cursor:'pointer',height:20}}/>
); })}
); } Object.assign(window, { ATTRS, POSITIONS, POS_SHORT, DEFAULT_SCORES, calcOverall, getTitle, SpiderChart, RevealAnimation, VoterScreen });