// Shared UI Components — pixel-perfect from design // Exports: StarField, MiniPlayer, AuthModal, GlowButton, Chip, SectionHeader, // FuturisticCard, EventCard, MixCard, TicketCard, VideoModal, Spinner var { useState, useEffect, useRef, useCallback } = React; /* ─── Star Field ─────────────────────────────────────────────────── */ function StarField() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); let W = canvas.width = window.innerWidth; let H = canvas.height = window.innerHeight; const rng = (a, b) => Math.random() * (b - a) + a; const ri = (a, b) => (rng(a, b)) | 0; const starList = []; let attempts = 0; while (starList.length < 340 && attempts < 2000) { attempts++; const i = starList.length; const tier = i < 200 ? 0 : i < 290 ? 1 : i < 330 ? 2 : 3; const x = rng(0, W), y = rng(0, H); const dx = x - W * 0.87, dy = y - H * 0.17; if (Math.sqrt(dx * dx + dy * dy) < W * 0.09) continue; const r = [rng(0.15, 0.7), rng(0.7, 1.4), rng(1.4, 2.4), rng(2.4, 3.8)][tier]; const cr = Math.random(); const color = cr > 0.88 ? `rgba(${ri(160,240)},${ri(200,255)},255,` : cr > 0.76 ? `rgba(200,${ri(225,255)},${ri(235,255)},` : cr > 0.6 ? `rgba(255,${ri(230,255)},${ri(190,230)},` : `rgba(255,255,255,`; starList.push({ x, y, r, color, alpha: rng(0.1, 0.65), maxAlpha: rng(0.45, tier===3?0.9:0.75), minAlpha: rng(0.02, 0.1), twinkleSpeed: rng(0.003, 0.009), twinkleDir: Math.random()>0.5?1:-1, hasFlare: tier>=2&&Math.random()>0.35 }); } const nebulae = [ { x:W*0.08,y:H*0.18,rx:500,ry:260,color:'20,70,200',alpha:0.07 }, { x:W*0.12,y:H*0.30,rx:320,ry:160,color:'10,120,220',alpha:0.05 }, { x:W*0.80,y:H*0.50,rx:380,ry:280,color:'0,170,210',alpha:0.06 }, { x:W*0.72,y:H*0.62,rx:200,ry:140,color:'0,200,180',alpha:0.04 }, { x:W*0.48,y:H*0.06,rx:450,ry:130,color:'80,30,180',alpha:0.045 }, { x:W*0.35,y:H*0.72,rx:280,ry:160,color:'30,60,160',alpha:0.035 }, ]; const saturn = { x:W*0.87,y:H*0.17,r:36 }; const asteroids = Array.from({length:28},()=>({x:rng(W*0.55,W*0.98),y:rng(H*0.30,H*0.55),r:rng(0.6,1.8),alpha:rng(0.15,0.5)})); const galaxy = { x:W*0.22,y:H*0.68,angle:0 }; let shooters = [], shooterTimer = 0; function spawnShooter() { const fast = Math.random()>0.6; shooters.push({x:rng(0,W*0.75),y:rng(0,H*0.45),vx:rng(fast?5:2.5,fast?9:5),vy:rng(fast?2:1,fast?4:2.5),tailLen:rng(60,fast?180:110),life:1,width:rng(0.8,fast?2.2:1.4)}); } function drawFlare(x,y,r,a) { const len=r*5,alpha=a*0.35; [[len,0],[0,len],[len*0.5,len*0.5]].forEach(([dx,dy])=>{ const g=ctx.createLinearGradient(x-dx,y-dy,x+dx,y+dy); g.addColorStop(0,`rgba(200,230,255,0)`);g.addColorStop(0.5,`rgba(220,240,255,${alpha})`);g.addColorStop(1,`rgba(200,230,255,0)`); ctx.beginPath();ctx.moveTo(x-dx,y-dy);ctx.lineTo(x+dx,y+dy);ctx.strokeStyle=g;ctx.lineWidth=0.8;ctx.stroke(); }); } function drawNebula(n) { const big=Math.max(n.rx,n.ry),g=ctx.createRadialGradient(0,0,0,0,0,big); g.addColorStop(0,`rgba(${n.color},${n.alpha})`);g.addColorStop(0.5,`rgba(${n.color},${n.alpha*0.4})`);g.addColorStop(1,`rgba(${n.color},0)`); ctx.save();ctx.translate(n.x,n.y);ctx.scale(n.rx/big,n.ry/big);ctx.beginPath();ctx.arc(0,0,big,0,Math.PI*2);ctx.fillStyle=g;ctx.fill();ctx.restore(); } function drawGalaxy(gx,gy,angle) { ctx.save();ctx.translate(gx,gy);ctx.rotate(angle); const core=ctx.createRadialGradient(0,0,0,0,0,22);core.addColorStop(0,'rgba(180,210,255,0.18)');core.addColorStop(0.4,'rgba(100,160,255,0.07)');core.addColorStop(1,'rgba(60,100,200,0)'); ctx.beginPath();ctx.arc(0,0,22,0,Math.PI*2);ctx.fillStyle=core;ctx.fill(); for(let arm=0;arm<2;arm++){ctx.save();ctx.rotate(arm*Math.PI);for(let s=0;s<60;s++){const t2=s/60,a2=t2*Math.PI*1.6,d=t2*55,px=Math.cos(a2)*d,py=Math.sin(a2)*d*0.45,sr=rng(0.3,1.0),sa=(1-t2)*0.22;ctx.beginPath();ctx.arc(px,py,sr,0,Math.PI*2);ctx.fillStyle=`rgba(${ri(140,210)},${ri(180,230)},255,${sa})`;ctx.fill();}ctx.restore();} ctx.restore(); } function drawSaturn(p) { ctx.save();ctx.translate(p.x,p.y);ctx.scale(1,0.28); for(let i=0;i<4;i++){const rr=p.r*(1.55+i*0.18),rw=p.r*0.1,rc=['rgba(120,180,240,0.22)','rgba(90,150,210,0.16)','rgba(140,200,255,0.12)','rgba(80,130,190,0.09)'][i];ctx.beginPath();ctx.arc(0,0,rr,Math.PI,Math.PI*2);ctx.strokeStyle=rc;ctx.lineWidth=rw*2.5/0.28;ctx.stroke();} ctx.restore(); const g=ctx.createRadialGradient(p.x-p.r*0.35,p.y-p.r*0.35,p.r*0.05,p.x,p.y,p.r); g.addColorStop(0,'#2a6090');g.addColorStop(0.25,'#1a4a72');g.addColorStop(0.55,'#0d2e52');g.addColorStop(1,'#050e1e'); ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle=g;ctx.fill(); ctx.save();ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.clip(); [[-0.25,0.06,'rgba(30,90,150,0.25)'],[0.1,0.05,'rgba(20,70,120,0.2)'],[0.35,0.04,'rgba(40,100,160,0.15)']].forEach(([oy,ry2,c])=>{ctx.beginPath();ctx.ellipse(p.x,p.y+oy*p.r,p.r,p.r*ry2,0,0,Math.PI*2);ctx.fillStyle=c;ctx.fill();}); ctx.restore(); const atm=ctx.createRadialGradient(p.x,p.y,p.r*0.85,p.x,p.y,p.r*1.6);atm.addColorStop(0,'rgba(60,150,230,0.18)');atm.addColorStop(0.5,'rgba(30,100,200,0.07)');atm.addColorStop(1,'rgba(20,60,180,0)'); ctx.beginPath();ctx.arc(p.x,p.y,p.r*1.6,0,Math.PI*2);ctx.fillStyle=atm;ctx.fill(); ctx.save();ctx.translate(p.x,p.y);ctx.scale(1,0.28); for(let i=0;i<4;i++){const rr=p.r*(1.55+i*0.18),rw=p.r*0.1,rc=['rgba(120,180,240,0.22)','rgba(90,150,210,0.16)','rgba(140,200,255,0.12)','rgba(80,130,190,0.09)'][i];ctx.beginPath();ctx.arc(0,0,rr,0,Math.PI);ctx.strokeStyle=rc;ctx.lineWidth=rw*2.5/0.28;ctx.stroke();} ctx.restore(); } let raf; function draw() { ctx.clearRect(0,0,W,H); galaxy.angle+=0.0003; nebulae.forEach(drawNebula); drawGalaxy(galaxy.x,galaxy.y,galaxy.angle); asteroids.forEach(a=>{ctx.beginPath();ctx.arc(a.x,a.y,a.r,0,Math.PI*2);ctx.fillStyle=`rgba(140,170,200,${a.alpha})`;ctx.fill();}); drawSaturn(saturn); starList.forEach(s=>{ s.alpha+=s.twinkleSpeed*s.twinkleDir; if(s.alpha>=s.maxAlpha||s.alpha<=s.minAlpha)s.twinkleDir*=-1; if(s.r>1.5){const g=ctx.createRadialGradient(s.x,s.y,0,s.x,s.y,s.r*3.5);g.addColorStop(0,s.color+s.alpha*0.9+')');g.addColorStop(1,s.color+'0)');ctx.beginPath();ctx.arc(s.x,s.y,s.r*3.5,0,Math.PI*2);ctx.fillStyle=g;ctx.fill();} ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=s.color+s.alpha+')';ctx.fill(); if(s.hasFlare&&s.r>1.8)drawFlare(s.x,s.y,s.r,s.alpha); }); shooterTimer++; if(shooterTimer>180+(Math.random()*280|0)){spawnShooter();shooterTimer=0;} shooters=shooters.filter(s=>s.life>0); shooters.forEach(s=>{ s.life-=0.025;s.x+=s.vx;s.y+=s.vy; const tail=s.tailLen*s.life,hyp=Math.hypot(s.vx,s.vy); const sg=ctx.createLinearGradient(s.x,s.y,s.x-(s.vx/hyp)*tail,s.y-(s.vy/hyp)*tail); sg.addColorStop(0,`rgba(255,255,255,${s.life*0.95})`);sg.addColorStop(0.15,`rgba(180,225,255,${s.life*0.7})`);sg.addColorStop(0.5,`rgba(100,180,255,${s.life*0.25})`);sg.addColorStop(1,'rgba(60,140,255,0)'); const nx=-s.vy/hyp,ny=s.vx/hyp,ex=s.x-(s.vx/hyp)*tail,ey=s.y-(s.vy/hyp)*tail; ctx.beginPath();ctx.moveTo(s.x+nx*s.width,s.y+ny*s.width);ctx.lineTo(s.x-nx*s.width,s.y-ny*s.width);ctx.lineTo(ex,ey);ctx.closePath();ctx.fillStyle=sg;ctx.fill(); const hg=ctx.createRadialGradient(s.x,s.y,0,s.x,s.y,s.width*4);hg.addColorStop(0,`rgba(255,255,255,${s.life*0.9})`);hg.addColorStop(1,'rgba(150,210,255,0)'); ctx.beginPath();ctx.arc(s.x,s.y,s.width*4,0,Math.PI*2);ctx.fillStyle=hg;ctx.fill(); }); raf=requestAnimationFrame(draw); } draw(); const onResize=()=>{W=canvas.width=window.innerWidth;H=canvas.height=window.innerHeight;saturn.x=W*0.87;saturn.y=H*0.17;galaxy.x=W*0.22;galaxy.y=H*0.68;nebulae[0].x=W*0.08;nebulae[1].x=W*0.12;nebulae[2].x=W*0.80;nebulae[3].x=W*0.72;nebulae[4].x=W*0.48;nebulae[5].x=W*0.35;}; window.addEventListener('resize',onResize); return()=>{cancelAnimationFrame(raf);window.removeEventListener('resize',onResize);}; },[]); return React.createElement('canvas',{ref:canvasRef,style:{position:'fixed',inset:0,zIndex:0,pointerEvents:'none'}}); } /* ─── Futuristic Card ────────────────────────────────────────────── */ function FuturisticCard({ children, active=false, onClick, style={} }) { const [hovered, setHovered] = useState(false); const hot = hovered || active; const bc = active ? 'rgba(50,114,168,0.9)' : hot ? 'rgba(50,114,168,0.6)' : 'rgba(50,114,168,0.25)'; const brkt = active ? '#3272a8' : hot ? '#5a92c0' : 'rgba(50,114,168,0.5)'; return React.createElement('div', { onMouseEnter:()=>setHovered(true), onMouseLeave:()=>setHovered(false), onClick, style:{ position:'relative', cursor:onClick?'pointer':'default', transition:'transform 0.2s,box-shadow 0.2s', transform:hot&&onClick?'translateY(-2px)':'none', boxShadow:active?'0 0 24px rgba(50,114,168,0.35),inset 0 0 30px rgba(50,114,168,0.06)':hot?'0 0 16px rgba(50,114,168,0.2)':'none', ...style }}, React.createElement('div', { style:{ position:'relative', background:hot?'rgba(5,18,44,0.93)':'rgba(3,11,28,0.88)', border:'1px solid '+bc, backdropFilter:'blur(14px)', padding:'22px 24px', transition:'background 0.2s,border-color 0.2s' }}, React.createElement('div', { style:{ position:'absolute',inset:0,zIndex:0,pointerEvents:'none', backgroundImage:'repeating-linear-gradient(0deg,transparent,transparent 3px,rgba(50,114,168,0.018) 3px,rgba(50,114,168,0.018) 4px)', opacity:hot?1:0.5,transition:'opacity 0.2s' }}), React.createElement('div', { style:{ position:'absolute',top:0,left:0,right:0,height:1,zIndex:2, background:active?'linear-gradient(90deg,#3272a8,rgba(50,114,168,0.3),transparent)':hot?'linear-gradient(90deg,rgba(50,114,168,0.8),rgba(50,114,168,0.2),transparent)':'linear-gradient(90deg,rgba(50,114,168,0.4),transparent)', transition:'background 0.2s' }}), React.createElement('div', { style:{ position:'absolute',bottom:0,left:0,right:0,height:1,zIndex:2, background:'linear-gradient(90deg,transparent,rgba(50,114,168,0.15),transparent)' }}), React.createElement('div', { style:{ position:'absolute',top:6,left:6,zIndex:3,width:12,height:12, borderTop:'1.5px solid '+brkt,borderLeft:'1.5px solid '+brkt, transition:'border-color 0.2s' }}), React.createElement('div', { style:{ position:'absolute',top:6,right:6,zIndex:3,width:12,height:12, borderTop:'1.5px solid '+brkt,borderRight:'1.5px solid '+brkt, transition:'border-color 0.2s' }}), React.createElement('div', { style:{ position:'absolute',bottom:6,left:6,zIndex:3,width:12,height:12, borderBottom:'1.5px solid '+brkt,borderLeft:'1.5px solid '+brkt, transition:'border-color 0.2s' }}), React.createElement('div', { style:{ position:'absolute',bottom:6,right:6,zIndex:3,width:12,height:12, borderBottom:'1.5px solid '+brkt,borderRight:'1.5px solid '+brkt, transition:'border-color 0.2s' }}), React.createElement('div', { style:{ position:'relative',zIndex:1 }}, children) ) ); } /* ─── Glow Button ────────────────────────────────────────────────── */ function GlowButton({ children, onClick, variant='primary', small=false, style={}, disabled=false, type='button' }) { const base = { display:'inline-flex',alignItems:'center',justifyContent:'center',gap:8, padding:small?'8px 18px':'12px 28px', fontFamily:"'Space Grotesk',sans-serif", fontWeight:700,letterSpacing:'0.08em', fontSize:small?12:14, textTransform:'uppercase', border:'none',borderRadius:0,cursor:disabled?'not-allowed':'pointer', transition:'all 0.2s', opacity:disabled?0.5:1, ...style }; if (variant==='primary') return React.createElement('button',{type,onClick,disabled,style:{...base,background:'linear-gradient(135deg,#3272a8,#9f84b2,#3272a8)',backgroundSize:'200% 200%',animation:'btnGlow 3s ease infinite',color:'#fff',boxShadow:'0 0 20px rgba(50,114,168,0.5),0 0 40px rgba(159,132,178,0.2)'}},children); if (variant==='ghost') return React.createElement('button',{type,onClick,disabled,style:{...base,background:'transparent',color:'#fff',border:'1px solid rgba(255,255,255,0.2)'}},children); if (variant==='pink') return React.createElement('button',{type,onClick,disabled,style:{...base,background:'linear-gradient(135deg,#9f84b2,#3272a8)',backgroundSize:'200% 200%',animation:'btnGlow 3s ease infinite reverse',color:'#fff',boxShadow:'0 0 18px rgba(159,132,178,0.5)'}},children); if (variant==='danger') return React.createElement('button',{type,onClick,disabled,style:{...base,background:'rgba(239,68,68,0.15)',color:'#ef4444',border:'1px solid rgba(239,68,68,0.3)'}},children); } /* ─── Chip ───────────────────────────────────────────────────────── */ function Chip({ label, color='#3272a8' }) { return React.createElement('span',{style:{display:'inline-block',padding:'3px 10px',borderRadius:20,fontSize:11,fontWeight:700,letterSpacing:'0.1em',textTransform:'uppercase',background:color+'22',color,border:`1px solid ${color}55`,fontFamily:"'Space Grotesk',sans-serif"}},label); } /* ─── Section Header ─────────────────────────────────────────────── */ function SectionHeader({ title, subtitle }) { return React.createElement('div',{style:{marginBottom:40}}, React.createElement('h2',{style:{fontFamily:"'Ethnocentric Rg','Orbitron','Space Grotesk',sans-serif",fontWeight:200,fontStretch:'expanded',fontSize:'clamp(24px,4vw,44px)',letterSpacing:'0.06em',color:'#fff',margin:0,textTransform:'uppercase'}},title), subtitle&&React.createElement('p',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.5)',fontSize:16,marginTop:8}},subtitle) ); } /* ─── Spinner ────────────────────────────────────────────────────── */ function Spinner() { return React.createElement('div',{style:{display:'flex',justifyContent:'center',padding:'60px 0'}}, React.createElement('div',{style:{width:32,height:32,border:'2px solid rgba(50,114,168,0.3)',borderTop:'2px solid #3272a8',borderRadius:'50%',animation:'spin 0.8s linear infinite'}}) ); } /* ─── Event Card ─────────────────────────────────────────────────── */ function EventCard({ event, onSelect, onRegister }) { return React.createElement(FuturisticCard,{onClick:()=>onSelect(event)}, event.posterImage&&React.createElement('img',{src:event.posterImage,alt:event.title,style:{width:'100%',borderRadius:4,objectFit:'cover',maxHeight:180,marginBottom:14}}), React.createElement('div',{style:{display:'flex',gap:8,flexWrap:'wrap',marginBottom:10}}, React.createElement(Chip,{label:event.status==='upcoming'?'Upcoming':'Past',color:event.status==='upcoming'?'#3272a8':'#64748b'}), event.isFree&&React.createElement(Chip,{label:'FREE',color:'#10b981'}), event.featured&&React.createElement(Chip,{label:'Featured',color:'#f59e0b'}) ), React.createElement('h3',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:800,fontSize:20,color:'#fff',margin:'0 0 8px',letterSpacing:'-0.01em',textTransform:'uppercase'}},event.title), React.createElement('div',{style:{display:'flex',flexDirection:'column',gap:3,marginBottom:10}}, React.createElement('span',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'#6ba3d4',fontSize:13,fontWeight:600}},event.displayDate+' · '+event.startTime), React.createElement('span',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.45)',fontSize:12}},event.venue+', '+event.location) ), React.createElement('p',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.55)',fontSize:13,margin:'0 0 14px',lineHeight:1.6}}, (event.description||'').substr(0,110)+(event.description&&event.description.length>110?'…':'')), React.createElement('div',{style:{display:'flex',gap:10}}, React.createElement(GlowButton,{onClick:e=>{e.stopPropagation();onSelect(event);},small:true,variant:'ghost'},'View Event'), event.status==='upcoming'&&React.createElement(GlowButton,{onClick:e=>{e.stopPropagation();onRegister(event);},small:true,variant:'primary'}, event.registrationMode==='interest'?'Register Interest':event.isFree?'Get Free Ticket':'Buy Ticket') ) ); } /* ─── Mix Card ───────────────────────────────────────────────────── */ function MixCard({ mix, onPlay, isPlaying }) { return React.createElement(FuturisticCard,{active:isPlaying,onClick:()=>onPlay(mix)}, mix.coverImage&&React.createElement('img',{src:mix.coverImage,alt:mix.title,style:{width:'100%',height:140,objectFit:'cover',marginBottom:14}}), React.createElement('div',{style:{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:14}}, React.createElement('div',{style:{width:52,height:52,flexShrink:0,clipPath:'polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px))',background:isPlaying?'#3272a8':'rgba(50,114,168,0.15)',display:'flex',alignItems:'center',justifyContent:'center',fontSize:20,boxShadow:isPlaying?'0 0 20px rgba(6,182,212,0.6)':'none',color:'#fff',transition:'all 0.2s'}},isPlaying?'⏸':'▶'), React.createElement('div',{style:{textAlign:'right'}}, React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",fontSize:11,fontWeight:700,letterSpacing:'0.12em',color:'rgba(255,255,255,0.35)',textTransform:'uppercase'}},mix.duration), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",fontSize:11,color:'rgba(255,255,255,0.25)',marginTop:2}},mix.month) ) ), React.createElement('h3',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:800,fontSize:17,color:'#fff',margin:'0 0 3px',textTransform:'uppercase',letterSpacing:'-0.01em'}},mix.title), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'#6ba3d4',fontSize:12,fontWeight:600,marginBottom:10,letterSpacing:'0.06em'}},mix.residentName), React.createElement('p',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.5)',fontSize:13,margin:'0 0 14px',lineHeight:1.6}},mix.description), React.createElement('div',{style:{display:'flex',gap:8,flexWrap:'wrap'}}, React.createElement(GlowButton,{small:true,variant:isPlaying?'pink':'primary',onClick:e=>{e.stopPropagation();onPlay(mix);}}, isPlaying?'Now Playing':'Play Mix'), mix.hasVideo&&!isPlaying&&React.createElement(GlowButton,{small:true,variant:'ghost',onClick:e=>{e.stopPropagation();onPlay(mix);}}, '▶ Watch') ) ); } /* ─── Ticket Card ────────────────────────────────────────────────── */ function TicketCard({ ticket }) { const qrRef = React.useRef(null); const event = ticket.event || {}; React.useEffect(() => { if (!qrRef.current || !ticket.qrCode) return; qrRef.current.innerHTML = ''; if (typeof QRCode !== 'undefined') { new QRCode(qrRef.current, { text: ticket.qrCode, width: 80, height: 80, colorDark: '#000000', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.H, }); } else { qrRef.current.textContent = ticket.qrCode; } }, [ticket.qrCode]); return React.createElement(FuturisticCard,{active:ticket.status==='valid'}, React.createElement('div',{style:{display:'flex',justifyContent:'space-between',alignItems:'flex-start',marginBottom:20}}, React.createElement('div',null, React.createElement(Chip,{label:ticket.status==='valid'?'Valid':ticket.status==='used'?'Used':'Cancelled',color:ticket.status==='valid'?'#10b981':'#64748b'}), React.createElement('h3',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:800,fontSize:20,color:'#fff',margin:'8px 0 4px',textTransform:'uppercase'}},event.title||'Event Ticket'), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'#3272a8',fontSize:13,fontWeight:600}},ticket.ticketTypeName||'General Entry') ), React.createElement('div',{ref:qrRef,style:{width:80,height:80,background:'#fff',borderRadius:4,flexShrink:0,display:'flex',alignItems:'center',justifyContent:'center',fontSize:8,fontFamily:'monospace',color:'#000',padding:2}}) ), React.createElement('div',{style:{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}, [['HOLDER',ticket.holderName],['REF',ticket.id],['DATE',event.displayDate||'—'],['VENUE',event.venue||'—']].map(([label,val])=> React.createElement('div',{key:label}, React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.4)',fontSize:10,letterSpacing:'0.1em',textTransform:'uppercase',marginBottom:2}},label), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'#fff',fontSize:13,fontWeight:600}},val) ) ) ) ); } /* ─── Auth Modal ─────────────────────────────────────────────────── */ function AuthModal({ mode, onClose, onSuccess }) { const [tab, setTab] = useState(mode||'login'); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [instagram, setInstagram] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); useEffect(() => { window.scrollTo(0, 0); }, []); const submit = async (e) => { e.preventDefault(); setError(''); setLoading(true); try { let res; if (tab === 'login') { res = await window.RNTECH_API.login(email, password); } else { if (!firstName.trim()) { setError('Please enter your first name.'); setLoading(false); return; } if (!lastName.trim()) { setError('Please enter your last name.'); setLoading(false); return; } res = await window.RNTECH_API.register(firstName, lastName, email, password, instagram); } window.RNTECH_AUTH.setToken(res.token); window.RNTECH_AUTH.setUser(res.user); onSuccess(res.user); } catch (err) { setError(err.message || 'Something went wrong'); } finally { setLoading(false); } }; const inputStyle = { width:'100%',padding:'12px 16px',background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.15)',borderRadius:0,color:'#fff',fontSize:15,fontFamily:"'Space Grotesk',sans-serif",outline:'none',boxSizing:'border-box' }; return React.createElement('div',{className:'modal-overlay',style:{position:'fixed',inset:0,zIndex:1000,background:'rgba(0,0,0,0.8)',backdropFilter:'blur(12px)',display:'flex',alignItems:'center',justifyContent:'center',padding:20},onClick:onClose}, React.createElement('div',{className:'modal-sheet',onClick:e=>e.stopPropagation(),style:{background:'#040c18',border:'1px solid rgba(50,114,168,0.3)',borderRadius:0,padding:40,width:'100%',maxWidth:440,boxShadow:'0 0 60px rgba(50,114,168,0.2)'}}, React.createElement('div',{className:'modal-handle'}), React.createElement('h2',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:800,fontSize:28,color:'#fff',margin:'0 0 8px',textTransform:'uppercase',letterSpacing:'-0.02em'}},tab==='login'?'Sign In':'Create Account'), React.createElement('p',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.4)',fontSize:14,marginBottom:28}},tab==='login'?'Welcome back to RNTECH.':'Join RNTECH. Get tickets and mixes all in one place.'), React.createElement('form',{onSubmit:submit,style:{display:'flex',flexDirection:'column',gap:14}}, tab==='register'&&React.createElement('div',{className:'name-row',style:{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10}}, React.createElement('input',{placeholder:'First Name',value:firstName,onChange:e=>setFirstName(e.target.value),required:true,style:inputStyle}), React.createElement('input',{placeholder:'Last Name',value:lastName,onChange:e=>setLastName(e.target.value),required:true,style:inputStyle}) ), React.createElement('input',{type:'email',placeholder:'Email Address',value:email,onChange:e=>setEmail(e.target.value),required:true,style:inputStyle}), React.createElement('input',{type:'password',placeholder:'Password',value:password,onChange:e=>setPassword(e.target.value),required:true,minLength:6,style:inputStyle}), tab==='register'&&React.createElement('input',{placeholder:'Instagram handle (optional)',value:instagram,onChange:e=>setInstagram(e.target.value),style:{...inputStyle,opacity:0.75}}), error&&React.createElement('div',{style:{background:'rgba(239,68,68,0.1)',border:'1px solid rgba(239,68,68,0.3)',borderRadius:4,padding:'10px 14px',fontFamily:"'Space Grotesk',sans-serif",color:'#ef4444',fontSize:13}},error), React.createElement('button',{type:'submit',disabled:loading,style:{padding:14,borderRadius:0,border:'none',cursor:loading?'wait':'pointer',background:'linear-gradient(135deg,#3272a8 0%,#06b6d4 100%)',color:'#fff',fontFamily:"'Space Grotesk',sans-serif",fontWeight:700,fontSize:14,letterSpacing:'0.08em',textTransform:'uppercase',boxShadow:'0 0 20px rgba(50,114,168,0.4)',opacity:loading?0.7:1}},loading?'Please wait…':tab==='login'?'Sign In':'Create Account'), React.createElement('button',{type:'button',onClick:()=>{setTab(tab==='login'?'register':'login');setError('');},style:{background:'none',border:'none',cursor:'pointer',fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.5)',fontSize:13}},tab==='login'?'No account? Create one →':'← Already have an account? Sign in') ) ) ); } /* ─── Video Modal ────────────────────────────────────────────────── */ function VideoModal({ mix, videoRef, onClose }) { if (!mix || !mix.videoSrc) return null; return React.createElement('div',{ style:{position:'fixed',inset:0,zIndex:500,background:'rgba(0,0,0,0.92)',backdropFilter:'blur(16px)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:20}, onClick:onClose }, React.createElement('div',{onClick:e=>e.stopPropagation(),style:{width:'100%',maxWidth:900,position:'relative'}}, React.createElement('div',{style:{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:16}}, React.createElement('div',null, React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:800,fontSize:18,color:'#fff',textTransform:'uppercase'}},mix.title), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'#3272a8',fontSize:13}},mix.residentName) ), React.createElement('button',{onClick:onClose,style:{background:'none',border:'1px solid rgba(255,255,255,0.2)',color:'#fff',cursor:'pointer',padding:'8px 16px',fontFamily:"'Space Grotesk',sans-serif",fontSize:12,fontWeight:700,letterSpacing:'0.08em',textTransform:'uppercase'}},'✕ CLOSE') ), // Move the persistent video element here when modal is open React.createElement('div',{id:'video-modal-slot',style:{width:'100%',background:'#000',aspectRatio:'16/9',position:'relative'}}, React.createElement('p',{style:{color:'rgba(255,255,255,0.3)',fontFamily:"'Space Grotesk',sans-serif",fontSize:13,textAlign:'center',paddingTop:40}}, 'Video loading…') ), mix.tracklist&&mix.tracklist.length>0&&React.createElement('div',{style:{marginTop:16}}, React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.3)',fontSize:11,letterSpacing:'0.12em',textTransform:'uppercase',marginBottom:8}},'Tracklist'), mix.tracklist.map((t,i)=>React.createElement('div',{key:i,style:{fontFamily:"'Space Grotesk',sans-serif",color:'rgba(255,255,255,0.6)',fontSize:13,padding:'4px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}},t)) ) ) ); } /* ─── Mini Player ────────────────────────────────────────────────── */ function MiniPlayer({ mix, videoRef, onClose, onWatch }) { const [playing, setPlaying] = useState(true); const [progress, setProgress] = useState(0); const [duration, setDuration] = useState(0); const [bars] = useState(()=>Array.from({length:20},()=>Math.random()*0.6+0.2)); // Sync with actual video element useEffect(() => { const vid = videoRef.current; if (!vid) return; const onTime = () => { if (vid.duration) setProgress((vid.currentTime / vid.duration) * 100); }; const onDur = () => setDuration(vid.duration); const onPlay = () => setPlaying(true); const onPause = () => setPlaying(false); vid.addEventListener('timeupdate', onTime); vid.addEventListener('durationchange', onDur); vid.addEventListener('play', onPlay); vid.addEventListener('pause', onPause); return () => { vid.removeEventListener('timeupdate', onTime); vid.removeEventListener('durationchange', onDur); vid.removeEventListener('play', onPlay); vid.removeEventListener('pause', onPause); }; }, [videoRef, mix]); const togglePlay = () => { const vid = videoRef.current; if (!vid) return; if (vid.paused) vid.play(); else vid.pause(); }; const seek = (e) => { const vid = videoRef.current; if (!vid || !vid.duration) return; const rect = e.currentTarget.getBoundingClientRect(); const pct = (e.clientX - rect.left) / rect.width; vid.currentTime = pct * vid.duration; }; return React.createElement('div',{style:{position:'fixed',bottom:20,left:'50%',transform:'translateX(-50%)',zIndex:200,width:'min(520px,calc(100vw - 40px))',background:'rgba(10,5,20,0.95)',border:'1px solid rgba(6,182,212,0.4)',borderRadius:6,padding:'14px 20px',backdropFilter:'blur(20px)',boxShadow:'0 0 40px rgba(50,114,168,0.25)',display:'flex',alignItems:'center',gap:14}}, // Album art / icon mix.coverImage ? React.createElement('img',{src:mix.coverImage,alt:'',style:{width:44,height:44,borderRadius:4,objectFit:'cover',flexShrink:0,boxShadow:'0 0 14px rgba(6,182,212,0.4)'}}) : React.createElement('div',{style:{width:44,height:44,borderRadius:4,flexShrink:0,background:'linear-gradient(135deg,#06b6d4,#3272a8)',display:'flex',alignItems:'center',justifyContent:'center',fontSize:18,boxShadow:'0 0 14px rgba(6,182,212,0.4)'}},'♪'), // Info + progress React.createElement('div',{style:{flex:1,minWidth:0}}, React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",fontWeight:700,fontSize:13,color:'#fff',textTransform:'uppercase',letterSpacing:'0.04em',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}},mix.title), React.createElement('div',{style:{fontFamily:"'Space Grotesk',sans-serif",fontSize:11,color:'#3272a8',marginBottom:6}},mix.residentName), // Animated bars React.createElement('div',{style:{display:'flex',alignItems:'flex-end',gap:2,height:20,marginBottom:4}}, bars.map((h,i)=>React.createElement('div',{key:i,style:{width:3,borderRadius:2,flexShrink:0,background:'#3272a8',height:playing?`${h*20}px`:'4px',opacity:playing?0.4+h*0.6:0.2,transition:'height 0.3s ease'}})) ), // Scrubber React.createElement('div',{onClick:seek,style:{height:3,background:'rgba(255,255,255,0.1)',borderRadius:2,overflow:'hidden',cursor:'pointer'}}, React.createElement('div',{style:{height:'100%',width:progress+'%',background:'#3272a8',transition:'width 0.1s linear'}}) ) ), // Watch button (if has video) mix.hasVideo&&React.createElement('button',{onClick:onWatch,style:{background:'none',border:'1px solid rgba(50,114,168,0.4)',color:'#3272a8',cursor:'pointer',padding:'6px 10px',fontSize:11,fontFamily:"'Space Grotesk',sans-serif",fontWeight:700,letterSpacing:'0.06em',textTransform:'uppercase',whiteSpace:'nowrap',flexShrink:0}},'▶ WATCH'), // Play/pause React.createElement('button',{onClick:togglePlay,style:{width:36,height:36,borderRadius:'50%',background:'rgba(255,255,255,0.1)',border:'1px solid rgba(255,255,255,0.15)',color:'#fff',cursor:'pointer',fontSize:14,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}},playing?'⏸':'▶'), // Close React.createElement('button',{onClick:onClose,style:{width:28,height:28,borderRadius:'50%',background:'rgba(255,255,255,0.05)',border:'1px solid rgba(255,255,255,0.1)',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:14,display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}},'×') ); } Object.assign(window, { StarField, GlowButton, Chip, SectionHeader, FuturisticCard, Spinner, EventCard, MixCard, TicketCard, AuthModal, MiniPlayer, VideoModal, });