Your IP : 216.73.216.79


Current Path : /var/www/html/unity/development/interactive-videoplayer/
Upload File :
Current File : /var/www/html/unity/development/interactive-videoplayer/videoplayer.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Video Player</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{--accent:#4a9eff;--dim:rgba(255,255,255,0.5)}
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#000;font-family:'DM Sans',sans-serif;color:#fff;margin:0;padding:0}
.pw{position:absolute;inset:0;width:100%;height:100%;background:#000;display:flex;align-items:center;justify-content:center}
.pw.hide-cur{cursor:none}
.player-video{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:1}
.trans-ov{position:absolute;inset:0;z-index:4;pointer-events:none;opacity:0}
.ov-layer{position:absolute;inset:0;z-index:10;pointer-events:none}
.ov-layer>*{pointer-events:auto}
.int-el{position:absolute;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:8px;font-family:'DM Sans',sans-serif;transition:transform .2s,opacity .3s,box-shadow .2s;opacity:0;pointer-events:none;text-align:center;font-size:14px;font-weight:600;overflow:hidden}
.int-el.vis{opacity:1;pointer-events:auto}
.int-el:hover{transform:scale(1.04)}
.int-el:active{transform:scale(.97)}
.int-el img.ii{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}
.int-el .ic{position:relative;z-index:2;display:flex;align-items:center;gap:6px;padding:6px 14px}
.int-el.type-hotspot{border-radius:50%;animation:hp 2s infinite}
@keyframes hp{0%,100%{box-shadow:0 0 0 0 rgba(255,138,80,.6)}50%{box-shadow:0 0 0 12px rgba(255,138,80,0)}}
.ch-ov{position:absolute;inset:0;z-index:20;background:rgba(0,0,0,.75);backdrop-filter:blur(8px);display:none;align-items:center;justify-content:center;flex-direction:column;gap:20px;padding:40px}
.ch-ov.active{display:flex}
.ch-title{font-size:28px;font-weight:700;text-align:center;text-shadow:0 2px 12px rgba(0,0,0,.5);margin-bottom:10px}
.ch-sub{font-size:16px;color:var(--dim);text-align:center;margin-bottom:20px}
.ch-btns{display:flex;flex-wrap:wrap;gap:16px;justify-content:center;max-width:800px}
.ch-btn{background:rgba(255,255,255,.1);border:2px solid rgba(255,255,255,.2);color:#fff;padding:18px 36px;border-radius:12px;font-family:'DM Sans',sans-serif;font-size:18px;font-weight:600;cursor:pointer;transition:all .25s;min-width:180px;text-align:center;backdrop-filter:blur(4px)}
.ch-btn:hover{background:var(--accent);border-color:var(--accent);transform:translateY(-3px);box-shadow:0 8px 30px rgba(74,158,255,.4)}
.popup{position:absolute;z-index:25;background:rgba(20,20,30,.95);border:1px solid rgba(255,255,255,.15);border-radius:12px;padding:24px 28px;max-width:420px;backdrop-filter:blur(12px);box-shadow:0 12px 40px rgba(0,0,0,.6);display:none;top:50%;left:50%;transform:translate(-50%,-50%)}
.popup.active{display:block}
.popup .pt{font-size:16px;line-height:1.6;margin-bottom:16px}
.popup .pc{background:var(--accent);border:none;color:#fff;padding:8px 20px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer}
.ctrls{position:absolute;bottom:0;left:0;right:0;z-index:30;background:linear-gradient(transparent,rgba(0,0,0,.9));padding:60px 20px 16px;transition:opacity .4s,transform .4s}
.ctrls.hidden{opacity:0;transform:translateY(10px);pointer-events:none}
.ctrls.off{display:none!important}
.pb-wrap{width:100%;height:20px;display:flex;align-items:center;cursor:pointer;margin-bottom:8px;padding:6px 0}
.pb-track{width:100%;height:4px;background:rgba(255,255,255,.2);border-radius:2px;position:relative;transition:height .15s}
.pb-wrap:hover .pb-track{height:6px}
.pb-fill{height:100%;background:var(--accent);border-radius:2px;position:relative}
.pb-thumb{position:absolute;right:-7px;top:50%;transform:translateY(-50%);width:14px;height:14px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px rgba(74,158,255,.5);opacity:0;transition:opacity .15s}
.pb-wrap:hover .pb-thumb{opacity:1}
.pb-buf{position:absolute;top:0;left:0;height:100%;background:rgba(255,255,255,.15);border-radius:2px}
.pm{position:absolute;top:-3px;width:6px;height:10px;border-radius:2px;transform:translateX(-50%);z-index:2}
.pm.type-hotspot{background:#ff8a50}.pm.type-button{background:#4a9eff}.pm.type-choice{background:#3ddc84}.pm.type-overlay{background:#b47aff}
.cr{display:flex;align-items:center;gap:12px}
.cb{background:none;border:none;color:#fff;cursor:pointer;font-size:20px;padding:6px;display:flex;align-items:center;justify-content:center;transition:color .2s,transform .15s}
.cb:hover{color:var(--accent);transform:scale(1.1)}
.cb img.ci{width:24px;height:24px;object-fit:contain}
.ct{font-size:13px;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
.cs{flex:1}
.vg{display:flex;align-items:center;gap:6px}
.vs{width:80px;height:4px;-webkit-appearance:none;appearance:none;background:rgba(255,255,255,.3);border-radius:2px;cursor:pointer}
.vs::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--accent);cursor:pointer}
.ld{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px;background:#000}
.ld.off{display:none}
.spin{width:44px;height:44px;border:3px solid rgba(255,255,255,.2);border-top-color:var(--accent);border-radius:50%;animation:sp .8s linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}
.ld-txt{font-size:14px;color:var(--dim)}
.bp{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:15;width:80px;height:80px;border-radius:50%;background:rgba(0,0,0,.6);border:2px solid rgba(255,255,255,.3);color:#fff;font-size:32px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(4px)}
.bp:hover{background:var(--accent);border-color:var(--accent);transform:translate(-50%,-50%) scale(1.1)}
.bp.hid{opacity:0;pointer-events:none;transform:translate(-50%,-50%) scale(.8)}
.np{position:absolute;inset:0;z-index:100;background:rgba(10,10,15,.95);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}
.np i{font-size:48px;color:rgba(255,255,255,.2)}
.np .nt{font-size:20px;font-weight:600}
.np .nd{font-size:14px;color:var(--dim);text-align:center;max-width:400px;line-height:1.6}
.np .lb{margin-top:8px;background:var(--accent);border:none;color:#fff;padding:12px 28px;border-radius:10px;font-size:15px;font-weight:600;cursor:pointer}
.np input{display:none}
.proj-loading{position:absolute;inset:0;z-index:110;display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;background:#000}
.proj-loading.active{display:flex}
.proj-loading .pl-text{font-size:18px;font-weight:500}
</style>
</head>
<body>
<div class="pw" id="pw">
  <video class="player-video" id="vid" playsinline></video>
  <audio id="bgm" loop preload="auto" style="display:none"></audio>
  <!-- Overlay image layer between video and controls -->
  <div id="ovImg" style="position:absolute;inset:0;z-index:2;pointer-events:none;display:none;background-repeat:no-repeat"></div>
  <div class="trans-ov" id="tov"></div>
  <div class="ov-layer" id="ovL"></div>
  <div class="ch-ov" id="chOv">
    <div class="ch-title" id="chTitle">What do you choose?</div>
    <div class="ch-sub" id="chSub">Select an option to continue</div>
    <div class="ch-btns" id="chBtns"></div>
  </div>
  <div class="popup" id="popup"><div class="pt" id="popTxt"></div><button class="pc" onclick="closePopup()">Continue</button></div>
  <div class="bp" id="bp" onclick="togglePlay()"><i class="bi bi-play-fill"></i></div>
  <!-- Custom free-positioned controls -->
  <div id="customCtrls" style="position:absolute;inset:0;z-index:30;pointer-events:none;display:none;transition:opacity 0.4s ease"></div>
  <div class="ctrls" id="ctrls">
    <div class="pb-wrap" id="pbW"><div class="pb-track" id="pbT"><div class="pb-buf" id="pbB"></div><div class="pb-fill" id="pbF" style="width:0%"><div class="pb-thumb"></div></div></div></div>
    <div class="cr">
      <button class="cb" id="btnPlay" onclick="togglePlay()"><i class="bi bi-play-fill" id="pIc"></i></button>
      <button class="cb" onclick="skip(-10)"><i class="bi bi-skip-backward-fill seek-icon"></i></button>
      <button class="cb" onclick="skip(10)"><i class="bi bi-skip-forward-fill seek-icon"></i></button>
      <div class="vg"><button class="cb" onclick="toggleMute()"><i class="bi bi-volume-up-fill" id="vIc"></i></button><input type="range" class="vs" id="vS" min="0" max="1" step=".05" value="1" oninput="setVol(this.value)"></div>
      <div class="ct"><span id="cT">0:00</span> / <span id="tT">0:00</span></div>
      <div class="cs"></div>
      <button class="cb" onclick="restartAll()" title="Restart"><i class="bi bi-arrow-counterclockwise"></i></button>
      <button class="cb" onclick="toggleFS()" title="Fullscreen"><i class="bi bi-fullscreen" id="fsI"></i></button>
    </div>
  </div>
  <div class="ld off" id="ld"><div class="spin"></div><div class="ld-txt" id="ldT">Loading...</div></div>
  <div class="proj-loading" id="pL"><div class="spin"></div><div class="pl-text" id="plT">Loading...</div></div>
  <div class="np" id="np">
    <i class="bi bi-film"></i><div class="nt">Interactive Video Player</div>
    <div class="nd">Load a project JSON from IVS Studio, or receive via iframe postMessage.</div>
    <button class="lb" onclick="document.getElementById('jI').click()"><i class="bi bi-folder2-open"></i> Load Project</button>
    <input type="file" id="jI" accept=".json" onchange="loadFile(this.files[0])">
  </div>
</div>
<script>
// ===== STATE =====
var PD=null,curVid=null,vidMap={},playing=false,ctrlTm=null,aInts=[],loaded=false;
var storyEnded=false;
var V=document.getElementById('vid');
var TOV=document.getElementById('tov');
var BGM=document.getElementById('bgm');
var pauseIconSrc='';
var globalAudioSrc='video'; // 'video','bgm','both','mute'

// ===== LOAD PROJECT =====
function loadProject(data){
  if(loaded)return;loaded=true;
  PD=data;vidMap={};
  (data.videos||[]).forEach(function(v){vidMap[v.id]=v;});
  document.getElementById('np').style.display='none';
  document.getElementById('pL').classList.remove('active');
  applySettings(data.project?data.project.settings:null);
  var sid=data.project?data.project.startVideoId:null;
  if(!sid&&data.videos&&data.videos.length)sid=data.videos[0].id;
  if(!sid)return;
  var autoPlay=!data.project||!data.project.settings||data.project.settings.autoPlay!==false;
  var sv=vidMap[sid];
  var startTr=sv?sv.startTransition||'':'';
  if(autoPlay){
    if(startTr==='fadeInBlack'||startTr==='fadeInWhite'){
      TOV.style.background=startTr==='fadeInWhite'?'#fff':'#000';
      TOV.style.opacity='1';TOV.style.transition='none';
    }
    loadAndPlay(sid,function(){
      var d=(PD.project&&PD.project.settings?PD.project.settings.transitionDuration:null)||600;
      if(startTr==='fadeInBlack'||startTr==='fadeInWhite'){
        setTimeout(function(){TOV.style.transition='opacity '+d+'ms ease';TOV.style.opacity='0';},50);
      }else if(startTr==='blur'){
        V.style.filter='blur(20px)';V.style.transition='none';
        setTimeout(function(){V.style.transition='filter '+d+'ms ease';V.style.filter='none';},50);
      }else if(startTr==='zoomIn'){
        V.style.transform='scale(1.5)';V.style.opacity='0';V.style.transition='none';
        setTimeout(function(){V.style.transition='transform '+d+'ms ease, opacity '+d+'ms ease';V.style.transform='scale(1)';V.style.opacity='1';},50);
      }
    });
  }else{
    if(sv){curVid=sid;V.src=sv.blobUrl||sv.src;V.load();setupInts(sid);setupEnd(sid);showBP();}
  }
}

function applySettings(s){
  if(!s)return;
  if(s.accentColor)document.documentElement.style.setProperty('--accent',s.accentColor);
  V.style.objectFit=s.videoFit||'contain';
  // Custom icons
  if(s.playIcon){var el=document.getElementById('pIc');el.outerHTML='<img class="ci" id="pIc" src="'+s.playIcon+'" data-custom="1">';}
  if(s.pauseIcon)pauseIconSrc=s.pauseIcon;
  if(s.seekIcon){document.querySelectorAll('.seek-icon').forEach(function(el){var img=document.createElement('img');img.className='ci seek-icon';img.src=s.seekIcon;img.style.cssText='width:20px;height:20px';el.parentNode.replaceChild(img,el);});}
  if(s.muteIcon){var mi=document.getElementById('vIc');if(mi)mi.outerHTML='<img class="ci" id="vIc" src="'+s.muteIcon+'" style="width:20px;height:20px">';}
  if(s.fsIcon){var fi=document.getElementById('fsI');if(fi)fi.outerHTML='<img class="ci" id="fsI" src="'+s.fsIcon+'" style="width:20px;height:20px">';}
  // Controls
  if(s.showControls===false)document.getElementById('ctrls').classList.add('off');
  if(s.allowSeeking===false)document.getElementById('pbW').style.pointerEvents='none';
  if(s.showProgress===false)document.getElementById('pbW').style.display='none';
  // Control bar positioning
  var ctrls=document.getElementById('ctrls');
  var pos=s.controlPosition||'bottom';
  ctrls.style.top='';ctrls.style.bottom='';ctrls.style.left='';ctrls.style.right='';ctrls.style.width='';ctrls.style.padding='';ctrls.style.borderRadius='';ctrls.style.background='';
  if(pos==='top'){ctrls.style.top='0';ctrls.style.bottom='auto';ctrls.style.background='linear-gradient(rgba(0,0,0,0.9),transparent)';ctrls.style.padding='16px 20px 60px';}
  else if(pos==='topLeft'){ctrls.style.top='10px';ctrls.style.left='10px';ctrls.style.bottom='auto';ctrls.style.right='auto';ctrls.style.width='320px';ctrls.style.borderRadius='12px';ctrls.style.background='rgba(0,0,0,0.8)';ctrls.style.padding='12px';}
  else if(pos==='topRight'){ctrls.style.top='10px';ctrls.style.right='10px';ctrls.style.left='auto';ctrls.style.bottom='auto';ctrls.style.width='320px';ctrls.style.borderRadius='12px';ctrls.style.background='rgba(0,0,0,0.8)';ctrls.style.padding='12px';}
  else if(pos==='bottomLeft'){ctrls.style.bottom='10px';ctrls.style.left='10px';ctrls.style.right='auto';ctrls.style.top='auto';ctrls.style.width='320px';ctrls.style.borderRadius='12px';ctrls.style.background='rgba(0,0,0,0.8)';ctrls.style.padding='12px';}
  else if(pos==='bottomRight'){ctrls.style.bottom='10px';ctrls.style.right='10px';ctrls.style.left='auto';ctrls.style.top='auto';ctrls.style.width='320px';ctrls.style.borderRadius='12px';ctrls.style.background='rgba(0,0,0,0.8)';ctrls.style.padding='12px';}
  // Control mode
  if(s.controlMode==='custom'){
    document.getElementById('ctrls').classList.add('off');
    document.getElementById('bp').classList.add('hid'); // Hide big play in custom mode
    renderCustomControls(s);
  }
  // Overlay image
  applyOverlayImg(s.overlayImg,s);
  // Choice text
  if(s.choiceTitle)document.getElementById('chTitle').textContent=s.choiceTitle;
  if(s.choiceSubtitle)document.getElementById('chSub').textContent=s.choiceSubtitle;
  if(s.loadingBg){document.getElementById('ld').style.background=s.loadingBg;document.getElementById('pL').style.background=s.loadingBg;}
  if(s.loadingText){document.getElementById('ldT').textContent=s.loadingText;document.getElementById('plT').textContent=s.loadingText;}
  // Audio
  globalAudioSrc=s.audioSource||'video';
  if(s.bgmFile){BGM.src=s.bgmFile;BGM.volume=s.bgmVolume||0.5;}
  if(globalAudioSrc==='mute'){V.muted=true;}
  else if(globalAudioSrc==='bgm'){V.muted=true;}
}

function applyOverlayImg(img,s){
  var el=document.getElementById('ovImg');
  if(!img){el.style.display='none';return;}
  el.style.display='block';
  el.style.backgroundImage='url('+img+')';
  el.style.backgroundSize=s.overlayImgSize||'cover';
  el.style.backgroundPosition=s.overlayImgPos||'center';
  el.style.opacity=s.overlayImgOpacity||1;
}

function renderCustomControls(s){
  var container=document.getElementById('customCtrls');
  container.style.display='block';container.innerHTML='';
  var w=window.innerWidth;var vp='desktop';
  if(w<=480)vp='mobileP';else if(w<=900)vp='tablet';
  else if(w<=1100&&window.innerHeight<window.innerWidth)vp='mobileL';
  var positions=(s.ctrlPositions&&s.ctrlPositions[vp])||{};
  var defaults={play:{x:50,y:50,w:5,h:8},rewind:{x:40,y:50,w:4,h:7},forward:{x:60,y:50,w:4,h:7},mute:{x:8,y:92,w:3,h:5},fullscreen:{x:95,y:92,w:3,h:5},replay:{x:88,y:92,w:3,h:5}};
  var btns=[
    {key:'play',show:s.showPlayBtn!==false,action:function(){togglePlay();},icon:'bi-play-fill',iconAlt:'bi-pause-fill'},
    {key:'rewind',show:s.showRewindBtn!==false,action:function(){skip(-10);},icon:'bi-skip-backward-fill'},
    {key:'forward',show:s.showForwardBtn!==false,action:function(){skip(10);},icon:'bi-skip-forward-fill'},
    {key:'mute',show:s.showMuteBtn!==false,action:function(){toggleMute();},icon:'bi-volume-up-fill'},
    {key:'fullscreen',show:s.showFsBtn!==false,action:function(){toggleFS();},icon:'bi-fullscreen'},
    {key:'replay',show:s.showReplayBtn!==false,action:function(){restartAll();},icon:'bi-arrow-counterclockwise'}
  ];
  btns.forEach(function(b){
    if(!b.show)return;
    var p=positions[b.key]||defaults[b.key]||{x:50,y:50,w:5,h:8};
    var el=document.createElement('div');
    el.id='cc_'+b.key;
    el.style.cssText='position:absolute;pointer-events:auto;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:transform 0.15s,opacity 0.4s;border-radius:50%;'
      +'left:'+p.x+'%;top:'+p.y+'%;width:'+p.w+'%;height:'+p.h+'%;transform:translate(-50%,-50%);';
    var customIcon=s[b.key+'Icon'];
    if(customIcon){
      el.innerHTML='<img src="'+customIcon+'" style="width:80%;height:80%;object-fit:contain">';
    }else{
      el.innerHTML='<i class="'+b.icon+'" style="font-size:clamp(16px,3vw,32px);color:#fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,0.6))"></i>';
    }
    el.onmouseenter=function(){el.style.transform='translate(-50%,-50%) scale(1.15)';};
    el.onmouseleave=function(){el.style.transform='translate(-50%,-50%)';};
    el.onclick=b.action;
    container.appendChild(el);
  });
  // Progress bar if enabled
  if(s.showProgress!==false){
    var pb=document.createElement('div');
    pb.style.cssText='position:absolute;bottom:3%;left:5%;width:90%;height:4px;background:rgba(255,255,255,0.2);border-radius:2px;pointer-events:auto;cursor:pointer;z-index:2';
    pb.innerHTML='<div id="ccPbFill" style="height:100%;background:var(--accent);border-radius:2px;width:0%;transition:width 0.1s"></div>';
    pb.onclick=function(e){var r=pb.getBoundingClientRect();V.currentTime=Math.max(0,Math.min(1,(e.clientX-r.left)/r.width))*(V.duration||0);};
    container.appendChild(pb);
  }
}

// Update custom control play icon state
function updCustomCtrl(){
  var el=document.getElementById('cc_play');
  if(!el)return;
  var s=PD&&PD.project?PD.project.settings:{};
  var img=el.querySelector('img');
  var icon=el.querySelector('i');
  if(img){
    if(playing&&s.pauseIcon){img.src=s.pauseIcon;}
    else if(s.playIcon){img.src=s.playIcon;}
  }else if(icon){
    icon.className=playing?'bi bi-pause-fill':'bi bi-play-fill';
  }
  // Update custom progress
  var pb=document.getElementById('ccPbFill');
  if(pb&&V.duration)pb.style.width=(V.currentTime/V.duration*100)+'%';
}

// ===== LOAD AND PLAY =====
function loadAndPlay(videoId,onStarted,skipLoading){
  var v=vidMap[videoId];if(!v)return;
  curVid=videoId;storyEnded=false;
  hideCh();closePopup();clearInts();
  if(!skipLoading)showLd(true);
  V.oncanplay=null;V.onended=null;
  V.style.transition='none';V.style.transform='';V.style.opacity='1';V.style.filter='';
  V.style.display='block';
  var fired=false;
  V.oncanplay=function(){
    if(fired)return;fired=true;
    showLd(false);
    var playPromise=V.play();
    if(playPromise&&playPromise.then){
      playPromise.then(function(){
        playing=true;updPI();hideBP();
        if(onStarted)onStarted();
      }).catch(function(e){
        playing=false;updPI();showBP();
        if(onStarted)onStarted();
      });
    }else{
      playing=true;updPI();hideBP();
      if(onStarted)onStarted();
    }
  };
  V.onerror=function(){showLd(false);showBP();};
  var videoSrc=v.blobUrl||v.src||'';
  V.src=videoSrc;
  V.load();
  // Handle audio for this node
  var nodeAudio=v.audioMode||'';
  var audioMode=nodeAudio||globalAudioSrc;
  var shouldLoop=v.audioLoop!==false;
  if(audioMode==='mute'){V.muted=true;BGM.pause();}
  else if(audioMode==='bgm'){V.muted=true;BGM.loop=true;if(BGM.src)BGM.play().catch(function(){});}
  else if(audioMode==='both'){V.muted=false;BGM.loop=true;if(BGM.src)BGM.play().catch(function(){});}
  else if(audioMode==='custom'&&v.audioFile){V.muted=true;BGM.src=v.audioFile;BGM.loop=shouldLoop;BGM.play().catch(function(){});}
  else{V.muted=false;BGM.pause();}
  // Per-node overlay image
  if(v.overlayImg){
    applyOverlayImg(v.overlayImg,PD.project.settings||{});
  }else if(PD&&PD.project&&PD.project.settings&&PD.project.settings.overlayImg){
    applyOverlayImg(PD.project.settings.overlayImg,PD.project.settings);
  }else{
    document.getElementById('ovImg').style.display='none';
  }
  setupInts(videoId);
  setupEnd(videoId);
}

// ===== TRANSITIONS =====
function transitionTo(videoId,trType){
  var tr=trType||getGlobalTr();
  var dur=getGlobalDur();
  if(tr==='cut'){loadAndPlay(videoId,null,true);return;}
  if(tr==='fade'||tr==='fadeWhite'){
    TOV.style.background=tr==='fadeWhite'?'#fff':'#000';
    TOV.style.transition='opacity '+(dur/2)+'ms ease';TOV.style.opacity='1';
    setTimeout(function(){loadAndPlay(videoId,function(){TOV.style.transition='opacity '+(dur/2)+'ms ease';TOV.style.opacity='0';},true);},dur/2);
  }else if(tr==='dissolve'){
    V.style.transition='opacity '+dur+'ms ease';V.style.opacity='0';
    setTimeout(function(){loadAndPlay(videoId,function(){V.style.opacity='0';V.style.transition='none';setTimeout(function(){V.style.transition='opacity '+dur+'ms ease';V.style.opacity='1';},20);},true);},dur/2);
  }else if(tr==='slideLeft'||tr==='slideRight'){
    var d1=tr==='slideLeft'?'-100%':'100%';var d2=tr==='slideLeft'?'100%':'-100%';
    V.style.transition='transform '+(dur/2)+'ms ease-in';V.style.transform='translateX('+d1+')';
    setTimeout(function(){V.style.transition='none';V.style.transform='translateX('+d2+')';loadAndPlay(videoId,function(){setTimeout(function(){V.style.transition='transform '+(dur/2)+'ms ease-out';V.style.transform='translateX(0)';},20);},true);},dur/2);
  }else if(tr==='zoomIn'){
    V.style.transition='transform '+(dur/2)+'ms ease, opacity '+(dur/2)+'ms ease';V.style.transform='scale(2)';V.style.opacity='0';
    setTimeout(function(){V.style.transition='none';V.style.transform='scale(0.5)';V.style.opacity='0';loadAndPlay(videoId,function(){setTimeout(function(){V.style.transition='transform '+(dur/2)+'ms ease, opacity '+(dur/2)+'ms ease';V.style.transform='scale(1)';V.style.opacity='1';},20);},true);},dur/2);
  }else if(tr==='blur'){
    V.style.transition='filter '+(dur/2)+'ms ease, opacity '+(dur/2)+'ms ease';V.style.filter='blur(20px)';V.style.opacity='0';
    setTimeout(function(){V.style.transition='none';V.style.filter='blur(20px)';V.style.opacity='0';loadAndPlay(videoId,function(){setTimeout(function(){V.style.transition='filter '+(dur/2)+'ms ease, opacity '+(dur/2)+'ms ease';V.style.filter='none';V.style.opacity='1';},20);},true);},dur/2);
  }else{loadAndPlay(videoId,null,true);}
}

function getGlobalTr(){return PD&&PD.project&&PD.project.settings?PD.project.settings.transition||'cut':'cut';}
function getGlobalDur(){return PD&&PD.project&&PD.project.settings?PD.project.settings.transitionDuration||600:600;}

// ===== END OF VIDEO =====
function setupEnd(videoId){
  if(!PD||!PD.connections){V.onended=function(){handleEnd();};return;}
  var direct=PD.connections.filter(function(c){return c.fromVideoId===videoId&&c.isDirectFlow;});
  var v=vidMap[videoId];
  var nodeBeh=v?v.endBehavior||'':'';
  // Per-node behavior overrides
  if(nodeBeh==='replay'){
    V.onended=function(){V.currentTime=0;V.play();playing=true;updPI();};return;
  }
  if(nodeBeh==='pause'){
    V.onended=function(){playing=false;storyEnded=true;updPI();showBP();};return;
  }
  if(nodeBeh==='choices'){
    // Show all connections (direct + interaction) as choices
    var allConns=PD.connections.filter(function(c){return c.fromVideoId===videoId;});
    if(allConns.length){
      V.onended=function(){playing=false;updPI();showChoices(allConns);};return;
    }
  }
  // Default flow logic
  if(direct.length===1){
    V.onended=function(){playing=false;updPI();transitionTo(direct[0].toVideoId,direct[0].transition||'');};
  }else if(direct.length>1){
    V.onended=function(){playing=false;updPI();showChoices(direct);};
  }else{
    V.onended=function(){handleEnd();};
  }
}

function handleEnd(){
  var v=curVid?vidMap[curVid]:null;
  var endTr=v?v.endTransition||'':'';
  var dur=getGlobalDur();
  var nodeBeh=v?v.endBehavior||'':'';
  var beh=nodeBeh||((PD&&PD.project&&PD.project.settings)?PD.project.settings.endBehavior||'pause':'pause');
  var doFinal=function(){
    if(beh==='loop'||beh==='replay'){
      V.style.transition='none';V.style.transform='';V.style.opacity='1';V.style.filter='';TOV.style.opacity='0';
      V.currentTime=0;V.play();playing=true;updPI();
    }else if(beh==='restart'&&PD&&PD.project&&PD.project.startVideoId){
      V.style.transition='none';V.style.transform='';V.style.opacity='1';V.style.filter='';TOV.style.opacity='0';
      transitionTo(PD.project.startVideoId);
    }else{
      playing=false;storyEnded=true;updPI();showBP();
    }
  };
  if(endTr&&endTr!=='cut'){
    if(endTr==='fade'||endTr==='fadeWhite'){
      TOV.style.background=endTr==='fadeWhite'?'#fff':'#000';
      TOV.style.transition='opacity '+dur+'ms ease';TOV.style.opacity='1';
      setTimeout(doFinal,dur);
    }else if(endTr==='blur'){
      V.style.transition='filter '+dur+'ms ease';V.style.filter='blur(20px)';
      setTimeout(doFinal,dur);
    }else if(endTr==='zoomIn'){
      V.style.transition='transform '+dur+'ms ease, opacity '+dur+'ms ease';V.style.transform='scale(1.5)';V.style.opacity='0';
      setTimeout(doFinal,dur);
    }else{doFinal();}
  }else{doFinal();}
}

// ===== INTERACTIONS =====
function getIntPos(intr){
  // Pick per-viewport position based on window width
  var w=window.innerWidth;var vp='desktop';
  if(w<=480)vp='mobileP';
  else if(w<=900)vp='tablet';
  else if(w<=1100&&window.innerHeight<window.innerWidth)vp='mobileL';
  if(intr.vpPos&&intr.vpPos[vp])return intr.vpPos[vp];
  return{x:intr.x,y:intr.y,w:intr.w,h:intr.h};
}

function setupInts(vid){
  clearInts();if(!PD||!PD.interactions)return;
  var ints=PD.interactions.filter(function(i){return i.videoId===vid;});
  var layer=document.getElementById('ovL');
  ints.forEach(function(intr){
    var el=document.createElement('div');
    el.className='int-el type-'+(intr.type||'button');
    var s=intr.style||{};
    var pos=getIntPos(intr);
    var bg=s.transparent?'transparent':(s.backgroundColor||'#4a9eff');
    var bw=s.borderWidth||0;
    var bc=s.borderColor||'#fff';
    var br=s.borderRadius||8;
    var shadow='';
    if(s.shadow==='sm')shadow='0 2px 8px rgba(0,0,0,0.3)';
    else if(s.shadow==='md')shadow='0 4px 20px rgba(0,0,0,0.5)';
    else if(s.shadow==='lg')shadow='0 8px 40px rgba(0,0,0,0.6)';
    else if(s.shadow==='glow')shadow='0 0 20px '+(s.backgroundColor||'#4a9eff');
    el.style.cssText='left:'+pos.x+'%;top:'+pos.y+'%;width:'+pos.w+'%;height:'+pos.h+'%;transform:translate(-50%,-50%);background-color:'+bg+';color:'+(s.textColor||'#fff')+';border-radius:'+br+'px;'+(bw?'border:'+bw+'px solid '+bc+';':'')+(shadow?'box-shadow:'+shadow+';':'');
    // Add attention animation class if set (loops infinitely)
    if(s.animateAttention){
      el.classList.add('animate__animated','animate__'+s.animateAttention,'animate__infinite');
    }
    var h='';
    var hasImg=s.image&&s.image.length>10;
    if(hasImg){
      h+='<img class="ii" src="'+s.image+'">';
      // If image exists, don't show text label — image IS the button
    }else{
      h+='<div class="ic">';
      if(s.icon)h+='<i class="'+s.icon+'"></i>';
      if(intr.label)h+='<span>'+intr.label+'</span>';
      h+='</div>';
    }
    el.innerHTML=h;
    el.addEventListener('click',function(){fireInt(intr);});
    layer.appendChild(el);
    aInts.push({el:el,data:intr});
  });
}
function clearInts(){document.getElementById('ovL').innerHTML='';aInts=[];var ms=document.querySelectorAll('.pm');ms.forEach(function(m){m.remove();});}

// Reposition interactions on window resize (viewport change)
var resizeTm=null;
window.addEventListener('resize',function(){
  clearTimeout(resizeTm);
  resizeTm=setTimeout(function(){
    aInts.forEach(function(o){
      var pos=getIntPos(o.data);
      o.el.style.left=pos.x+'%';o.el.style.top=pos.y+'%';o.el.style.width=pos.w+'%';o.el.style.height=pos.h+'%';
    });
    // Re-render custom controls for new viewport
    if(PD&&PD.project&&PD.project.settings&&PD.project.settings.controlMode==='custom'){
      renderCustomControls(PD.project.settings);
    }
  },150);
});
function updInts(){var t=V.currentTime;var dur=V.duration||9999;var isPausedAtEnd=V.paused&&t>=dur-0.5;
aInts.forEach(function(o){
  var shouldShow=(t>=o.data.startTime&&t<=o.data.endTime)||(isPausedAtEnd&&o.data.endTime>=dur-0.5);
  var isShown=o.el.classList.contains('vis');
  var s=o.data.style||{};
  if(shouldShow&&!isShown){
    o.el.classList.add('vis');
    if(o._exitAnim){o.el.classList.remove('animate__animated','animate__'+o._exitAnim);o._exitAnim=null;}
    if(s.animateIn){o.el.classList.add('animate__animated','animate__'+s.animateIn);}
    // Re-add attention animation after entrance
    if(s.animateAttention){
      setTimeout(function(){
        if(s.animateIn)o.el.classList.remove('animate__'+s.animateIn);
        o.el.classList.add('animate__animated','animate__'+s.animateAttention,'animate__infinite');
      },1100);
    }
  }else if(!shouldShow&&isShown&&!isPausedAtEnd){
    if(s.animateOut){
      o._exitAnim=s.animateOut;
      o.el.classList.remove('animate__infinite');
      if(s.animateAttention)o.el.classList.remove('animate__'+s.animateAttention);
      if(s.animateIn)o.el.classList.remove('animate__'+s.animateIn);
      o.el.classList.add('animate__animated','animate__'+s.animateOut);
      setTimeout(function(){o.el.classList.remove('vis','animate__animated','animate__'+s.animateOut);o._exitAnim=null;},1000);
    }else{
      o.el.classList.remove('vis');
      if(s.animateIn)o.el.classList.remove('animate__animated','animate__'+s.animateIn);
      if(s.animateAttention)o.el.classList.remove('animate__'+s.animateAttention,'animate__infinite');
    }
  }
});}
function fireInt(intr){
  var a=intr.action;if(!a)return;
  if(a.pauseVideo){V.pause();playing=false;updPI();}
  if(a.type==='jumpToVideo'&&a.targetVideoId&&vidMap[a.targetVideoId]){
    var conn=null;if(PD.connections)conn=PD.connections.find(function(c){return c.fromPortId===intr.id;});
    transitionTo(a.targetVideoId,conn?conn.transition||'':'');
  }else if(a.type==='pause'){V.pause();playing=false;updPI();}
  else if(a.type==='openUrl'&&a.value){window.open(a.value,'_blank');}
  else if(a.type==='showOverlay'){showPopup(a.value||'Info');}
}

// ===== CHOICES =====
function showChoices(conns){
  var bc=document.getElementById('chBtns');bc.innerHTML='';
  conns.forEach(function(conn){
    var b=document.createElement('button');b.className='ch-btn';
    if(conn.color)b.style.setProperty('--accent',conn.color);
    var lbl=conn.label;
    if(!lbl||!lbl.trim()){var tgt=vidMap[conn.toVideoId];lbl=tgt?tgt.name:'Continue';if(lbl.length>30)lbl=lbl.substring(0,28)+'...';}
    b.textContent=lbl;
    b.addEventListener('click',function(){hideCh();transitionTo(conn.toVideoId,conn.transition||'');});
    bc.appendChild(b);
  });
  document.getElementById('chOv').classList.add('active');
}
function hideCh(){document.getElementById('chOv').classList.remove('active');}
function showPopup(t){document.getElementById('popTxt').textContent=t;document.getElementById('popup').classList.add('active');}
function closePopup(){document.getElementById('popup').classList.remove('active');}

// ===== CONTROLS =====
function togglePlay(){
  // If story has ended, restart from start node
  if(storyEnded){
    storyEnded=false;
    TOV.style.opacity='0';V.style.transition='none';V.style.transform='';V.style.opacity='1';V.style.filter='';
    if(PD&&PD.project&&PD.project.startVideoId){
      transitionTo(PD.project.startVideoId);
    }else{
      V.currentTime=0;V.play();playing=true;updPI();hideBP();
    }
    return;
  }
  if(V.paused){
    V.play();playing=true;hideBP();
    if(parseFloat(TOV.style.opacity)>0){TOV.style.transition='opacity '+(getGlobalDur())+'ms ease';TOV.style.opacity='0';}
  }else{V.pause();playing=false;}
  updPI();
}

function updPI(){
  var el=document.getElementById('pIc');
  if(el){
    if(el.getAttribute('data-custom')==='1'){
      if(playing&&pauseIconSrc){el.src=pauseIconSrc;}
      else if(!playing&&PD&&PD.project&&PD.project.settings&&PD.project.settings.playIcon){el.src=PD.project.settings.playIcon;}
    }else if(el.tagName==='I'){
      el.className=playing?'bi bi-pause-fill':'bi bi-play-fill';
    }
  }
  updCustomCtrl();
}

function showBP(){
  // Don't show big play button if custom controls mode is active (play button is in the custom layer)
  if(PD&&PD.project&&PD.project.settings&&PD.project.settings.controlMode==='custom'){
    // Just make sure custom controls are visible
    var cc=document.getElementById('customCtrls');
    if(cc)cc.style.opacity='1';
    return;
  }
  var bp=document.getElementById('bp');
  bp.classList.remove('hid');
  if(storyEnded&&PD&&PD.project&&PD.project.settings&&PD.project.settings.replayIcon){
    bp.innerHTML='<img src="'+PD.project.settings.replayIcon+'" style="width:40px;height:40px;object-fit:contain">';
  }else{
    bp.innerHTML='<i class="bi bi-play-fill"></i>';
  }
}
function hideBP(){document.getElementById('bp').classList.add('hid');}
function skip(s){V.currentTime=Math.max(0,Math.min(V.duration||0,V.currentTime+s));}
function toggleMute(){V.muted=!V.muted;BGM.muted=V.muted;document.getElementById('vIc').className=V.muted?'bi bi-volume-mute-fill':'bi bi-volume-up-fill';document.getElementById('vS').value=V.muted?0:V.volume;}
function setVol(v){V.volume=v;V.muted=v==0;BGM.muted=v==0;document.getElementById('vIc').className=v==0?'bi bi-volume-mute-fill':v<0.5?'bi bi-volume-down-fill':'bi bi-volume-up-fill';}
function toggleFS(){var w=document.getElementById('pw');if(!document.fullscreenElement){(w.requestFullscreen||w.webkitRequestFullscreen||w.msRequestFullscreen).call(w);document.getElementById('fsI').className='bi bi-fullscreen-exit';}else{document.exitFullscreen();document.getElementById('fsI').className='bi bi-fullscreen';}}
function restartAll(){storyEnded=false;TOV.style.opacity='0';V.style.transition='none';V.style.transform='';V.style.opacity='1';V.style.filter='';if(PD&&PD.project&&PD.project.startVideoId)transitionTo(PD.project.startVideoId);else{V.currentTime=0;V.play();playing=true;updPI();hideBP();}}

// ===== PROGRESS =====
document.getElementById('pbW').addEventListener('mousedown',function(e){
  var sk=function(ev){var r=document.getElementById('pbT').getBoundingClientRect();V.currentTime=Math.max(0,Math.min(1,(ev.clientX-r.left)/r.width))*(V.duration||0);};
  sk(e);var onM=function(ev){sk(ev);};var onU=function(){document.removeEventListener('mousemove',onM);document.removeEventListener('mouseup',onU);};
  document.addEventListener('mousemove',onM);document.addEventListener('mouseup',onU);
});

V.addEventListener('timeupdate',function(){if(!V.duration)return;document.getElementById('pbF').style.width=(V.currentTime/V.duration*100)+'%';document.getElementById('cT').textContent=fmt(V.currentTime);document.getElementById('tT').textContent=fmt(V.duration);updInts();updCustomCtrl();});
V.addEventListener('progress',function(){if(V.buffered.length)document.getElementById('pbB').style.width=(V.buffered.end(V.buffered.length-1)/V.duration*100)+'%';});
V.addEventListener('loadedmetadata',function(){
  document.getElementById('tT').textContent=fmt(V.duration);
  if(curVid&&PD&&PD.interactions){
    var tk=document.getElementById('pbT');
    PD.interactions.filter(function(i){return i.videoId===curVid;}).forEach(function(intr){
      var m=document.createElement('div');m.className='pm type-'+(intr.type||'button');m.style.left=((intr.startTime/V.duration)*100)+'%';m.title=intr.label||intr.type;tk.appendChild(m);
    });
  }
});

// ===== AUTO HIDE =====
function resetCtrl(){var c=document.getElementById('ctrls'),w=document.getElementById('pw'),cc=document.getElementById('customCtrls');c.classList.remove('hidden');w.classList.remove('hide-cur');if(cc)cc.style.opacity='1';clearTimeout(ctrlTm);if(PD&&PD.project&&PD.project.settings&&PD.project.settings.autoHide!==false&&playing){ctrlTm=setTimeout(function(){c.classList.add('hidden');w.classList.add('hide-cur');if(cc)cc.style.opacity='0';},3000);}}
document.getElementById('pw').addEventListener('mousemove',resetCtrl);
document.getElementById('pw').addEventListener('click',function(e){if(e.target===V)togglePlay();resetCtrl();});

function showLd(s){var el=document.getElementById('ld');el.classList.toggle('off',!s);}

// ===== KEYBOARD =====
document.addEventListener('keydown',function(e){
  if(e.key===' '||e.key==='k'){e.preventDefault();togglePlay();}
  else if(e.key==='ArrowLeft'){skip(-5);}
  else if(e.key==='ArrowRight'){skip(5);}
  else if(e.key==='ArrowUp'){e.preventDefault();setVol(Math.min(1,V.volume+0.1));document.getElementById('vS').value=V.volume;}
  else if(e.key==='ArrowDown'){e.preventDefault();setVol(Math.max(0,V.volume-0.1));document.getElementById('vS').value=V.volume;}
  else if(e.key==='f'){toggleFS();}
  else if(e.key==='m'){toggleMute();}
});

function fmt(s){if(!s||isNaN(s))return '0:00';var m=Math.floor(s/60),sc=Math.floor(s%60);return m+':'+(sc<10?'0':'')+sc;}
function loadFile(f){if(!f)return;var r=new FileReader();r.onload=function(e){try{loadProject(JSON.parse(e.target.result));}catch(err){alert('Invalid JSON: '+err.message);}};r.readAsText(f);}

// URL param
var srcP=new URLSearchParams(location.search).get('src');
if(srcP){document.getElementById('np').style.display='none';document.getElementById('pL').classList.add('active');fetch(srcP).then(function(r){return r.json();}).then(function(d){document.getElementById('pL').classList.remove('active');loadProject(d);}).catch(function(){document.getElementById('pL').classList.remove('active');document.getElementById('np').style.display='flex';});}

// postMessage
window.addEventListener('message',function(e){
  if(e.data&&e.data.type==='loadProject'&&e.data.data){document.getElementById('np').style.display='none';loadProject(e.data.data);}
  var m=e.data;if(!m||m.type==='loadProject')return;
  if(m.type==='play'){V.play();playing=true;updPI();}
  else if(m.type==='pause'){V.pause();playing=false;updPI();}
  else if(m.type==='seek'&&m.time!==undefined){V.currentTime=m.time;}
  else if(m.type==='jumpToVideo'&&m.videoId){transitionTo(m.videoId);}
  else if(m.type==='setVolume'&&m.volume!==undefined){setVol(m.volume);}
});
function notifyP(t){window.parent.postMessage({type:t},'*');}
V.addEventListener('play',function(){notifyP('play');});
V.addEventListener('pause',function(){notifyP('pause');updInts();});
V.addEventListener('ended',function(){notifyP('ended');updInts();});
</script>
</body>
</html>