Add initial implementation of GD Demand List application with multi-page support
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
// Lightweight multi-page handler for GD Demand List
|
||||
(function(){
|
||||
function qs(id){return document.getElementById(id)}
|
||||
|
||||
const page = document.body.dataset.page;
|
||||
|
||||
// Storage helpers
|
||||
function read(key, fallback){
|
||||
try{const v = localStorage.getItem(key); return v?JSON.parse(v):fallback}
|
||||
catch(e){return fallback}
|
||||
}
|
||||
function write(key, val){localStorage.setItem(key,JSON.stringify(val))}
|
||||
|
||||
// Players page
|
||||
if(page==='players'){
|
||||
const form = qs('player-form'); const nameIn = qs('player-name'); const list = qs('players');
|
||||
let players = read('gd_players',[]);
|
||||
function render(){list.innerHTML=''; players.forEach((p,idx)=>{
|
||||
const li=document.createElement('li'); li.innerHTML=`<span><span class="name">${escapeHtml(p.name)}</span> <span class="muted">(${p.id})</span></span>`;
|
||||
const actions=document.createElement('div'); actions.className='actions';
|
||||
const edit=document.createElement('button'); edit.textContent='Edit'; edit.onclick=()=>{const nv=prompt('Edit name',p.name); if(nv){players[idx].name=nv; write('gd_players',players); render()}};
|
||||
const del=document.createElement('button'); del.textContent='Delete'; del.onclick=()=>{if(confirm('Delete player?')){players.splice(idx,1); write('gd_players',players); render()}};
|
||||
actions.appendChild(edit); actions.appendChild(del); li.appendChild(actions); list.appendChild(li);
|
||||
})}
|
||||
form.addEventListener('submit',e=>{e.preventDefault(); const name=nameIn.value.trim(); if(!name) return; players.push({id:Date.now().toString(36),name}); write('gd_players',players); nameIn.value=''; render()});
|
||||
render();
|
||||
}
|
||||
|
||||
// Lists page
|
||||
if(page==='lists'){
|
||||
const levelsEl = qs('levels'); const listArea = qs('list-area'); const titleEl = qs('list-title');
|
||||
// Load hard-coded data file data.txt (level|position|title|url per line)
|
||||
function loadData(){
|
||||
fetch('data.txt').then(r=>r.text()).then(txt=>{
|
||||
const lines = txt.split(/\r?\n/).map(l=>l.trim()).filter(Boolean);
|
||||
const items = lines.map(l=>{
|
||||
const parts = l.split('|').map(p=>p.trim());
|
||||
return {level:parts[0]||'Unknown',position:parts[1]||'',title:parts[2]||'Untitled',url:parts[3]||''};
|
||||
});
|
||||
setupLevels(items);
|
||||
}).catch(err=>{listArea.innerHTML='<p class="muted">Failed to load data.txt — run via a local server.</p>'; console.error(err)});
|
||||
}
|
||||
|
||||
function setupLevels(items){
|
||||
const levels = [...new Set(items.map(i=>i.level))];
|
||||
levelsEl.innerHTML=''; levels.forEach(l=>{
|
||||
const a=document.createElement('a'); a.href='#'; a.textContent=l; a.className='level-link'; a.onclick=(e)=>{e.preventDefault(); selectLevel(l,items,a)}; levelsEl.appendChild(a);
|
||||
});
|
||||
// default select first
|
||||
if(levels.length) selectLevel(levels[0],items,levelsEl.querySelector('.level-link'));
|
||||
}
|
||||
|
||||
function selectLevel(level, items, linkEl){
|
||||
// mark active
|
||||
levelsEl.querySelectorAll('.level-link').forEach(a=>a.classList.remove('active'));
|
||||
if(linkEl) linkEl.classList.add('active');
|
||||
titleEl.textContent = level + ' Videos';
|
||||
const filtered = items.filter(it=>it.level===level).sort((a,b)=> (a.position||0)-(b.position||0));
|
||||
listArea.innerHTML = '';
|
||||
filtered.forEach(it=>{
|
||||
const card = document.createElement('div'); card.className='video-card';
|
||||
const h = document.createElement('h3'); h.textContent = `${it.position} — ${it.title}`;
|
||||
card.appendChild(h);
|
||||
const url = it.url;
|
||||
if(/youtube\.com|youtu\.be/.test(url)){
|
||||
const id = extractYouTubeID(url);
|
||||
const iframe = document.createElement('iframe'); iframe.src = `https://www.youtube.com/embed/${id}`; iframe.allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'; iframe.allowFullscreen = true;
|
||||
card.appendChild(iframe);
|
||||
} else if(url){
|
||||
const a = document.createElement('a'); a.href = url; a.textContent = url; a.target='_blank'; card.appendChild(a);
|
||||
} else {
|
||||
const p = document.createElement('p'); p.textContent = 'No URL'; card.appendChild(p);
|
||||
}
|
||||
listArea.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function extractYouTubeID(url){
|
||||
const m = url.match(/(?:v=|\/embed\/|youtu\.be\/)([A-Za-z0-9_-]{6,})/); return m?m[1]:'';
|
||||
}
|
||||
|
||||
loadData();
|
||||
}
|
||||
|
||||
// Utility
|
||||
function escapeHtml(s){return String(s).replace(/[&<>"']/g, c=>({"&":"&","<":"<",">":">","\"":""","'":"'"})[c])}
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
Easy|1|Beginner Tutorial|https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
Easy|2|Simple Level Showcase|https://www.youtube.com/watch?v=V-_O7nl0Ii0
|
||||
Normal|1|Intermediate Run|https://www.youtube.com/watch?v=3JZ_D3ELwOQ
|
||||
Hard|1|Hard Demon Playthrough|https://www.youtube.com/watch?v=fJ9rUzIMcZQ
|
||||
Insane|1|Insane Demon Compilation|https://www.youtube.com/watch?v=L_jWHffIx5E
|
||||
new|1|Insane Demon Compilation|https://www.youtube.com/watch?v=L_jWHffIx5E
|
||||
cool|1|Insane Demon Compilation|https://www.youtube.com/watch?v=L_jWHffIx5E
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>GD Demand List</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body data-page="index">
|
||||
<header>
|
||||
<h1>GD Demand List</h1>
|
||||
<nav>
|
||||
<a href="players.html">Players</a>
|
||||
<a href="lists.html">Lists</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<p>Welcome — choose a page above to manage players or demand lists.</p>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Lists — GD Demand List</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body data-page="lists">
|
||||
<header>
|
||||
<h1>Demand Lists</h1>
|
||||
<nav>
|
||||
<a href="index.html">Home</a>
|
||||
<a href="players.html">Players</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="layout">
|
||||
<aside class="sidebar panel">
|
||||
<h2>Levels</h2>
|
||||
<ul id="levels"></ul>
|
||||
</aside>
|
||||
|
||||
<section class="panel content">
|
||||
<h2 id="list-title">All Videos</h2>
|
||||
<div id="list-area"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Players — GD Demand List</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body data-page="players">
|
||||
<header>
|
||||
<h1>Players</h1>
|
||||
<nav>
|
||||
<a href="index.html">Home</a>
|
||||
<a href="lists.html">Lists</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section class="panel">
|
||||
<h2>Add Player</h2>
|
||||
<form id="player-form">
|
||||
<input id="player-name" placeholder="Player name" required />
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Players</h2>
|
||||
<ul id="players"></ul>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
:root{--bg:#0f1724;--panel:#0b1220;--accent:#ff6b6b;--muted:#94a3b8;--text:#e6eef8}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;color:var(--text);background:linear-gradient(180deg,#061021 0%,#071426 100%)}
|
||||
header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px}
|
||||
header h1{margin:0;font-size:1.2rem}
|
||||
nav a{color:var(--muted);margin-left:12px;text-decoration:none}
|
||||
main{padding:16px 24px}
|
||||
.layout{display:flex;gap:12px}
|
||||
.sidebar{width:200px}
|
||||
.content{flex:1}
|
||||
.panel{background:rgba(255,255,255,0.02);padding:12px;border-radius:8px;margin-bottom:12px}
|
||||
form input{padding:8px;border-radius:6px;border:1px solid rgba(255,255,255,0.06);margin-right:8px;color:var(--text);background:transparent}
|
||||
button{background:var(--accent);color:#fff;border:none;padding:8px 12px;border-radius:6px}
|
||||
ul{list-style:none;padding:0;margin:0}
|
||||
li{display:flex;align-items:center;justify-content:space-between;padding:8px;border-bottom:1px solid rgba(255,255,255,0.02)}
|
||||
.actions button{margin-left:6px;background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted);padding:6px;border-radius:6px}
|
||||
.name{font-weight:600}
|
||||
.muted{color:var(--muted);font-size:0.9rem}
|
||||
|
||||
.video-card{margin-bottom:12px;padding:10px;border-radius:8px;background:rgba(255,255,255,0.02)}
|
||||
.video-card iframe{width:100%;height:320px;border:0;border-radius:6px}
|
||||
.level-link{display:block;padding:6px 8px;border-radius:6px;color:var(--muted);text-decoration:none}
|
||||
.level-link.active{background:rgba(255,255,255,0.03);color:var(--text)}
|
||||
Reference in New Issue
Block a user