class ErrorBoundary extends React.Component{
  constructor(props){super(props);this.state={hasError:false,error:null}}
  static getDerivedStateFromError(error){return{hasError:true,error:error}}
  componentDidCatch(error,info){console.error('LINECONIC CRASH:',error,info)}
  render(){
    if(this.state.hasError)return React.createElement('div',{style:{background:'#000',color:'#FF007F',width:'100vw',height:'100vh',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',fontFamily:"'Space Mono',monospace",gap:'16px',padding:'40px',textAlign:'center'}},
      React.createElement('div',{style:{fontSize:'clamp(40px,8vw,80px)'}},'\u26A0'),
      React.createElement('div',{style:{fontSize:'clamp(14px,2vw,20px)',textTransform:'uppercase',letterSpacing:'.1em'}},'SOMETHING BROKE'),
      React.createElement('div',{style:{fontSize:'clamp(11px,1.2vw,14px)',color:'rgba(255,255,255,.5)',maxWidth:'500px'}},String(this.state.error||'')),
      React.createElement('button',{onClick:function(){window.location.reload()},style:{marginTop:'16px',padding:'12px 32px',background:'transparent',border:'1px solid #FF007F',color:'#FF007F',fontFamily:"'Space Mono',monospace",fontSize:'14px',cursor:'pointer',textTransform:'uppercase',letterSpacing:'.08em'}},'RELOAD')
    );
    return this.props.children;
  }
}

// ═══════════════════════════════════════
// APP (root component)
// ═══════════════════════════════════════
function App(){
  const params=new URLSearchParams(window.location.search);
  const pathMode={'/operator':'operator','/audience':'audience','/vote':'vote','/answers':'answers','/play':'companion','/remote':'remote'}[window.location.pathname];
  const mode=pathMode||params.get('mode')||'launcher';
  const isLauncher=mode==='launcher';
  const isAudience=mode==='audience';
  const isVote=mode==='vote';
  const isAnswers=mode==='answers';
  const isCompanion=mode==='companion';
  const isRemote=mode==='remote';

  // Auth state for protected routes
  const needsAuth=mode==='operator'||mode==='answers'||mode==='remote'||mode==='audience';
  const[authUser,setAuthUser]=useState(null);
  const[authLoading,setAuthLoading]=useState(needsAuth);

  useEffect(function(){
    if(!needsAuth||!fbauth){setAuthLoading(false);return}
    var unsub=fbauth.onAuthStateChanged(function(user){
      setAuthUser(user);
      setAuthLoading(false);
    });
    return function(){unsub()};
  },[]);

  // Load saved state from localStorage (operator only restores slide position)
  const savedState=useMemo(()=>{
    try{const s=JSON.parse(localStorage.getItem('lineconic_state'));if(s){if(mode!=='operator'){s.currentSlide=0;s.scores=[0,0];s.guests=0;s.revealState={}}return{...initialState,...s}}}catch(e){}
    return initialState;
  },[]);

  const[state,dispatch]=useReducer(reducer,savedState);
  const stateRef=useRef(state);
  stateRef.current=state;
  const[autoTimer,setAutoTimer]=useState(false);
  const[autoAdvance,setAutoAdvance]=useState(false);
  const autoTimerRef=useRef(false);
  const autoAdvanceRef=useRef(false);
  autoTimerRef.current=autoTimer;
  autoAdvanceRef.current=autoAdvance;
  const autoTimerDuration=useRef(30);
  const[rewardTier,setRewardTier]=useState(null);
  const[pairingCode,setPairingCode]=useState(null);
  const[remotePaired,setRemotePaired]=useState(null);

  // Generate pairing code on show load (operator only)
  function generatePairingCode(){
    var c=String(Math.floor(1000+Math.random()*9000));
    setPairingCode(c);
    if(fbdb&&state.show&&state.show.id){
      fbdb.ref('live/'+state.show.id+'/pairing_code').set(c);
      fbdb.ref('live/'+state.show.id+'/remote_paired').remove();
      fbdb.ref('live/'+state.show.id+'/remote_cmd').remove();
    }
    setRemotePaired(null);
    return c;
  }

  // On show load, generate code + listen for paired device (operator only)
  useEffect(function(){
    if(mode!=='operator'||!fbdb||!state.show||!state.show.id)return;
    // Generate initial code
    var c=generatePairingCode();
    // Listen for paired device
    var ref=fbdb.ref('live/'+state.show.id+'/remote_paired');
    ref.on('value',function(snap){
      setRemotePaired(snap.val()||null);
    });
    return function(){ref.off()};
  },[state.show&&state.show.id]);

  // Remote command listener (operator only)
  useEffect(function(){
    if(mode!=='operator'||!fbdb||!state.show||!state.show.id)return;
    var lastTs=Date.now();
    var ref=fbdb.ref('live/'+state.show.id+'/remote_cmd');
    ref.on('value',function(snap){
      var d=snap.val();if(!d||!d.type||!d.ts)return;
      if(d.ts<=lastTs)return; // prevent replay on reconnect
      lastTs=d.ts;
      switch(d.type){
        case'NEXT':stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'NEXT_SLIDE'});break;
        case'PREV':stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'PREV_SLIDE'});break;
        case'REVEAL':dispatch({type:'TOGGLE_REVEAL'});break;
        case'SCORE_CYAN_UP':dispatch({type:'SCORE_CYAN',payload:1});break;
        case'SCORE_CYAN_DOWN':dispatch({type:'SCORE_CYAN',payload:-1});break;
        case'SCORE_PINK_UP':dispatch({type:'SCORE_PINK',payload:1});break;
        case'SCORE_PINK_DOWN':dispatch({type:'SCORE_PINK',payload:-1});break;
        case'TOGGLE_SCOREBOARD':dispatch({type:'TOGGLE_SCOREBOARD'});break;
        case'TOGGLE_SCOREBOARD_POS':dispatch({type:'TOGGLE_SCOREBOARD_POS'});break;
        case'TOGGLE_MUTE':dispatch({type:'TOGGLE_MUTE'});break;
        default:
          if(d.type.startsWith('JUMP_SECTION:')){
            dispatch({type:'JUMP_SECTION',payload:parseInt(d.type.split(':')[1])});
          }else if(d.type.startsWith('TIMER:')){
            var parts=d.type.split(':');var tAction=parts[1];var tDur=parts[2]?parseInt(parts[2]):0;
            if(tAction==='start'&&tDur){startTimerSystem(tDur,null,function(){if(autoAdvanceRef.current)dispatch({type:'NEXT_SLIDE'})});broadcastTimerAction('start',tDur)}
            else if(tAction==='stop'){stopTimerSystem();broadcastTimerAction('stop')}
            else if(tAction==='pause'){pauseTimerSystem();broadcastTimerAction('pause')}
            else if(tAction==='resume'){resumeTimerSystem();broadcastTimerAction('resume')}
          }else if(d.type.startsWith('FX:')){
            broadcastFx(d.type.split(':')[1]);
          }
          break;
      }
    });
    return function(){ref.off()};
  },[state.show&&state.show.id]);

  // Load show data
  // Priority: localStorage (same session) → Firebase active_show (cross-device) → ros-v1.json (default)
  useEffect(()=>{
    if(state.show)return;
    function restoreSlidePos(){if(mode!=='operator')return;try{const s=JSON.parse(localStorage.getItem('lineconic_state'));if(s&&s.currentSlide){dispatch({type:'GO_TO_SLIDE',payload:s.currentSlide})}}catch(e){}}
    function loadShow(show){localStorage.setItem('lineconic_show',JSON.stringify(show));localStorage.setItem('lineconic_show_ts',Date.now());dispatch({type:'SET_SHOW',payload:show});restoreSlidePos()}
    // 1. Try localStorage first (bust cache if version is stale)
    try{
      const cached=localStorage.getItem('lineconic_show');
      if(cached){var cShow=JSON.parse(cached);var cTs=parseInt(localStorage.getItem('lineconic_show_ts')||'0',10);var fourHours=4*60*60*1000;if(cShow.version&&cShow.version>=2&&(Date.now()-cTs)<fourHours){dispatch({type:'SET_SHOW',payload:cShow});restoreSlidePos();return}}
    }catch(e){}
    // 2. Try Firebase active_show (set by builder PUSH TO FIREBASE)
    if(fbdb){
      fbdb.ref('active_show').once('value',snap=>{
        const activeId=snap.val();
        if(activeId){
          fbdb.ref('shows/'+activeId).once('value',showSnap=>{
            const show=showSnap.val();
            if(show&&show.sections){loadShow(show);return}
            // Active show ID exists but data missing — fall through
            fallbackToStatic();
          });
        }else{
          fallbackToStatic();
        }
      });
    }else{
      fallbackToStatic();
    }
    function fallbackToStatic(){
      // 3. Fetch from ros-v1.json
      fetch('/ros-v1.json').then(r=>r.json()).then(show=>{
        loadShow(show);
        // Seed default to Firebase show_index if not already there
        if(fbdb){
          fbdb.ref('show_index/'+show.id).once('value',snap=>{
            if(!snap.val()){
              var updates={};
              updates['shows/'+show.id]=show;
              updates['show_index/'+show.id]={name:show.name,created:show.created||0,templateId:null};
              fbdb.ref().update(updates).catch(()=>{});
            }
          });
        }
      }).catch(()=>{
        // 4. Last resort: any show in Firebase
        if(fbdb){fbdb.ref('shows').limitToFirst(1).once('value',snap=>{const v=snap.val();if(v){const key=Object.keys(v)[0];loadShow(v[key])}})}
      });
    }
  },[]);

  // Persist state to localStorage on change
  useEffect(()=>{
    const toSave={currentSlide:state.currentSlide,scores:state.scores,revealState:state.revealState,showScoreboard:state.showScoreboard,scoreboardPosition:state.scoreboardPosition,muted:state.muted,guests:state.guests};
    try{localStorage.setItem('lineconic_state',JSON.stringify(toSave))}catch(e){console.warn('[lineconic] localStorage write failed (lineconic_state):',e&&e.message)}
  },[state.currentSlide,state.scores,state.revealState,state.showScoreboard,state.scoreboardPosition,state.muted,state.guests]);

  // Persist show edits (reorder/swap/import) to localStorage. Debounced 250ms so a
  // burst of edits doesn't repeatedly serialize the entire show.
  useEffect(function(){
    if(!state.show)return;
    var t=setTimeout(function(){
      try{localStorage.setItem('lineconic_show',JSON.stringify(state.show));localStorage.setItem('lineconic_show_ts',Date.now())}
      catch(e){console.warn('[lineconic] localStorage write failed (lineconic_show):',e&&e.message)}
    },250);
    return function(){clearTimeout(t)};
  },[state.show]);

  // Load red flags from localStorage on mount
  useEffect(function(){
    try{var saved=JSON.parse(localStorage.getItem('lineconic_redflags'));if(Array.isArray(saved)&&saved.length)dispatch({type:'SET_RED_FLAGS',payload:saved})}catch(e){console.warn('[lineconic] could not restore red flags:',e&&e.message)}
  },[]);

  // Restore BYPASS EXTRAS toggle from localStorage on mount (operator only; receivers get it via sync)
  useEffect(function(){
    try{if(localStorage.getItem('lineconic_extras_bypassed')==='1')dispatch({type:'SET_EXTRAS_BYPASSED',payload:true})}catch(e){console.warn('[lineconic] could not restore extras-bypass:',e&&e.message)}
  },[]);

// ═══ SYNC: Operator → Firebase + BroadcastChannel ═══
const bcRef=useRef(null);
const syncSkipRef=useRef(false);
// Tracks whether the operator's last Firebase sync write succeeded. Used to render
// a SYNC OFFLINE pill so silent .catch(()=>{}) failures aren't actually invisible.
const[syncOnline,setSyncOnline]=useState(true);

useEffect(()=>{
  try{bcRef.current=new BroadcastChannel('lineconic-ctrl')}catch(e){}
  return()=>{if(bcRef.current)bcRef.current.close()};
},[]);

// Mirror Firebase socket health to the operator's pill. Keeps the indicator honest
// even when no writes are happening (idle moments between slide changes).
useEffect(()=>{
  if(mode!=='operator'||!fbdb)return;
  var r=fbdb.ref('.info/connected');
  var h=function(snap){if(!snap.val())setSyncOnline(false)};
  r.on('value',h);
  return()=>r.off('value',h);
},[mode]);

useEffect(()=>{
  if(mode!=='operator'||!state.show||syncSkipRef.current)return;
  const sync={slide:state.currentSlide,scores:state.scores,revealState:state.revealState,scoreboard:state.showScoreboard,scoreboardPosition:state.scoreboardPosition,muted:state.muted,rewardTier:rewardTier,redFlags:state.redFlags,extrasBypassed:!!state.extrasBypassed,bonusSections:state.bonusSections||[],showName:state.show.name||'',showDate:new Date().toISOString().split('T')[0]};
  // Build companion metadata so /play knows what's happening without full show data
  const flat=flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections);
  const curSlide=flat[state.currentSlide];
  if(curSlide){
    const qTypes=['source_q','acronym_q','hotseat_prompt'];
    const isQ=qTypes.indexOf(curSlide.type)!==-1;
    const revealed=curSlide.id&&state.revealState&&state.revealState[curSlide.id]==='revealed';
    // Find section name via slideToSection (handles bonuses/red-flag injection consistently)
    const _secIdx=slideToSection(state.show,state.currentSlide,state.redFlags,state.extrasBypassed,state.bonusSections);
    const secName=(_secIdx>=0&&state.show.sections[_secIdx])?state.show.sections[_secIdx].name:null;
    sync.companion={slideId:curSlide.id||null,section:secName,slideType:curSlide.type,question:isQ?(curSlide.content&&curSlide.content.primary||null):null,category:curSlide.category||null,answer:isQ?(curSlide.content&&curSlide.content.answer||null):null,answerRevealed:!!(isQ&&revealed),host_notes:curSlide.host_notes||null,source:(curSlide.content&&curSlide.content.source)||null,secondary:(curSlide.content&&curSlide.content.secondary)||null,points:curSlide.points||null};
    sync.totalSlides=flat.length;
  }
  if(fbdb&&state.show.id){
    fbdb.ref('live/'+state.show.id+'/state').set(sync).then(function(){setSyncOnline(true)}).catch(function(){setSyncOnline(false)});
    fbdb.ref('active_show').set(state.show.id).catch(function(){setSyncOnline(false)});
  }
  if(bcRef.current){bcRef.current.postMessage({type:'sync',...sync})}
},[state.currentSlide,state.scores,state.revealState,state.showScoreboard,state.scoreboardPosition,state.muted,rewardTier,state.redFlags,state.extrasBypassed,state.bonusSections,state.show]);

// DOA topic push — operator pushes currentTopic on slide change so /vote page works
useEffect(()=>{
  if(mode!=='operator'||!state.show)return;
  const flat=flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections);
  const slide=flat[state.currentSlide];
  if(!slide)return;
  if(slide.type==='doa_vote'){pushTopic(slide.content.primary)}
  else if(slide.type!=='doa_verdict_dead'&&slide.type!=='doa_verdict_alive'){pushTopic(null)}
},[state.currentSlide,state.show,state.redFlags,state.extrasBypassed,state.bonusSections]);

useEffect(()=>{
  if(mode==='operator'||!state.show)return;
  const showId=state.show.id;
  let fbUnsub=null;
  if(fbdb){
    const ref=fbdb.ref('live/'+showId+'/state');
    const handler=snap=>{
      const d=snap.val();if(!d)return;
      syncSkipRef.current=true;
      if(d.redFlags)dispatch({type:'SET_RED_FLAGS',payload:d.redFlags,fromSync:true});
      if(d.extrasBypassed!==undefined)dispatch({type:'SET_EXTRAS_BYPASSED',payload:d.extrasBypassed,fromSync:true});
      if(Array.isArray(d.bonusSections))dispatch({type:'SET_BONUS_SECTIONS',payload:d.bonusSections});
      if(d.slide!==undefined)dispatch({type:'GO_TO_SLIDE',payload:d.slide});
      if(d.scores)dispatch({type:'SET_SCORES',payload:d.scores});
      if(d.revealState)dispatch({type:'SET_REVEAL_STATE',payload:d.revealState});
      if(d.scoreboard!==undefined)dispatch({type:'SET_SCOREBOARD',payload:d.scoreboard});
      if(d.scoreboardPosition)dispatch({type:'SET_SCOREBOARD_POS',payload:d.scoreboardPosition});
      if(d.muted!==undefined)dispatch({type:'SET_MUTED',payload:d.muted});
      if(d.rewardTier!==undefined)setRewardTier(d.rewardTier);
      syncSkipRef.current=false;
    };
    ref.on('value',handler);
    fbUnsub=()=>ref.off('value',handler);
    // FX listener (rain, flutter)
    const fxRef=fbdb.ref('live/'+showId+'/fx');
    const fxHandler=snap=>{const d=snap.val();if(d)handleFxMsg(d)};
    fxRef.on('value',fxHandler);
    const origUnsub=fbUnsub;
    fbUnsub=()=>{origUnsub();fxRef.off('value',fxHandler)};
  }
  const bcHandler=ev=>{
    const d=ev.data;if(d.type!=='sync')return;
    syncSkipRef.current=true;
    if(d.redFlags)dispatch({type:'SET_RED_FLAGS',payload:d.redFlags,fromSync:true});
    if(d.extrasBypassed!==undefined)dispatch({type:'SET_EXTRAS_BYPASSED',payload:d.extrasBypassed,fromSync:true});
    if(Array.isArray(d.bonusSections))dispatch({type:'SET_BONUS_SECTIONS',payload:d.bonusSections});
    if(d.slide!==undefined)dispatch({type:'GO_TO_SLIDE',payload:d.slide});
    if(d.scores)dispatch({type:'SET_SCORES',payload:d.scores});
    if(d.revealState)dispatch({type:'SET_REVEAL_STATE',payload:d.revealState});
    if(d.scoreboard!==undefined)dispatch({type:'SET_SCOREBOARD',payload:d.scoreboard});
    if(d.scoreboardPosition)dispatch({type:'SET_SCOREBOARD_POS',payload:d.scoreboardPosition});
    if(d.muted!==undefined)dispatch({type:'SET_MUTED',payload:d.muted});
    if(d.rewardTier!==undefined)setRewardTier(d.rewardTier);
    syncSkipRef.current=false;
  };
  if(bcRef.current)bcRef.current.onmessage=ev=>{
    const d=ev.data;
    if(d.type==='sync')bcHandler(ev);
    if(d.type==='timer')handleTimerMsg(d);
    if(d.type==='fx')handleFxMsg(d);
  };
  return()=>{if(fbUnsub)fbUnsub();if(bcRef.current)bcRef.current.onmessage=null};
},[mode,state.show]);

// ═══ TIMER SYNC ═══
const audienceSlideRef=useRef(null);

function broadcastTimerAction(action,duration){
  const s=stateRef.current;
  const msg={type:'timer',action:action,duration:duration||0,timestamp:Date.now()};
  if(fbdb&&s.show&&s.show.id)fbdb.ref('live/'+s.show.id+'/timer').set(msg).catch(()=>{});
  if(bcRef.current)bcRef.current.postMessage(msg);
}

// ═══ FX SYNC (rain, flutter etc) ═══
function broadcastFx(fxType){
  const s=stateRef.current;
  const msg={type:'fx',fx:fxType,timestamp:Date.now()};
  if(fbdb&&s.show&&s.show.id)fbdb.ref('live/'+s.show.id+'/fx').set(msg).catch(()=>{});
  if(bcRef.current)bcRef.current.postMessage(msg);
}
function handleFxMsg(d){
  if(!d||d.type!=='fx')return;
  // Ignore stale FX from previous sessions (>5s old)
  if(d.timestamp&&Date.now()-d.timestamp>5000)return;
  if(d.fx==='rain'){startParticles('rain');sfx('receipt_rain',stateRef.current.muted)}
  else if(d.fx==='flutter')startParticles('flutter');
  else if(d.fx==='airhorn')playSample(SND_AIRHORN);
  else if(d.fx==='rimshot')playSample(SND_RIMSHOT);
  else if(d.fx==='crickets')playSample(SND_CRICKETS);
  else if(d.fx==='scream')playSample(SND_SCREAM);
}

function handleTimerMsg(d){
  if(!d||d.type!=='timer')return;
  // Find the .tb element on current audience slide
  const tbEl=audienceSlideRef.current?audienceSlideRef.current.querySelector('.tb'):null;
  switch(d.action){
    case'start':{
      const elapsed=(Date.now()-d.timestamp)/1000;
      const remaining=Math.max(0,d.duration-elapsed);
      if(remaining>0.5)startTimerSystem(remaining,tbEl);
      break;
    }
    case'pause':pauseTimerSystem();break;
    case'resume':resumeTimerSystem();break;
    case'stop':stopTimerSystem();break;
  }
}

// Audience listens for timer events via Firebase
useEffect(()=>{
  if(mode==='operator'||!state.show)return;
  if(!fbdb)return;
  const ref=fbdb.ref('live/'+state.show.id+'/timer');
  const handler=snap=>{
    const d=snap.val();
    if(d)handleTimerMsg(d);
  };
  ref.on('value',handler);
  return()=>ref.off('value',handler);
},[mode,state.show]);

// ═══ AUTO-TIMER: start timer on question slides ═══
useEffect(()=>{
  if(mode!=='operator'||!autoTimerRef.current||!state.show)return;
  const flat=flattenSlides(state.show,state.redFlags,state.extrasBypassed,state.bonusSections);
  const slide=flat[state.currentSlide];
  if(!slide)return;
  const isQ=slide.type==='source_q'||slide.type==='acronym_q'||slide.type==='hotseat_prompt';
  if(!isQ)return;
  const dur=autoTimerDuration.current;
  const onEnd=function(){if(autoAdvanceRef.current)dispatch({type:'NEXT_SLIDE'})};
  startTimerSystem(dur,null,onEnd);
  broadcastTimerAction('start',dur);
},[state.currentSlide,autoTimer]);

  // Set cursor style for audience mode
  useEffect(()=>{
    if(isAudience)document.body.classList.add('audience-mode');
    return()=>document.body.classList.remove('audience-mode');
  },[isAudience]);

  // Keyboard handler
  useEffect(()=>{
    function handleKey(ev){
      if(ev.target.tagName==='INPUT'||ev.target.tagName==='TEXTAREA')return;
      const s=stateRef.current;
      switch(ev.key){
        case'ArrowRight':case' ':case'PageDown':ev.preventDefault();stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'NEXT_SLIDE'});break;
        case'ArrowLeft':case'PageUp':ev.preventDefault();stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'PREV_SLIDE'});break;
        case'Home':stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'GO_TO_SLIDE',payload:0});break;
        case'End':{const flat=flattenSlides(s.show,s.redFlags,s.extrasBypassed,s.bonusSections);stopTimerSystem();broadcastTimerAction('stop');dispatch({type:'GO_TO_SLIDE',payload:flat.length-1});break}
        case'r':case'R':dispatch({type:'TOGGLE_REVEAL'});break;
        case's':dispatch({type:'TOGGLE_SCOREBOARD'});break;
        case'S':if(ev.shiftKey)dispatch({type:'TOGGLE_SCOREBOARD_POS'});else dispatch({type:'TOGGLE_SCOREBOARD'});break;
        case't':case'T':{const dur=autoTimerDuration.current||30;const onEnd=function(){if(autoAdvanceRef.current)dispatch({type:'NEXT_SLIDE'})};startTimerSystem(dur,null,onEnd);broadcastTimerAction('start',dur);break}
        case'm':case'M':dispatch({type:'TOGGLE_MUTE'});break;
        case'q':case'Q':dispatch({type:'SCORE_CYAN',payload:1});break;
        case'w':case'W':dispatch({type:'SCORE_CYAN',payload:-1});break;
        case'p':case'P':dispatch({type:'SCORE_PINK',payload:1});break;
        case'o':case'O':dispatch({type:'SCORE_PINK',payload:-1});break;
        case'a':case'A':setAutoTimer(function(v){return!v});break;
        case'd':case'D':setAutoAdvance(function(v){return!v});break;
        case'z':case'Z':broadcastFx('airhorn');break;
        case'x':case'X':broadcastFx('rimshot');break;
        case'b':case'B':broadcastFx('crickets');break;
        case'n':case'N':broadcastFx('scream');break;
        case',':broadcastFx('rain');break;
        case'g':case'G':setRewardTier(function(v){const cycle=[null,'cancel','grind','flop'];const i=cycle.indexOf(v);return cycle[(i+1)%cycle.length]});break;
        case'v':case'V':{const flat=flattenSlides(s.show,s.redFlags,s.extrasBypassed,s.bonusSections);const slide=flat[s.currentSlide];if(slide&&slide.type==='doa_vote'){pushTopic(slide.content.primary||null)}break}
        case'l':case'L':if(!s.liveMode)dispatch({type:'TOGGLE_SETLIST_EDITOR'});break;
        case'f':case'F':if(!document.fullscreenElement)document.documentElement.requestFullscreen();else document.exitFullscreen();break;
        case'?':dispatch({type:'TOGGLE_SHORTCUTS'});break;
        case'Escape':dispatch({type:'CLOSE_OVERLAYS'});break;
        case'1':case'2':case'3':case'4':case'5':case'6':case'7':case'8':case'9':
          if(s.show){const idx=parseInt(ev.key)-1;if(idx<s.show.sections.length)dispatch({type:'JUMP_SECTION',payload:idx})}break;
      }
    }
    window.addEventListener('keydown',handleKey);
    return()=>window.removeEventListener('keydown',handleKey);
  },[]);

  // Public routes — no auth needed
  if(isLauncher)return <LauncherView/>;
  if(isVote)return <VoteView/>;
  if(isCompanion){
    var urlParams=new URLSearchParams(window.location.search);
    if(urlParams.get('mode')==='redflag')return <RedFlagPreShow source="pre-show"/>;
    if(urlParams.get('mode')==='redflag-door')return <RedFlagPreShow source="door"/>;
    return <CompanionView/>;
  }
  // Auth gate — protected routes (operator, answers, audience, remote)
  if(authLoading)return <AuthLoadingView/>;
  if(needsAuth&&!authUser){window.location.href='/';return null;}

  if(isAudience){
    if(!state.show)return <div style={{background:'#000',color:'var(--signal)',width:'100vw',height:'100vh',display:'flex',alignItems:'center',justifyContent:'center',fontFamily:"'Space Mono',monospace"}}>LOADING SHOW DATA...</div>;
    return <AudienceView state={state} dispatch={dispatch} slideRef={audienceSlideRef} rewardTier={rewardTier}/>;
  }

  // Protected routes — user is authenticated
  if(!state.show)return <div style={{background:'#000',color:'var(--signal)',width:'100vw',height:'100vh',display:'flex',alignItems:'center',justifyContent:'center',fontFamily:"'Space Mono',monospace"}}>LOADING SHOW DATA...</div>;
  if(isRemote)return <RemoteView state={state}/>;
  if(isAnswers)return <AnswersView show={state.show} extrasBypassed={state.extrasBypassed}/>;
  return <>
    {!syncOnline&&<div style={{position:'fixed',top:8,right:8,zIndex:9999,padding:'4px 10px',background:'rgba(255,0,127,.15)',border:'1px solid var(--signal)',color:'var(--signal)',fontFamily:"'Space Mono',monospace",fontSize:10,letterSpacing:'.12em',textTransform:'uppercase',pointerEvents:'none'}} title="Firebase write failed — audience may be desynced">SYNC OFFLINE</div>}
    <OperatorView state={state} dispatch={dispatch} onTimerAction={broadcastTimerAction} onFx={broadcastFx} autoTimer={autoTimer} autoAdvance={autoAdvance} setAutoTimer={setAutoTimer} setAutoAdvance={setAutoAdvance} autoAdvanceRef={autoAdvanceRef} autoTimerDuration={autoTimerDuration} pairingCode={pairingCode} remotePaired={remotePaired} generatePairingCode={generatePairingCode}/>
  </>;
}
