/* OVERLAY COMPOSER MODAL HOTFIX — 2026-03-07 | FILE: assets/js/modules/overlay-composer.js */
(function(){
  const d=document, w=window;
  const PROJECT=(w.ITAHUKA_PROJECT&&w.ITAHUKA_PROJECT.id)||w.PROJECT||new URLSearchParams(location.search).get('project')||'default';
  const USER_ID=(w.ITAHUKA_PROJECT&&w.ITAHUKA_PROJECT.user_id)||0;
  const fxState = w.fxState || (w.fxState={overlays:{enabled:{},settings:{}}});

  function $(id){ return d.getElementById(id); }
  function uid(p){ return (p||'id')+'_'+Math.random().toString(36).slice(2,10); }
  function clone(v){ return JSON.parse(JSON.stringify(v)); }
  function escapeHtml(s){ return String(s==null?'':s).replace(/[&<>\"]/g,m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[m])); }
  function pct(v){ return Math.max(0, Math.min(100, Number(v||0))); }
  function deepGet(obj, path){
    if(!path) return '';
    const src = Object.assign({}, w.overlayComposerData||{}, w.streamOverlayData||{}, obj||{});
    return String(path.split('.').reduce((acc,k)=>acc && acc[k], src) ?? '');
  }
  function bindText(layer){
    let txt=String(layer.text||'');
    txt=txt.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, p)=>deepGet(null, String(p).trim()));
    if(layer.bindingPath) txt = deepGet(null, layer.bindingPath) || txt;
    return txt;
  }

  function ensureModalRoot(){
    let modal=$('overlayComposerModal');
    if(!modal){
      modal=d.createElement('div');
      modal.id='overlayComposerModal';
      modal.className='ocModal';
      modal.setAttribute('aria-hidden','true');
      d.body.appendChild(modal);
    }
    try{
      modal.classList.remove('overlay');
      modal.classList.add('ocMountedRoot');
      if(modal.parentElement!==d.body) d.body.appendChild(modal);
    }catch(_){ }
    if(!modal.querySelector('.ocShell')){
      modal.innerHTML=`
      <div class="ocShell">
        <div class="ocHeader">
          <div class="ocHeaderTitle">
            <div class="ocEyebrow">Graphics Composer</div>
            <div class="ocTitleRow"><b>Overlay Composer</b><span class="ocSub">Templates, timeline, keyframes, bindings, preview/program</span></div>
          </div>
          <div class="ocHeaderActions">
            <input id="ocSceneName" type="text" placeholder="Scene name" />
            <button class="miniBtn" id="ocNewSceneBtn" type="button">New</button>
            <button class="miniBtn" id="ocSaveSceneBtn" type="button">Save</button>
            <button class="miniBtn" id="ocToPreviewBtn" type="button">To Preview</button>
            <button class="miniBtn ok" id="ocToProgramBtn" type="button">To Program</button>
            <button class="miniBtn danger" id="ocCloseBtn" type="button">Close</button>
          </div>
        </div>
        <div class="ocToolbar">
          <div class="ocToolbarGroup" id="ocTemplates"></div>
          <div class="ocToolbarGroup ocToolbarRight">
            <label class="ocToggle"><input id="ocSnapToggle" type="checkbox" checked /> Snap Grid</label>
            <label class="ocToggle"><input id="ocSafeToggle" type="checkbox" checked /> Safe Area</label>
            <label class="ocToggle"><input id="ocStageAnimToggle" type="checkbox" checked /> Animate</label>
          </div>
        </div>
        <div class="ocBody">
          <aside class="ocPanel ocLeft">
            <div class="ocPanelTitle">Scenes</div>
            <div class="ocSceneList" id="ocSceneList"></div>
            <div class="ocPanelTitle">Layers</div>
            <div class="ocLayerList" id="ocLayerList"></div>
          </aside>
          <main class="ocCenter">
            <div class="ocCanvasTools">
              <label class="miniBtn" for="ocImageInput">Import Image</label>
              <input id="ocImageInput" type="file" accept="image/png,image/jpeg,image/webp,image/svg+xml" hidden />
              <button class="miniBtn" id="ocAddTextBtn" type="button">Add Text</button>
              <button class="miniBtn" id="ocAddRectBtn" type="button">Add Panel</button>
              <button class="miniBtn" id="ocDuplicateLayerBtn" type="button">Duplicate Layer</button>
              <button class="miniBtn danger" id="ocDeleteLayerBtn" type="button">Delete Layer</button>
              <button class="miniBtn" id="ocPreviewAnimBtn" type="button">Preview Anim</button>
            </div>
            <div class="ocStageWrap">
              <div class="ocStage chromeCard" id="ocStage">
                <div class="ocStageGrid"></div>
                <div class="ocSafeArea"></div>
              </div>
            </div>
            <div class="ocTimeline chromeCard">
              <div class="ocTimelineHead">
                <div class="ocPanelTitle">Timeline / Animation</div>
                <div class="ocTimelineLegend">Entrance · Continuous · Outrace · Keyframes</div>
              </div>
              <div class="ocTimelineRows" id="ocTimelineRows"></div>
            </div>
          </main>
          <aside class="ocPanel ocRight">
            <div class="ocPanelTitle">Inspector</div>
            <div class="ocInspector" id="ocInspector"></div>
          </aside>
        </div>
      </div>`;
    }
    modal.setAttribute('aria-hidden','true');
    modal.classList.remove('show','active');
    modal.style.display='none';
    return modal;
  }

  const modal=ensureModalRoot();
  const ocSceneName=$('ocSceneName'), ocTemplates=$('ocTemplates'), ocSceneList=$('ocSceneList'), ocLayerList=$('ocLayerList'),
        ocInspector=$('ocInspector'), ocStage=$('ocStage'), ocTimelineRows=$('ocTimelineRows'), ocImageInput=$('ocImageInput'),
        ocStageAnimToggle=$('ocStageAnimToggle'), ocSnapToggle=$('ocSnapToggle'), ocSafeToggle=$('ocSafeToggle');
  if(!ocStage || !ocTemplates || !ocSceneList || !ocLayerList || !ocInspector || !ocTimelineRows){
    console.error('Overlay Composer hotfix: required modal nodes missing');
    return;
  }

  function textLayer(text,x,y,wid,hei,opt){
    opt=opt||{};
    return {id:uid('layer'),type:'text',name:text,typeLabel:'Text',text:text,x:x,y:y,w:wid,h:hei,z:1,color:opt.color||'#ffffff',fontSize:opt.fontSize||42,fontWeight:opt.fontWeight||800,align:opt.align||'left',opacity:1,rotate:0,scale:1,bgColor:opt.bgColor||'',radius:opt.radius||0,bindingPath:'',inFx:'slide-up',inDur:600,loop:'none',loopDur:2800,outFx:'fade-down',outDur:400,keyframes:[{t:0,x:x,y:y,scale:1,opacity:1,rotate:0},{t:100,x:x,y:y,scale:1,opacity:1,rotate:0}]};
  }
  function panelLayer(x,y,wid,hei,opt){
    opt=opt||{};
    return {id:uid('layer'),type:'panel',name:'Panel',typeLabel:'Panel',x:x,y:y,w:wid,h:hei,z:1,fill:opt.fill||'rgba(0,0,0,.58)',opacity:opt.opacity==null?1:opt.opacity,rotate:0,scale:1,radius:opt.radius||16,inFx:'fade-up',inDur:500,loop:'none',loopDur:2500,outFx:'fade-down',outDur:350,keyframes:[{t:0,x:x,y:y,scale:1,opacity:1,rotate:0},{t:100,x:x,y:y,scale:1,opacity:1,rotate:0}]};
  }
  function imageLayer(src,x,y,wid,hei){
    return {id:uid('layer'),type:'image',name:'Image',typeLabel:'Image',src:src,x:x,y:y,w:wid,h:hei,z:1,opacity:1,rotate:0,scale:1,radius:0,inFx:'zoom-in',inDur:500,loop:'none',loopDur:2500,outFx:'fade-down',outDur:350,keyframes:[{t:0,x:x,y:y,scale:1,opacity:1,rotate:0},{t:100,x:x,y:y,scale:1,opacity:1,rotate:0}]};
  }
  function mkScene(name,layers,templateKey){ return {id:uid('scene'),name:name,layers:layers,templateKey:templateKey||'custom'}; }
  function templates(){ return [
    {key:'lower-third', name:'Lower Third', note:'Name strap', build(){ return mkScene('Lower Third',[panelLayer(3,76,42,16,{fill:'rgba(8,11,18,.66)', radius:18}),panelLayer(3,76,2.2,16,{fill:'#ffb000', radius:0}),textLayer('Presenter Name',6,79,34,6,{fontSize:44}),textLayer('Role / Location',6,87,30,4,{fontSize:22,color:'rgba(255,255,255,.78)',fontWeight:700})],'lower-third'); }},
    {key:'breaking', name:'Breaking News', note:'Alert strap', build(){ return mkScene('Breaking News',[panelLayer(0,68,100,18,{fill:'linear-gradient(90deg, rgba(0,0,0,.88), rgba(0,0,0,.42))', radius:0}),panelLayer(0,68,16,18,{fill:'#c62828', radius:0}),textLayer('BREAKING',3,71,12,5,{fontSize:32}),textLayer('Major developing story headline goes here',18,76,72,6,{fontSize:28})],'breaking'); }},
    {key:'scoreboard', name:'Scoreboard', note:'Sports', build(){ return mkScene('Scoreboard',[panelLayer(35,5,30,12,{fill:'rgba(0,0,0,.62)', radius:16}),textLayer('HOME',37,8,7,4,{fontSize:24}),textLayer('2',45,7,6,6,{fontSize:42}),textLayer('AWAY',53,8,7,4,{fontSize:24}),textLayer('1',61,7,6,6,{fontSize:42}),textLayer('72:15',46,13,8,3,{fontSize:18}),panelLayer(34,4,32,14,{fill:'rgba(255,255,255,.08)', radius:18, opacity:.5})],'scoreboard'); }},
    {key:'headline', name:'Full Frame Headline', note:'Full frame', build(){ return mkScene('Full Frame Headline',[panelLayer(0,0,100,100,{fill:'linear-gradient(180deg, rgba(2,4,10,.25), rgba(2,4,10,.82))', radius:0}),textLayer('FULL SCREEN',5,14,22,4,{fontSize:24,color:'#ffb000'}),textLayer('Main Headline Goes Here',5,26,70,8,{fontSize:58}),textLayer('Supporting information or context line',5,38,66,5,{fontSize:26,color:'rgba(255,255,255,.78)'})],'headline'); }},
    {key:'bug', name:'Corner Bug', note:'Logo', build(){ return mkScene('Corner Bug',[panelLayer(82,5,13,13,{fill:'rgba(0,0,0,.58)', radius:14}),textLayer('LIVE',85,9,8,4,{fontSize:30})],'bug'); }},
    {key:'side-panel', name:'Side Panel', note:'Data panel', build(){ return mkScene('Side Panel',[panelLayer(72,0,28,100,{fill:'linear-gradient(180deg, rgba(4,7,12,.2), rgba(4,7,12,.88))', radius:0}),textLayer('TOPIC',76,12,18,3,{fontSize:18,color:'#ffb000'}),textLayer('Side Panel Title',76,18,18,6,{fontSize:36}),textLayer('{{ticker.headline}}',76,32,18,28,{fontSize:22,color:'rgba(255,255,255,.78)'})],'side-panel'); }}
  ]; }

  const state={ scenes:[], currentSceneId:null, selectedLayerId:null, snap:true, safe:true, animate:true };
  function currentScene(){ return state.scenes.find(s=>s.id===state.currentSceneId) || state.scenes[0] || null; }
  function currentLayer(){ const s=currentScene(); return s ? s.layers.find(l=>l.id===state.selectedLayerId) || s.layers[0] || null : null; }
  function sortLayers(s){ s.layers.sort((a,b)=>Number(a.z||0)-Number(b.z||0)); }
  function snap(v){ return state.snap ? Math.round(Number(v||0)/0.5)*0.5 : Number(v||0); }

  async function api(action, body){
    const res = await fetch('overlay_composer_api.php', {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body: JSON.stringify(Object.assign({ action, project: PROJECT, user_id: USER_ID }, body||{}))});
    const data = await res.json().catch(()=>({ok:false,error:'Bad JSON'}));
    if(!res.ok || data.ok===false) throw new Error(data.error || ('HTTP '+res.status));
    return data;
  }
  function saveLocal(){ localStorage.setItem('itahuka_overlay_composer_'+PROJECT, JSON.stringify({scenes: state.scenes, currentSceneId: state.currentSceneId})); }
  function loadLocal(){ try{ const raw=localStorage.getItem('itahuka_overlay_composer_'+PROJECT); if(!raw) return false; const obj=JSON.parse(raw); if(!Array.isArray(obj.scenes)||!obj.scenes.length) return false; state.scenes=obj.scenes; state.currentSceneId=obj.currentSceneId||obj.scenes[0].id; state.selectedLayerId=currentScene()?.layers[0]?.id||null; return true; }catch(_){ return false; } }
  async function loadRemote(){ try{ const res = await fetch('overlay_composer_api.php?project='+encodeURIComponent(PROJECT)+'&user_id='+encodeURIComponent(USER_ID), {credentials:'same-origin'}); const data = await res.json(); if(data&&data.ok&&Array.isArray(data.scenes)&&data.scenes.length){ state.scenes=data.scenes; state.currentSceneId=data.activeSceneId||data.scenes[0].id; state.selectedLayerId=currentScene()?.layers[0]?.id||null; renderAll(); } }catch(err){ console.warn(err); } }
  async function saveScene(){ const s=currentScene(); if(!s) return; s.name=(ocSceneName.value||s.name||'Untitled Scene').trim(); saveLocal(); renderSceneList(); renderComposerThumbs(); await api('save_scene', {scene:s, activeSceneId:s.id}); }

  function openComposer(sceneId){
    if(!state.scenes.length){ const seed=templates().slice(0,3).map(t=>t.build()); state.scenes=seed; state.currentSceneId=seed[0].id; state.selectedLayerId=seed[0].layers[0].id; }
    if(sceneId) state.currentSceneId=sceneId;
    modal.style.display='flex';
    modal.classList.add('show','active');
    modal.setAttribute('aria-hidden','false');
    d.body.classList.add('oc-composer-open');
    renderAll();
  }
  function closeComposer(){ modal.classList.remove('show','active'); modal.setAttribute('aria-hidden','true'); modal.style.display='none'; d.body.classList.remove('oc-composer-open'); }

  function renderTemplates(){ ocTemplates.innerHTML=''; templates().forEach(t=>{ const b=d.createElement('button'); b.className='ocTemplateBtn'; b.type='button'; b.innerHTML='<span>'+t.name+'</span><small>'+t.note+'</small>'; b.onclick=()=>{ const s=t.build(); state.scenes.push(s); state.currentSceneId=s.id; state.selectedLayerId=s.layers[0]?.id||null; renderAll(); saveScene().catch(console.warn); }; ocTemplates.appendChild(b); }); }
  function renderSceneList(){ const s0=currentScene(); ocSceneList.innerHTML=''; state.scenes.forEach(s=>{ sortLayers(s); const el=d.createElement('div'); el.className='ocSceneItem'+(s.id===state.currentSceneId?' active':''); el.innerHTML='<div><b>'+escapeHtml(s.name||'Untitled')+'</b></div><div class="ocSceneMeta">'+(s.layers?.length||0)+' layers · '+escapeHtml(s.templateKey||'custom')+'</div>'; el.onclick=()=>{ state.currentSceneId=s.id; state.selectedLayerId=s.layers[0]?.id||null; renderAll(); }; ocSceneList.appendChild(el); }); if(s0) ocSceneName.value=s0.name||''; }
  function renderLayerList(){ const s=currentScene(); ocLayerList.innerHTML=''; if(!s) return; sortLayers(s); [...s.layers].sort((a,b)=>Number(b.z||0)-Number(a.z||0)).forEach(l=>{ const el=d.createElement('div'); el.className='ocLayerItem'+(l.id===state.selectedLayerId?' active':''); el.innerHTML='<div><b>'+escapeHtml(l.name||l.typeLabel||l.type)+'</b></div><div class="ocLayerMeta">'+escapeHtml(l.type)+' · z '+(l.z||1)+'</div>'; el.onclick=()=>{ state.selectedLayerId=l.id; renderAll(); }; ocLayerList.appendChild(el); }); }
  function inAnimName(name){ return ({'slide-up':'ocSlideUp','slide-right':'ocSlideRight','fade-up':'ocFadeUp','zoom-in':'ocZoomIn','fade-down':'ocFadeDown'})[name]||'ocFadeUp'; }
  function loopAnimName(name){ return ({'none':'','pulse':'ocPulse','float':'ocFloat','blink':'ocBlink','drift':'ocDrift'})[name]||''; }
  function ensureAnimStyle(id, cssText){ let node=$(id); if(!node){ node=d.createElement('style'); node.id=id; d.head.appendChild(node); } node.textContent=cssText; }
  ensureAnimStyle('ocAnimBase', '@keyframes ocSlideUp{from{transform:translateY(22px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes ocSlideRight{from{transform:translateX(-28px);opacity:0}to{transform:translateX(0);opacity:1}}@keyframes ocFadeUp{from{transform:translateY(8px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes ocZoomIn{from{transform:scale(.88);opacity:0}to{transform:scale(1);opacity:1}}@keyframes ocFadeDown{from{opacity:1}to{opacity:0;transform:translateY(10px)}}@keyframes ocPulse{0%,100%{opacity:1}50%{opacity:.72}}@keyframes ocFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}@keyframes ocBlink{0%,48%,100%{opacity:1}50%,98%{opacity:.4}}@keyframes ocDrift{0%{transform:translateX(0)}50%{transform:translateX(8px)}100%{transform:translateX(0)}}');
  function buildKeyframeAnimation(layer){ const frames=Array.isArray(layer.keyframes)?layer.keyframes.slice().sort((a,b)=>Number(a.t)-Number(b.t)):[]; if(!frames.length) return ''; const name='ocKF_'+layer.id.replace(/[^a-z0-9_]/ig,''); const css='@keyframes '+name+'{'+frames.map(f=>`${Math.max(0,Math.min(100,Number(f.t||0)))}%{left:${pct(f.x)}%;top:${pct(f.y)}%;opacity:${Number(f.opacity==null?1:f.opacity)};transform:rotate(${Number(f.rotate||0)}deg) scale(${Number(f.scale||1)})}` ).join('')+'}'; ensureAnimStyle('style_'+name, css); return name; }
  function animationCss(l, keyframesName){ const parts=[]; if(state.animate&&l.inFx&&l.inFx!=='none') parts.push(inAnimName(l.inFx)+' '+Math.max(50,Number(l.inDur||500))+'ms ease both'); if(state.animate&&l.loop&&l.loop!=='none') parts.push(loopAnimName(l.loop)+' '+Math.max(120,Number(l.loopDur||2400))+'ms linear infinite'); if(keyframesName) parts.push(keyframesName+' '+Math.max(250,Number(l.kfDur||3000))+'ms linear '+(l.kfLoop?'infinite':'1')+' both'); return parts.join(', '); }
  function applyLayerBox(el,l){ el.style.left=pct(l.x)+'%'; el.style.top=pct(l.y)+'%'; el.style.width=pct(l.w)+'%'; el.style.height=pct(l.h)+'%'; el.style.opacity=String(Number(l.opacity==null?1:l.opacity)); el.style.borderRadius=Number(l.radius||0)+'px'; el.style.transform='rotate('+Number(l.rotate||0)+'deg) scale('+Number(l.scale||1)+')'; el.style.zIndex=String(Number(l.z||1)); }
  function renderSceneToLayer(layerHost, scene){ if(!scene||!layerHost) return; const holder=d.createElement('div'); holder.className='overlay-composer-scene'; holder.style.position='absolute'; holder.style.inset='0'; holder.style.pointerEvents='none'; sortLayers(scene); scene.layers.forEach(l=>{ const el=d.createElement('div'); el.className='ocEl '+(l.type==='text'?'ocElText':l.type==='image'?'ocElImage':'ocElPanel'); applyLayerBox(el,l); const kfName=buildKeyframeAnimation(l); if(state.animate) el.style.animation=animationCss(l,kfName); if(l.type==='text'){ el.innerHTML='<div class="ocBoundText">'+escapeHtml(bindText(l)).replace(/\n/g,'<br>')+'</div>'; el.style.color=l.color||'#fff'; el.style.fontSize=(Number(l.fontSize||32))+'px'; el.style.fontWeight=String(l.fontWeight||800); el.style.textAlign=l.align||'left'; if(l.bgColor){ el.style.background=l.bgColor; el.style.padding='8px 12px'; } } else if(l.type==='image'){ el.innerHTML='<img src="'+escapeHtml(l.src||'')+'" alt="" />'; } else { el.style.background=l.fill||'rgba(0,0,0,.58)'; if((l.fill||'').indexOf('gradient')>=0) el.style.background=l.fill; } holder.appendChild(el); }); layerHost.appendChild(holder); }
  function enableDrag(){ let drag=null; ocStage.querySelectorAll('.ocEl').forEach(el=>{ el.onmousedown=(ev)=>{ const s=currentScene(); const l=currentLayer(); if(!s||!l||!el.classList.contains('selected')) return; drag={sx:ev.clientX, sy:ev.clientY, x:Number(l.x||0), y:Number(l.y||0)}; ev.preventDefault(); }; }); d.onmousemove=(ev)=>{ if(!drag) return; const rect=ocStage.getBoundingClientRect(); const l=currentLayer(); if(!l) return; l.x=snap(drag.x + ((ev.clientX-drag.sx)/rect.width)*100); l.y=snap(drag.y + ((ev.clientY-drag.sy)/rect.height)*100); renderStage(); renderInspector(); }; d.onmouseup=()=>{ drag=null; }; }
  function renderStage(){ const s=currentScene(); ocStage.innerHTML='<div class="ocStageGrid"></div><div class="ocSafeArea"></div>'; ocStage.classList.toggle('show-grid', !!state.snap); ocStage.classList.toggle('show-safe', !!state.safe); if(!s) return; sortLayers(s); s.layers.forEach(l=>{ const el=d.createElement('div'); el.className='ocEl '+(l.type==='text'?'ocElText':l.type==='image'?'ocElImage':'ocElPanel')+(l.id===state.selectedLayerId?' selected':''); applyLayerBox(el,l); const kfName=buildKeyframeAnimation(l); if(state.animate) el.style.animation=animationCss(l,kfName); if(l.type==='text'){ el.innerHTML=escapeHtml(bindText(l)).replace(/\n/g,'<br>'); el.style.color=l.color||'#fff'; el.style.fontSize=(Number(l.fontSize||32))+'px'; el.style.fontWeight=String(l.fontWeight||800); el.style.textAlign=l.align||'left'; if(l.bgColor){ el.style.background=l.bgColor; el.style.padding='8px 12px'; } } else if(l.type==='image'){ el.innerHTML='<img src="'+escapeHtml(l.src||'')+'" alt="" />'; } else { el.style.background=l.fill||'rgba(0,0,0,.58)'; if((l.fill||'').indexOf('gradient')>=0) el.style.background=l.fill; } el.onclick=(ev)=>{ ev.stopPropagation(); state.selectedLayerId=l.id; renderLayerList(); renderInspector(); renderStage(); }; ocStage.appendChild(el); }); enableDrag(); }
  function renderTimeline(){ const s=currentScene(); ocTimelineRows.innerHTML=''; if(!s) return; sortLayers(s); s.layers.forEach(l=>{ const row=d.createElement('div'); row.className='ocTimelineRow'; const inPct=Math.max(4, Math.min(40, Number(l.inDur||500)/30)); const outPct=Math.max(4, Math.min(35, Number(l.outDur||400)/30)); const loopPct=Math.max(10, 96-inPct-outPct); row.innerHTML='<div class="ocTimelineName">'+escapeHtml(l.name||l.type)+'</div><div class="ocTimelineBars"><div class="ocBar in" style="left:1%;width:'+inPct+'%"></div><div class="ocBar loop" style="left:'+(1+inPct)+'%;width:'+loopPct+'%"></div><div class="ocBar out" style="right:1%;width:'+outPct+'%"></div></div>'; ocTimelineRows.appendChild(row); }); }
  function field(label, html){ const wrap=d.createElement('div'); wrap.className='ocField'; wrap.innerHTML='<label>'+label+'</label>'+html; return wrap; }
  function renderInspector(){ const l=currentLayer(); ocInspector.innerHTML=''; if(!l) return; ocInspector.appendChild(field('Layer Name','<input id="oc_i_name" value="'+escapeHtml(l.name||'')+'">')); ocInspector.appendChild(field('Type','<input value="'+escapeHtml(l.type)+'" disabled>')); const g=d.createElement('div'); g.className='ocGrid2'; g.append(field('Z','<input id="oc_i_z" type="number" value="'+Number(l.z||1)+'">')); g.append(field('X %','<input id="oc_i_x" type="number" step="0.5" value="'+Number(l.x||0)+'">')); g.append(field('Y %','<input id="oc_i_y" type="number" step="0.5" value="'+Number(l.y||0)+'">')); g.append(field('W %','<input id="oc_i_w" type="number" step="0.5" value="'+Number(l.w||0)+'">')); g.append(field('H %','<input id="oc_i_h" type="number" step="0.5" value="'+Number(l.h||0)+'">')); g.append(field('Opacity','<input id="oc_i_opacity" type="number" step="0.1" min="0" max="1" value="'+Number(l.opacity==null?1:l.opacity)+'">')); g.append(field('Rotation','<input id="oc_i_rotate" type="number" step="1" value="'+Number(l.rotate||0)+'">')); g.append(field('Scale','<input id="oc_i_scale" type="number" step="0.1" value="'+Number(l.scale||1)+'">')); g.append(field('Radius','<input id="oc_i_radius" type="number" step="1" value="'+Number(l.radius||0)+'">')); ocInspector.appendChild(g);
    if(l.type==='text'){ ocInspector.appendChild(field('Text','<textarea id="oc_i_text">'+escapeHtml(l.text||'')+'</textarea>')); ocInspector.appendChild(field('Color','<input id="oc_i_color" value="'+escapeHtml(l.color||'#ffffff')+'">')); ocInspector.appendChild(field('Font Size','<input id="oc_i_fontSize" type="number" value="'+Number(l.fontSize||32)+'">')); ocInspector.appendChild(field('Binding Path','<input id="oc_i_binding" placeholder="e.g. ticker.headline" value="'+escapeHtml(l.bindingPath||'')+'">')); }
    if(l.type==='panel'){ ocInspector.appendChild(field('Fill','<input id="oc_i_fill" value="'+escapeHtml(l.fill||'rgba(0,0,0,.58)')+'">')); }
    ocInspector.appendChild(field('Entrance','<select id="oc_i_inFx"><option value="none">none</option><option value="slide-up">slide-up</option><option value="slide-right">slide-right</option><option value="fade-up">fade-up</option><option value="zoom-in">zoom-in</option></select>'));
    ocInspector.appendChild(field('In ms','<input id="oc_i_inDur" type="number" value="'+Number(l.inDur||500)+'">'));
    ocInspector.appendChild(field('Continuous','<select id="oc_i_loop"><option value="none">none</option><option value="pulse">pulse</option><option value="float">float</option><option value="blink">blink</option><option value="drift">drift</option></select>'));
    ocInspector.appendChild(field('Loop ms','<input id="oc_i_loopDur" type="number" value="'+Number(l.loopDur||2500)+'">'));
    ocInspector.appendChild(field('Outrace','<select id="oc_i_outFx"><option value="none">none</option><option value="fade-down">fade-down</option></select>'));
    ocInspector.appendChild(field('Out ms','<input id="oc_i_outDur" type="number" value="'+Number(l.outDur||350)+'">'));
    [['oc_i_inFx','inFx'],['oc_i_loop','loop'],['oc_i_outFx','outFx']].forEach(([id,key])=>{ const n=$(id); if(n) n.value=l[key]||'none'; });
    ocInspector.querySelectorAll('input,textarea,select').forEach(n=>{ n.oninput=n.onchange=()=>{ if(!$('oc_i_name')) return; l.name=$('oc_i_name').value; l.z=Number($('oc_i_z').value||1); l.x=Number($('oc_i_x').value||0); l.y=Number($('oc_i_y').value||0); l.w=Number($('oc_i_w').value||0); l.h=Number($('oc_i_h').value||0); l.opacity=Number($('oc_i_opacity').value||1); l.rotate=Number($('oc_i_rotate').value||0); l.scale=Number($('oc_i_scale').value||1); l.radius=Number($('oc_i_radius').value||0); if(l.type==='text'){ l.text=$('oc_i_text').value; l.color=$('oc_i_color').value; l.fontSize=Number($('oc_i_fontSize').value||32); l.bindingPath=$('oc_i_binding').value; } if(l.type==='panel'){ l.fill=$('oc_i_fill').value; } l.inFx=$('oc_i_inFx').value; l.inDur=Number($('oc_i_inDur').value||500); l.loop=$('oc_i_loop').value; l.loopDur=Number($('oc_i_loopDur').value||2500); l.outFx=$('oc_i_outFx').value; l.outDur=Number($('oc_i_outDur').value||350); renderLayerList(); renderStage(); renderTimeline(); saveLocal(); }; }); }
  function renderComposerThumbs(){ const host=$('fxOverlays'); if(!host) return; [...host.querySelectorAll('[data-composer-scene="1"]')].forEach(n=>n.remove()); state.scenes.forEach(scene=>{ const item=d.createElement('div'); item.className='thumb fxThumb'+(((fxState.overlays.settings.composer||{}).sceneId===scene.id && fxState.overlays.enabled.composer)?' active':''); item.setAttribute('data-composer-scene','1'); item.innerHTML='<div class="thumbArt">COMPOSER</div><div class="thumbMeta"><b>'+escapeHtml(scene.name)+'</b></div>'; item.onclick=()=>{ fxState.overlays.enabled.composer=true; fxState.overlays.settings.composer={sceneId:scene.id}; saveFxAndUpdate(); renderComposerThumbs(); }; item.ondblclick=()=>openComposer(scene.id); host.appendChild(item); }); }
  function injectLauncherTile(){ const host=$('fxOverlays'); if(!host || host.querySelector('[data-composer-launcher="1"]')) return; const tile=d.createElement('div'); tile.className='thumb fxThumb ocComposerTile'; tile.setAttribute('data-composer-launcher','1'); tile.innerHTML='<div class="ocLauncherArt"></div><div class="ocLauncherLabel"><div class="ocLauncherTitle">Graphics</div><div class="ocLauncherSub">Overlay Composer</div></div>'; tile.onclick=()=>openComposer(); tile.ondblclick=()=>openComposer(); host.appendChild(tile); }
  function saveFxAndUpdate(){ if(typeof w.saveFx==='function') try{ w.saveFx(); }catch(_){} if(typeof w.updatePreviewOverlays==='function') try{ w.updatePreviewOverlays(); }catch(_){} if(typeof w.updateProgramOverlays==='function') try{ w.updateProgramOverlays(); }catch(_){} }
  function applySceneToPreview(){ const s=currentScene(); if(!s) return; fxState.overlays.enabled.composer=true; fxState.overlays.settings.composer={sceneId:s.id}; saveFxAndUpdate(); renderComposerThumbs(); }
  function applySceneToProgram(){ const s=currentScene(); if(!s) return; fxState.overlays.enabled.composer=true; fxState.overlays.settings.composer={sceneId:s.id}; if(w.programSnapshot){ w.programSnapshot.overlaysEnabled = clone(fxState.overlays.enabled); w.programSnapshot.overlaysSettings = clone(fxState.overlays.settings); } saveFxAndUpdate(); renderComposerThumbs(); }
  const prevRender = w.renderOverlays;
  if(typeof prevRender==='function' && !w.__overlayComposerPatched){ w.renderOverlays=function(layer, overlaysEnabled, overlaysSettings){ prevRender(layer, overlaysEnabled, overlaysSettings); const en=overlaysEnabled||fxState.overlays.enabled||{}; const st=overlaysSettings||fxState.overlays.settings||{}; if(!en.composer||!st.composer||!st.composer.sceneId) return; const scene=state.scenes.find(s=>s.id===st.composer.sceneId); if(scene) renderSceneToLayer(layer, scene); }; w.__overlayComposerPatched=true; }

  $('ocCloseBtn').onclick=closeComposer;
  modal.addEventListener('click', (ev)=>{ if(ev.target===modal) closeComposer(); });
  modal.querySelector('.ocShell')?.addEventListener('click', (ev)=>ev.stopPropagation());
  d.addEventListener('keydown', (ev)=>{ if(ev.key==='Escape' && (modal.classList.contains('show')||modal.classList.contains('active'))) closeComposer(); });
  $('ocNewSceneBtn').onclick=()=>{ const s=templates()[0].build(); s.name='New Scene'; state.scenes.push(s); state.currentSceneId=s.id; state.selectedLayerId=s.layers[0].id; renderAll(); };
  $('ocSaveSceneBtn').onclick=()=>saveScene().catch(err=>alert(err.message||String(err)));
  $('ocToPreviewBtn').onclick=applySceneToPreview;
  $('ocToProgramBtn').onclick=applySceneToProgram;
  $('ocAddTextBtn').onclick=()=>{ const s=currentScene(); if(!s) return; const l=textLayer('New Text',10,10,24,7,{fontSize:34}); l.z=s.layers.length+1; s.layers.push(l); state.selectedLayerId=l.id; renderAll(); };
  $('ocAddRectBtn').onclick=()=>{ const s=currentScene(); if(!s) return; const l=panelLayer(12,12,24,14,{fill:'rgba(0,0,0,.58)',radius:16}); l.z=s.layers.length+1; s.layers.push(l); state.selectedLayerId=l.id; renderAll(); };
  $('ocDuplicateLayerBtn').onclick=()=>{ const s=currentScene(), l=currentLayer(); if(!s||!l) return; const n=clone(l); n.id=uid('layer'); n.name=(l.name||l.type)+' Copy'; n.x=snap(Number(n.x||0)+2); n.y=snap(Number(n.y||0)+2); n.z=s.layers.length+1; s.layers.push(n); state.selectedLayerId=n.id; renderAll(); };
  $('ocDeleteLayerBtn').onclick=()=>{ const s=currentScene(), l=currentLayer(); if(!s||!l) return; s.layers=s.layers.filter(x=>x.id!==l.id); state.selectedLayerId=s.layers[0]?.id||null; renderAll(); };
  $('ocPreviewAnimBtn').onclick=()=>renderStage();
  ocImageInput.addEventListener('change', ev=>{ const file=ev.target.files&&ev.target.files[0]; if(!file) return; const fr=new FileReader(); fr.onload=()=>{ const s=currentScene(); if(!s) return; const l=imageLayer(String(fr.result||''),20,20,20,20); l.z=s.layers.length+1; s.layers.push(l); state.selectedLayerId=l.id; renderAll(); }; fr.readAsDataURL(file); });
  ocSnapToggle.addEventListener('change',()=>{ state.snap=!!ocSnapToggle.checked; renderStage(); });
  ocSafeToggle.addEventListener('change',()=>{ state.safe=!!ocSafeToggle.checked; renderStage(); });
  ocStageAnimToggle.addEventListener('change',()=>{ state.animate=!!ocStageAnimToggle.checked; renderStage(); });
  ocStage.addEventListener('click', ()=>{ state.selectedLayerId=null; renderLayerList(); renderInspector(); });

  function renderAll(){ saveLocal(); injectLauncherTile(); renderTemplates(); renderSceneList(); renderLayerList(); renderStage(); renderTimeline(); renderInspector(); renderComposerThumbs(); }
  if(!loadLocal()){ const seed=templates().slice(0,3).map(t=>t.build()); state.scenes=seed; state.currentSceneId=seed[0].id; state.selectedLayerId=seed[0].layers[0].id; }
  renderAll(); loadRemote(); w.openOverlayComposer=openComposer; w.__overlayComposerState=state;
})();
