Run 2: layout restructure and pop-out overlay

- Bottom bar: two-row layout — ticker above centered controls
- Ticker speed halved (0.5px/frame) and longer bottom bar (76px)
- Pop-out overlay: shows "PLAYING IN MINI PLAYER" when popup is open
- Pop-out re-press: focuses existing popup window instead of opening a new one
- Clear overlay and popoutWin ref when video returns from popup

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
Aaron
2026-05-23 11:13:07 -04:00
parent 04fffb11d6
commit 7318d7ed7f
+39 -16
View File
@@ -17,7 +17,7 @@
--accent:#ff2200;--accent2:#00d4ff;--text:#f0f0ff;--dim:#6668a0; --accent:#ff2200;--accent2:#00d4ff;--text:#f0f0ff;--dim:#6668a0;
--font-d:'Bebas Neue',Impact,'Arial Black',sans-serif; --font-d:'Bebas Neue',Impact,'Arial Black',sans-serif;
--font-b:'Rajdhani',sans-serif; --font-b:'Rajdhani',sans-serif;
--top-h:52px;--bot-h:58px; --top-h:52px;--bot-h:76px;
} }
html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);font-family:var(--font-b);color:var(--text);user-select:none;-webkit-tap-highlight-color:transparent} html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);font-family:var(--font-b);color:var(--text);user-select:none;-webkit-tap-highlight-color:transparent}
@@ -51,6 +51,11 @@ html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);font-famil
/* Player */ /* Player */
#pw{position:absolute;inset:0;z-index:0;overflow:hidden;background:#000} #pw{position:absolute;inset:0;z-index:0;overflow:hidden;background:#000}
#pw-cover{position:absolute;inset:0;z-index:1;background:transparent} #pw-cover{position:absolute;inset:0;z-index:1;background:transparent}
#po-overlay{position:absolute;inset:0;z-index:3;display:none;align-items:center;justify-content:center;background:rgba(6,6,15,.88)}
.po-msg{text-align:center}
.po-icon{font-size:52px;margin-bottom:12px;opacity:.7}
.po-title{font-family:var(--font-d);font-size:28px;letter-spacing:4px;margin-bottom:8px}
.po-sub{font-size:12px;color:var(--dim);letter-spacing:1px}
#pw iframe{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:none;pointer-events:none} #pw iframe{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:none;pointer-events:none}
/* Overlays */ /* Overlays */
@@ -112,13 +117,15 @@ html,body{width:100%;height:100%;overflow:hidden;background:var(--bg);font-famil
#pb-thumb{position:absolute;right:-5px;top:-3px;width:10px;height:10px;background:#fff;border-radius:50%;opacity:0;transition:opacity .15s;pointer-events:none} #pb-thumb{position:absolute;right:-5px;top:-3px;width:10px;height:10px;background:#fff;border-radius:50%;opacity:0;transition:opacity .15s;pointer-events:none}
/* Bottom bar */ /* Bottom bar */
#bb{position:absolute;bottom:0;left:0;right:0;height:var(--bot-h);z-index:5;display:flex;align-items:center;padding:0 14px;gap:12px} #bb{position:absolute;bottom:0;left:0;right:0;height:var(--bot-h);z-index:5;display:flex;flex-direction:column;justify-content:center;align-items:stretch;padding:5px 14px;gap:2px}
.bb-top{display:flex;align-items:center;gap:8px;overflow:hidden}
.bb-bot{display:flex;justify-content:center;align-items:center}
.np-lbl{font-family:var(--font-d);font-size:12px;letter-spacing:2.5px;color:var(--accent);flex-shrink:0;display:flex;align-items:center;gap:5px;white-space:nowrap} .np-lbl{font-family:var(--font-d);font-size:12px;letter-spacing:2.5px;color:var(--accent);flex-shrink:0;display:flex;align-items:center;gap:5px;white-space:nowrap}
.np-lbl-dot{width:5px;height:5px;background:var(--accent);border-radius:50%;animation:dot-p 1s ease-in-out infinite} .np-lbl-dot{width:5px;height:5px;background:var(--accent);border-radius:50%;animation:dot-p 1s ease-in-out infinite}
.np-wrap{flex:1;overflow:hidden;height:100%;display:flex;align-items:center;position:relative} .np-wrap{flex:1;overflow:hidden;height:100%;display:flex;align-items:center;position:relative}
#np-ticker{white-space:nowrap;font-size:15px;font-weight:600;letter-spacing:.4px;will-change:transform;position:absolute;left:0;top:50%;transform-origin:left center;margin-top:-10px} #np-ticker{white-space:nowrap;font-size:15px;font-weight:600;letter-spacing:.4px;will-change:transform;position:absolute;left:0;top:50%;transform-origin:left center;margin-top:-10px}
.np-art{color:var(--accent2)} .np-art{color:var(--accent2)}
.ctrls{display:flex;align-items:center;gap:3px;flex-shrink:0} .ctrls{display:flex;align-items:center;gap:3px}
.c-btn{background:rgba(255,255,255,.06);border:1px solid var(--border);color:var(--dim);cursor:pointer;width:34px;height:34px;display:flex;align-items:center;justify-content:center;font-size:15px;transition:.15s} .c-btn{background:rgba(255,255,255,.06);border:1px solid var(--border);color:var(--dim);cursor:pointer;width:34px;height:34px;display:flex;align-items:center;justify-content:center;font-size:15px;transition:.15s}
.c-btn:hover{background:rgba(255,255,255,.12);color:var(--text)} .c-btn:hover{background:rgba(255,255,255,.12);color:var(--text)}
.c-btn.act{background:rgba(0,212,255,.13);border-color:var(--accent2);color:var(--accent2)} .c-btn.act{background:rgba(0,212,255,.13);border-color:var(--accent2);color:var(--accent2)}
@@ -274,6 +281,13 @@ body.idle .gt,body.idle .gb,body.idle .gr,body.idle .gl{opacity:0;transition:opa
<!-- ── APP ── --> <!-- ── APP ── -->
<div id="app"> <div id="app">
<div id="pw"><div id="yt-player"></div><div id="pw-cover"></div></div> <div id="pw"><div id="yt-player"></div><div id="pw-cover"></div></div>
<div id="po-overlay">
<div class="po-msg">
<div class="po-icon">&#10697;</div>
<div class="po-title">PLAYING IN MINI PLAYER</div>
<div class="po-sub">Press P to bring it back into focus</div>
</div>
</div>
<div class="gt"></div> <div class="gt"></div>
<div class="gb"></div> <div class="gb"></div>
<div class="gr"></div> <div class="gr"></div>
@@ -321,18 +335,22 @@ body.idle .gt,body.idle .gb,body.idle .gr,body.idle .gl{opacity:0;transition:opa
</div> </div>
<div id="bb"> <div id="bb">
<div class="np-lbl"><span class="np-lbl-dot"></span><span>NOW PLAYING</span></div> <div class="bb-top">
<div class="np-wrap"> <div class="np-lbl"><span class="np-lbl-dot"></span><span>NOW PLAYING</span></div>
<div id="np-ticker"> <div class="np-wrap">
<span class="np-art" id="npa"></span><span id="npt">Select a channel above to begin</span> <div id="np-ticker">
<span class="np-art" id="npa"></span><span id="npt">Select a channel above to begin</span>
</div>
</div> </div>
</div> </div>
<div class="ctrls"> <div class="bb-bot">
<button class="c-btn" id="b-prev" title="Previous (&#8592;)">&#9194;</button> <div class="ctrls">
<button class="c-btn" id="b-play" title="Play / Pause (Space)">&#9654;</button> <button class="c-btn" id="b-prev" title="Previous (&#8592;)">&#9194;</button>
<button class="c-btn" id="b-next" title="Next (&#8594;)">&#9193;</button> <button class="c-btn" id="b-play" title="Play / Pause (Space)">&#9654;</button>
<button class="c-btn" id="b-shuf" title="Shuffle (S)">&#8652;</button> <button class="c-btn" id="b-next" title="Next (&#8594;)">&#9193;</button>
<button class="c-btn" id="b-sb" title="Channels (C)">&#9776;</button> <button class="c-btn" id="b-shuf" title="Shuffle (S)">&#8652;</button>
<button class="c-btn" id="b-sb" title="Channels (C)">&#9776;</button>
</div>
</div> </div>
</div> </div>
@@ -364,7 +382,8 @@ const A = {
npTimer:null, pbTimer:null, npTimer:null, pbTimer:null,
tickerRaf:null, tickerPos:9999, tcTimer:null, idleTimer:null, tickerRaf:null, tickerPos:9999, tcTimer:null, idleTimer:null,
tab:'channels', tab:'channels',
lastActivity:Date.now(), isIdle:false lastActivity:Date.now(), isIdle:false,
popoutWin:null
}; };
const FD = "'Bebas Neue',Impact,'Arial Black',sans-serif"; const FD = "'Bebas Neue',Impact,'Arial Black',sans-serif";
const DEBUG = false; const DEBUG = false;
@@ -525,7 +544,7 @@ function showTitleCard(artist, title){
function tickerLoop(){ function tickerLoop(){
const el=qs('#np-ticker'), wrap=qs('.np-wrap'); const el=qs('#np-ticker'), wrap=qs('.np-wrap');
if(!el||!wrap){ A.tickerRaf=requestAnimationFrame(tickerLoop); return; } if(!el||!wrap){ A.tickerRaf=requestAnimationFrame(tickerLoop); return; }
A.tickerPos -= 1.0; A.tickerPos -= 0.5;
if(A.tickerPos < -(el.offsetWidth+20)) A.tickerPos = wrap.offsetWidth+20; if(A.tickerPos < -(el.offsetWidth+20)) A.tickerPos = wrap.offsetWidth+20;
el.style.transform=`translateX(${A.tickerPos}px) translateY(-50%)`; el.style.transform=`translateX(${A.tickerPos}px) translateY(-50%)`;
el.style.top='50%'; el.style.top='50%';
@@ -959,6 +978,7 @@ setInterval(()=>{
// ── Pop-out transfer ── // ── Pop-out transfer ──
function popOut(){ function popOut(){
if(A.popoutWin&&!A.popoutWin.closed){ A.popoutWin.focus(); return; }
try{ try{
const data=A.player?.getVideoData?.(); const data=A.player?.getVideoData?.();
const time=A.player?.getCurrentTime?.(); const time=A.player?.getCurrentTime?.();
@@ -967,7 +987,8 @@ function popOut(){
A.player.pauseVideo(); A.player.pauseVideo();
} }
}catch(e){} }catch(e){}
window.open(location.href,'vidflow-mini','popup,width=480,height=270'); A.popoutWin=window.open(location.href,'vidflow-mini','popup,width=480,height=270');
qs('#po-overlay').style.display='flex';
} }
// Popup → on close, write current position back so main window can resume // Popup → on close, write current position back so main window can resume
@@ -991,6 +1012,8 @@ if(!window.opener){
const t=JSON.parse(e.newValue||'null'); const t=JSON.parse(e.newValue||'null');
if(t?.videoId&&t?.from==='popout'&&A.ready){ if(t?.videoId&&t?.from==='popout'&&A.ready){
localStorage.removeItem('vf_transfer'); localStorage.removeItem('vf_transfer');
qs('#po-overlay').style.display='none';
A.popoutWin=null;
A.player?.loadVideoById?.({videoId:t.videoId,startSeconds:t.startSeconds||0}); A.player?.loadVideoById?.({videoId:t.videoId,startSeconds:t.startSeconds||0});
} }
}catch(err){} }catch(err){}