| Current Path : /var/www/html/unity/development/interactive-videoplayer/ |
| 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>