// title-scenes.jsx — CONCERTIX punk title sequence (KERAS-style)
// Loads after animations.jsx (Stage, Sprite, useTime, useSprite, Easing, clamp on window)

const { useTime, useSprite, Easing, clamp, interpolate } = window;

const RED   = '#0E0C0B';   // black reel
const OXRED = '#4A1418';
const INK   = '#0c0a09';
const PAPER = '#F2EDE3';   // cream
const BRASS = '#C2925A';   // gold accent

// ---- seeded pseudo-random ----
function rnd(seed){ const x = Math.sin(seed*99.13)*43758.5453; return x - Math.floor(x); }

// ---- spiky starburst path ----
function burstPath(cx, cy, outer, inner, spikes, seed){
  const pts = [];
  const n = spikes*2;
  for(let i=0;i<n;i++){
    const isOuter = i%2===0;
    const baseR = isOuter ? outer : inner;
    const jitter = 1 + (rnd(seed+i)-0.5)*0.16;
    const r = baseR*jitter;
    const ang = (i/n)*Math.PI*2 - Math.PI/2 + (rnd(seed+i*3)-0.5)*0.05;
    pts.push([cx+Math.cos(ang)*r, cy+Math.sin(ang)*r]);
  }
  return 'M'+pts.map(p=>p[0].toFixed(1)+','+p[1].toFixed(1)).join('L')+'Z';
}

// star polygon as CSS clip-path "polygon(...)" in % coords
function burstPolygon(outerPct, innerPct, spikes, seed){
  const pts=[]; const n=spikes*2;
  for(let i=0;i<n;i++){
    const isOuter=i%2===0;
    const baseR=isOuter?outerPct:innerPct;
    const jitter=1+(rnd(seed+i)-0.5)*0.16;
    const r=baseR*jitter;
    const ang=(i/n)*Math.PI*2 - Math.PI/2 + (rnd(seed+i*3)-0.5)*0.05;
    pts.push((50+Math.cos(ang)*r).toFixed(2)+'% '+(50+Math.sin(ang)*r).toFixed(2)+'%');
  }
  return 'polygon('+pts.join(', ')+')';
}

// =====================================================================
//  StarburstPhoto — B&W photo clipped into a spiky white-rimmed star
// =====================================================================
function StarburstPhoto({ src, size=660, spikes=15, seed=2, spin=0, noFrame=false, tint=null }){
  const white = React.useMemo(()=>burstPolygon(49,34,spikes,seed),[spikes,seed]);
  const inner = React.useMemo(()=>burstPolygon(44,30.6,spikes,seed),[spikes,seed]);
  if (noFrame){
    return (
      <div style={{position:'relative', width:size, height:size, transform:`rotate(${spin}deg)`,
                   filter:'drop-shadow(8px 14px 0 rgba(0,0,0,0.32))'}}>
        <img src={src} alt="" draggable={false}
          style={{width:'100%', height:'100%', objectFit:'contain', display:'block', userSelect:'none'}}/>
      </div>
    );
  }
  return (
    <div style={{position:'relative', width:size, height:size, transform:`rotate(${spin}deg)`,
                 filter:'drop-shadow(6px 12px 0 rgba(0,0,0,0.22))'}}>
      <div style={{position:'absolute', inset:0, background:PAPER, clipPath:white}}/>
      <div style={{position:'absolute', inset:0, clipPath:inner,
        backgroundImage:`url("${src}")`, backgroundSize:'cover', backgroundPosition:'center center', backgroundRepeat:'no-repeat',
        filter:'contrast(1.05) saturate(1.05)'}}/>
      {tint && (
        <>
          <div style={{position:'absolute', inset:0, clipPath:inner,
            background:tint, mixBlendMode:'color', pointerEvents:'none'}}/>
          <div style={{position:'absolute', inset:0, clipPath:inner,
            background:tint, mixBlendMode:'screen', opacity:.22, pointerEvents:'none'}}/>
        </>
      )}
    </div>
  );
}

// =====================================================================
//  GlitchWord — heavy condensed word with RGB-split judder + entry
// =====================================================================
function GlitchWord({ text, size, color=PAPER, localT=0, appearAt=0, stagger=0.05,
                     skew=-4, stretch=1.18, jitterAmp=2.4, seed=1 }){
  const t = useTime();
  const letters = text.split('');
  return (
    <div style={{display:'flex', whiteSpace:'pre', transform:`skewX(${skew}deg) scaleY(${stretch})`}}>
      {letters.map((ch,i)=>{
        const at = appearAt + i*stagger;
        const p = clamp((localT-at)/0.32, 0, 1);
        const e = Easing.easeOutBack(p);
        const opacity = clamp((localT-at)/0.14, 0, 1);
        // continuous micro-judder
        const j = (rnd(seed+i + Math.floor(t*22))-0.5)*jitterAmp;
        const jy = (rnd(seed+i*2 + Math.floor(t*22))-0.5)*jitterAmp;
        // entry: drop from above + overshoot
        const ty = (1-e)*-70 + jy;
        const sx = 0.7+0.3*e;
        // RGB split amount jitters
        const split = 1.5 + Math.abs(j)*1.1;
        return (
          <span key={i} style={{
            display:'inline-block',
            fontFamily:'Anton, sans-serif',
            fontSize:size, lineHeight:0.92, color,
            transform:`translate(${j}px, ${ty}px) scaleX(${sx})`,
            opacity,
            textShadow:`${split}px 0 rgba(255,40,30,0.85), ${-split}px 0 rgba(0,220,255,0.7)`,
            willChange:'transform',
          }}>{ch}</span>
        );
      })}
    </div>
  );
}

// =====================================================================
//  VHS overlay — grain, scanlines, vignette, floating specks, jitter
// =====================================================================
function VHS({ w, h }){
  const t = useTime();
  const specks = React.useMemo(()=>Array.from({length:26},(_,i)=>({
    x:rnd(i*1.7), y:rnd(i*2.3), s:rnd(i*3.1)*3+1, sp:rnd(i*4.7)*0.4+0.1, ph:rnd(i*5.9)*6
  })),[]);
  return (
    <div style={{position:'absolute', inset:0, pointerEvents:'none', zIndex:50, overflow:'hidden'}}>
      {/* specks only — no grain/scanlines/scan-band/vignette so the red field is
          perfectly uniform and the reel blends into the page with no visible seam */}
      {specks.map((s,i)=>{
        const yy = (s.y - (t*s.sp)%1 +1)%1;
        return <div key={i} style={{position:'absolute', left:s.x*w, top:yy*h,
          width:s.s, height:s.s, background:'rgba(255,255,255,0.7)', borderRadius:1,
          opacity:0.4+0.4*Math.sin(t*4+s.ph)}}/>;
      })}
    </div>
  );
}

// flash cut — quick white/black blip at a boundary
function FlashCut({ at, dur=0.16 }){
  const t = useTime();
  const p = (t-at)/dur;
  if(p<0||p>1) return null;
  const op = Math.sin(p*Math.PI);
  return <div style={{position:'absolute', inset:0, zIndex:60, pointerEvents:'none',
    background: p<0.5? '#fff':'#000', opacity:op*0.9, mixBlendMode:'difference'}}/>;
}

// big faint index number in a corner
function IndexNo({ n, x, y }){
  return <div style={{position:'absolute', left:x, top:y, fontFamily:'Anton, sans-serif',
    fontSize:200, color:'rgba(242,237,227,0.13)', lineHeight:0.8, transform:'skewX(-5deg)'}}>{n}</div>;
}

// =====================================================================
//  ArtistCard — one full scene
// =====================================================================
function ArtistCard({ start, end, src, lines, photoSide, idx, venue, date,
                     tagTop, tagBot, accent=RED, seed=2, noFrame=false, photoSize=680, tint=null }){
  return (
    <Sprite start={start} end={end}>
      {({ localTime, duration })=>{
        const dur = duration;
        const photoX = photoSide==='left' ? 150 : 1920-150-680;
        const typeAlignRight = photoSide==='left';
        // photo entry + slow zoom (ken burns) + exit
        const inT = clamp(localTime/0.35,0,1);
        const outT = clamp((localTime-(dur-0.35))/0.35,0,1);
        const pe = Easing.easeOutBack(inT);
        const pScale = (0.82 + 0.18*pe) * (1 + 0.05*clamp((localTime-0.5)/(dur-0.85),0,1)) * (1-0.12*outT);
        const pRot = (1-pe)*-10 + Math.sin(localTime*1.6)*1.2;
        const pOpacity = noFrame
          ? clamp(localTime/0.04,0,1) * (1-outT)   // noFrame: snap in instantly to match text
          : clamp(localTime/0.18,0,1) * (1-outT);
        const tExit = 1 - Easing.easeInCubic(outT);
        return (
          <div style={{position:'absolute', inset:0, background:accent, overflow:'hidden'}}>
            <IndexNo n={idx} x={typeAlignRight? 1660:90} y={40}/>

            {/* photo */}
            <div style={{position:'absolute', left:photoX, top:200, opacity:pOpacity,
              transform:`scale(${pScale}) rotate(${pRot}deg)`, transformOrigin:'center'}}>
              <StarburstPhoto src={src} size={photoSize} seed={seed} spin={photoSide==='left'?-4:5} noFrame={noFrame} tint={tint}/>
            </div>

            {/* small two-word caption (tak / terlalu style) */}
            <div style={{position:'absolute',
              [typeAlignRight?'left':'right']: 160, top: 854,
              textAlign: typeAlignRight?'left':'right',
              transform:`skewX(-6deg) translateY(${(1-inT)*24}px)`, opacity:inT*tExit,
              fontFamily:'Archivo, sans-serif', fontWeight:900, textTransform:'lowercase',
              lineHeight:0.9}}>
              <div style={{fontSize:26, color:'rgba(255,255,255,0.6)'}}>{tagTop}</div>
              <div style={{fontSize:40, color:'#fff'}}>{tagBot}</div>
            </div>

            {/* giant artist name */}
            <div style={{position:'absolute',
              top: 168,
              [typeAlignRight?'right':'left']: 110,
              textAlign: typeAlignRight?'right':'left',
              transform:`rotate(${typeAlignRight?-3:3}deg) translateY(${(1-tExit)*30}px)`,
              transformOrigin: typeAlignRight?'right top':'left top',
              opacity:tExit }}>
              {lines.map((w,i)=>(
                <div key={i} style={{display:'flex', justifyContent: typeAlignRight?'flex-end':'flex-start'}}>
                  <GlitchWord text={w} size={Math.min(232, Math.floor(1520/Math.max(w.length,4)))} localT={localTime}
                              appearAt={0.0+i*0.06} seed={seed*7+i*13} skew={typeAlignRight?-4:-2}/>
                </div>
              ))}
            </div>

            {/* mono venue line */}
            <div style={{position:'absolute', left:0, right:0, bottom:64, textAlign:'center',
              fontFamily:'Space Mono, monospace', fontWeight:700, fontSize:26, letterSpacing:'0.32em',
              textTransform:'uppercase', color:BRASS, opacity:clamp((localTime-0.4)/0.4,0,1)*tExit,
              transform:`translateY(${(1-clamp((localTime-0.4)/0.4,0,1))*20}px)`}}>
              {venue} &nbsp;·&nbsp; {date}
            </div>
          </div>
        );
      }}
    </Sprite>
  );
}

// =====================================================================
//  Intro & Outro
// =====================================================================
function Intro({ start, end }){
  return (
    <Sprite start={start} end={end}>
      {({ localTime, duration })=>{
        const out = clamp((localTime-(duration-0.3))/0.3,0,1);
        const reveal = clamp(localTime/0.4,0,1);
        return (
          <div style={{position:'absolute', inset:0, background:RED, overflow:'hidden',
            opacity:1-out}}>
            <div style={{position:'absolute', inset:0, display:'flex', flexDirection:'column',
              alignItems:'center', justifyContent:'center'}}>
              <div style={{fontFamily:'Space Mono, monospace', fontWeight:700, fontSize:30,
                letterSpacing:'0.5em', color:BRASS, textTransform:'uppercase', marginBottom:10,
                opacity:clamp((localTime-0.5)/0.4,0,1)}}>presents</div>
              <div style={{overflow:'hidden'}}>
                <GlitchWord text="BOXOFFIX" size={340} color={PAPER} localT={localTime}
                  appearAt={0.0} stagger={0.06} skew={-4} seed={3}/>
              </div>
              <div style={{fontFamily:'Archivo, sans-serif', fontWeight:900, fontSize:44,
                color:'#fff', textTransform:'uppercase', letterSpacing:'0.04em', marginTop:18,
                transform:`skewX(-6deg)`, opacity:clamp((localTime-0.8)/0.4,0,1)}}>live &amp; loud · 2026</div>
            </div>
          </div>
        );
      }}
    </Sprite>
  );
}

function Outro({ start, end }){
  return (
    <Sprite start={start} end={end}>
      {({ localTime, duration })=>{
        const reveal = clamp(localTime/0.4,0,1);
        const pulse = 1 + Math.sin(localTime*8)*0.015;
        return (
          <div style={{position:'absolute', inset:0, background:RED, overflow:'hidden',
            display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center'}}>
            <div style={{fontFamily:'Space Mono, monospace', fontWeight:700, fontSize:28,
              letterSpacing:'0.42em', color:BRASS, textTransform:'uppercase', marginBottom:6,
              opacity:clamp((localTime-0.5)/0.4,0,1)}}>tickets on sale now</div>
            <div style={{transform:`scale(${pulse})`}}>
              <GlitchWord text="BOXOFFIX" size={232} color={PAPER} localT={localTime}
                appearAt={0} stagger={0.05} skew={-4} seed={9}/>
            </div>
            <div style={{fontFamily:'Archivo, sans-serif', fontWeight:900, fontSize:40,
              color:PAPER, textTransform:'uppercase', letterSpacing:'0.06em', marginTop:22,
              opacity:clamp((localTime-0.9)/0.4,0,1)}}>boxoffix.live</div>
          </div>
        );
      }}
    </Sprite>
  );
}

// =====================================================================
//  Timestamp label (for commenting)
// =====================================================================
function Clock(){
  const t = useTime();
  const root = document.querySelector('[data-screen-label]');
  React.useEffect(()=>{ if(root) root.setAttribute('data-screen-label', t.toFixed(0)+'s'); },[Math.floor(t)]);
  return null;
}

// =====================================================================
//  Root
// =====================================================================
function ConcertixTitles(){
  const W=1920, H=1080, DUR=9.6;
  return (
    <div data-screen-label="0s" style={{position:'absolute', inset:0}}>
      <Stage width={W} height={H} duration={DUR} background={RED} frameColor={RED} persistKey="concertix-titles">
        <Clock/>
        <ArtistCard start={0.0} end={2.4} idx="01" src="art/matt_face_v3.png" photoSize={780}
          lines={['MATT','RIFE']} photoSide="left" seed={2}
          tagTop="wild" tagBot="'n out" venue="The Wiltern" date="LA · Oct 20"/>
        <ArtistCard start={2.4} end={4.8} idx="02" src="art/kanye_color.png" photoSize={720}
          lines={['KANYE','WEST']} photoSide="right" seed={5}
          tagTop="no" tagBot="encore" venue="The Forum" date="LA · Nov 04"/>
        <ArtistCard start={4.8} end={7.2} idx="03" src="art/coldplay_color.png" photoSize={720}
          lines={['COLD','PLAY']} photoSide="left" seed={11}
          tagTop="sky" tagBot="full" venue="Wembley Stadium" date="LDN · Aug 12"/>
        <ArtistCard start={7.2} end={9.6} idx="04" src="art/mark_face.png" noFrame
          lines={['MARK','NORMAND']} photoSide="left" seed={7}
          tagTop="stand" tagBot="up" venue="Comedy Cellar" date="NYC · Sep 14"/>

        {/* glitch flash cuts at scene boundaries */}
        <FlashCut at={2.4}/><FlashCut at={4.8}/><FlashCut at={7.2}/>

        <VHS w={W} h={H}/>
      </Stage>
    </div>
  );
}

window.ConcertixTitles = ConcertixTitles;
