From f8fcec309bcf785a4268736860cccddf270e41f1 Mon Sep 17 00:00:00 2001 From: Miles allen Date: Sat, 28 Mar 2026 00:23:52 -0500 Subject: [PATCH] Add initial implementation of GD Demand List application with multi-page support --- app.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ data.txt | 7 +++++ index.html | 22 +++++++++++++ lists.html | 30 ++++++++++++++++++ players.html | 33 ++++++++++++++++++++ styles.css | 23 ++++++++++++++ 6 files changed, 202 insertions(+) create mode 100644 app.js create mode 100644 data.txt create mode 100644 index.html create mode 100644 lists.html create mode 100644 players.html create mode 100644 styles.css diff --git a/app.js b/app.js new file mode 100644 index 0000000..e7374f0 --- /dev/null +++ b/app.js @@ -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=`${escapeHtml(p.name)} (${p.id})`; + 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='

Failed to load data.txt — run via a local server.

'; 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])} +})(); diff --git a/data.txt b/data.txt new file mode 100644 index 0000000..4add1a7 --- /dev/null +++ b/data.txt @@ -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 diff --git a/index.html b/index.html new file mode 100644 index 0000000..16bb632 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + GD Demand List + + + +
+

GD Demand List

+ +
+
+

Welcome — choose a page above to manage players or demand lists.

+
+ + + diff --git a/lists.html b/lists.html new file mode 100644 index 0000000..80f5944 --- /dev/null +++ b/lists.html @@ -0,0 +1,30 @@ + + + + + + Lists — GD Demand List + + + +
+

Demand Lists

+ +
+
+ + +
+

All Videos

+
+
+
+ + + diff --git a/players.html b/players.html new file mode 100644 index 0000000..2d4aa14 --- /dev/null +++ b/players.html @@ -0,0 +1,33 @@ + + + + + + Players — GD Demand List + + + +
+

Players

+ +
+
+
+

Add Player

+
+ + +
+
+ +
+

Players

+
    +
    +
    + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..166b95e --- /dev/null +++ b/styles.css @@ -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)}