Your IP : 216.73.216.79


Current Path : /var/www/html/unity/development/multilayer-video-player/
Upload File :
Current File : /var/www/html/unity/development/multilayer-video-player/admin.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AI Emcee · Control Desk</title>

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/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=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">

<style>
:root{
  --void:#0a0b0d; --panel:#14161a; --panel-2:#1c2026; --panel-3:#242a32;
  --ink:#e9edf2; --muted:#818b98; --line:#2a2f37;
  --live:#ff5a3c; --standby:#35d0d8;
  --disp:'Space Grotesk',system-ui,sans-serif; --body:'Inter',system-ui,sans-serif; --mono:'Space Mono',ui-monospace,monospace;
}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;background:
  radial-gradient(1100px 500px at 85% -10%,rgba(53,208,216,.06),transparent 60%),
  radial-gradient(900px 500px at 0% 110%,rgba(255,90,60,.05),transparent 55%),
  var(--void);color:var(--ink);font-family:var(--body)}
a{color:var(--standby)}
kbd{background:var(--panel-3);border:1px solid var(--line);border-bottom-width:2px;border-radius:5px;padding:1px 6px;font-family:var(--mono);font-size:.78em}

/* Header */
.desk-head{position:sticky;top:0;z-index:10;display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  padding:16px 26px;background:rgba(10,11,13,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.brand{display:flex;flex-direction:column;line-height:1.1;margin-right:auto}
.brand .kick{font-family:var(--mono);font-size:10.5px;letter-spacing:.4em;color:var(--standby)}
.brand h1{font-family:var(--disp);font-weight:600;font-size:22px;margin:3px 0 0;letter-spacing:-.01em}
.act{font-size:13px;font-weight:500;border:1px solid var(--line);color:var(--ink);background:var(--panel-2);
  border-radius:3px;padding:8px 14px;text-decoration:none;display:inline-flex;align-items:center;gap:7px;cursor:pointer}
.act:hover{background:var(--panel-3);color:var(--ink)}
.act--go{background:var(--standby);border-color:var(--standby);color:var(--void);font-weight:600}
.act--go:hover{background:#57dbe2;color:var(--void)}
.act--live{background:transparent;border-color:var(--live);color:var(--live)}
.act--live:hover{background:rgba(255,90,60,.12);color:var(--live)}

/* Layout */
.wrap{max-width:1180px;margin:0 auto;padding:26px;display:grid;grid-template-columns:1fr 320px;gap:22px;align-items:start}
.card-desk{background:var(--panel);border:1px solid var(--line);border-radius:5px;padding:22px 24px}
.card-desk > h2{font-family:var(--disp);font-weight:600;font-size:16px;margin:0 0 4px}
.card-desk > .sub{font-size:13px;color:var(--muted);margin:0 0 18px;line-height:1.5}

/* Side rail */
.rail{display:flex;flex-direction:column;gap:18px;position:sticky;top:88px}
.rail h3{font-family:var(--mono);font-size:10.5px;letter-spacing:.24em;text-transform:uppercase;color:var(--muted);margin:0 0 12px}
.layermap{display:flex;flex-direction:column;gap:9px}
.lm{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--panel-2);border:1px solid var(--line);border-left-width:3px;border-radius:3px}
.lm .tag{font-family:var(--mono);font-size:11px;letter-spacing:.1em;color:var(--muted);width:26px}
.lm .name{font-family:var(--disp);font-weight:600;font-size:14px}
.lm .meta{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--ink);text-align:right}
.lm .meta small{display:block;color:var(--muted);letter-spacing:.06em}
.lm--bg{border-left-color:var(--muted)}
.lm--main{border-left-color:var(--live)}
.lm--overlay{border-left-color:var(--standby)}
.settings-mini{display:flex;flex-direction:column;gap:8px;font-size:12.5px;color:var(--muted)}
.settings-mini div{display:flex;justify-content:space-between}
.settings-mini b{color:var(--ink);font-weight:500;font-family:var(--mono);font-size:11.5px}
.flowbox{font-size:12.5px;color:var(--muted);line-height:1.65}
.flowbox b{color:var(--ink);font-weight:500}
.srcnote{font-size:12px;color:var(--muted);line-height:1.6;background:var(--panel-2);border:1px solid var(--line);border-radius:3px;padding:12px 14px}
.srcnote code{color:var(--standby);font-family:var(--mono);font-size:11.5px}

#toasts{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:8px}
.vp-toast{font-family:var(--mono);font-size:12px;background:rgba(16,18,22,.9);border:1px solid var(--line);border-left:3px solid var(--standby);border-radius:2px;padding:9px 15px}
.vp-toast.warn{border-left-color:var(--live)}

@media (max-width:900px){ .wrap{grid-template-columns:1fr} .rail{position:static} }
</style>
</head>
<body>

<header class="desk-head">
  <div class="brand"><span class="kick">AI EMCEE · CONFIG</span><h1>Control Desk</h1></div>
  <input type="file" id="importFile" accept="application/json,.json" hidden>
  <button class="act" id="btnImport"><i class="bi bi-upload"></i> Import</button>
  <button class="act" id="btnExport"><i class="bi bi-download"></i> Export config.json</button>
  <button class="act act--go" id="btnSave"><i class="bi bi-save"></i> Save</button>
  <a class="act act--live" href="videoplayer.html" target="_blank" rel="noopener"><i class="bi bi-play-btn"></i> Open player</a>
</header>

<div class="wrap">
  <main class="card-desk">
    <h2>Playlists &amp; settings</h2>
    <p class="sub">Edits save to this browser automatically. Use <b>Export config.json</b> to place a file next to the player for a server deployment. <span id="sourceNote" class="text-muted"></span></p>
    <div id="editorHost"></div>
    <div class="d-flex gap-2 mt-3 flex-wrap">
      <button class="act" id="btnReset"><i class="bi bi-arrow-counterclockwise"></i> Reset to defaults</button>
    </div>
  </main>

  <aside class="rail">
    <section>
      <h3>Layer map</h3>
      <div class="layermap">
        <div class="lm lm--bg"><span class="tag">L1</span><span class="name">Backdrop</span><span class="meta" id="mBg">—</span></div>
        <div class="lm lm--main"><span class="tag">L2</span><span class="name">Emcee</span><span class="meta" id="mMain">—</span></div>
        <div class="lm lm--overlay"><span class="tag">L3</span><span class="name">Idle</span><span class="meta" id="mOverlay">—</span></div>
      </div>
    </section>
    <section>
      <h3>Current setup</h3>
      <div class="settings-mini">
        <div><span>Display fit</span><b id="mDisp">—</b></div>
        <div><span>Starts in</span><b id="mStart">—</b></div>
        <div><span>End of segment</span><b id="mEnd">—</b></div>
        <div><span>Idle order</span><b id="mOrder">—</b></div>
      </div>
    </section>
    <section>
      <h3>Live flow</h3>
      <p class="flowbox"><b>Space</b> hands the stage to the next emcee segment (L2, <span style="color:var(--live)">on air</span>). When it ends or you press space again, it pauses and the idle loop (L3, <span style="color:var(--standby)">standby</span>) fades in until you press space once more.</p>
    </section>
    <section>
      <h3>Video paths</h3>
      <p class="srcnote">Paths are relative to the player file. Keep clips in a <code>videos/</code> folder beside <code>videoplayer.html</code>, e.g. <code>videos/main/intro.mp4</code>. Full URLs (<code>https://…</code>) also work. Use web-friendly <code>.mp4</code> (H.264/AAC) or <code>.webm</code>.</p>
    </section>
  </aside>
</div>

<div id="toasts"></div>

<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="config-editor.js"></script>
<script>
(function(){
  "use strict";
  const $=(s)=>document.querySelector(s);
  let cfg=null;

  function toast(msg,kind){
    const t=document.createElement("div");
    t.className="vp-toast animate__animated animate__fadeInUp"+(kind==="warn"?" warn":"");
    t.textContent=msg; $("#toasts").appendChild(t);
    setTimeout(()=>{ t.classList.add("animate__fadeOutDown"); setTimeout(()=>t.remove(),500); },2400);
  }

  const cap=(s)=>s? s.charAt(0).toUpperCase()+s.slice(1).replace(/-/g," ") : "—";
  function summary(){
    const en=(a)=>a.filter(v=>v.enabled&&v.src).length;
    const bg=cfg.background;
    $("#mBg").innerHTML = bg.enabled&&bg.src ? (cap(bg.loopMode)==="Count"?("×"+bg.loopCount):cap(bg.loopMode))+"<small>enabled</small>" : "Off<small>disabled</small>";
    $("#mMain").innerHTML = en(cfg.main.playlist)+" / "+cfg.main.playlist.length+"<small>segments</small>";
    $("#mOverlay").innerHTML = en(cfg.overlay.videos)+" / "+cfg.overlay.videos.length+"<small>clips</small>";
    $("#mDisp").textContent = cfg.player.displayMode==="fit-height"?"Fit height":cfg.player.displayMode==="fit-width"?"Fit width":cap(cfg.player.displayMode);
    $("#mStart").textContent = cfg.player.startMode==="playing"?"On air":"Standby";
    $("#mEnd").textContent = cfg.main.playbackMode==="continuous"?"Play through":"Pause + idle";
    $("#mOrder").textContent = cap(cfg.overlay.order);
  }

  let loadedUrl=null;
  function persist(c){ if(!loadedUrl) VP.saveLocal(c); }
  function onChange(c){ persist(c); summary(); }
  function render(){ VP.renderEditor($("#editorHost"),cfg,{onChange}); summary(); }

  $("#btnSave").addEventListener("click",()=>{ VP.saveLocal(cfg); toast("Saved to this browser"); });
  $("#btnExport").addEventListener("click",()=>{ VP.download(cfg); toast("config.json downloaded"); });
  $("#btnImport").addEventListener("click",()=>$("#importFile").click());
  $("#importFile").addEventListener("change",async(e)=>{
    const f=e.target.files[0]; if(!f)return;
    try{ cfg=await VP.importFromFile(f); loadedUrl=null; VP.saveLocal(cfg); render(); toast("Config imported"); }
    catch(err){ toast(err.message,"warn"); }
    e.target.value="";
  });
  $("#btnReset").addEventListener("click",async()=>{
    if(!confirm("Clear your saved edits in this browser? The desk falls back to the ?load file, config.json, or built-in defaults."))return;
    VP.clearLocal(); const r=await VP.resolve(); cfg=r.config; loadedUrl=r.loadedUrl||null;
    $("#sourceNote").textContent="Loaded from "+r.source+"."; render(); toast("Cleared — using "+r.source);
  });

  (async function(){
    const r=await VP.resolve(); cfg=r.config; loadedUrl=r.loadedUrl||null;
    $("#sourceNote").textContent="Loaded from "+r.source+".";
    if(loadedUrl){ const a=document.querySelector('a.act--live'); if(a) a.href="videoplayer.html?load="+encodeURIComponent(loadedUrl); }
    render();
  })();
})();
</script>
</body>
</html>