diff --git a/app.js b/app.js index 7c488cd..b65607b 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,7 @@ const page = document.body.dataset.page; let cachedItems = null; + let cachedLevelMeta = null; // Storage helpers function read(key, fallback){ @@ -19,6 +20,21 @@ }); } + function parseLevelMeta(txt){ + const map = {}; + txt.split(/\r?\n/).map(l=>l.trim()).filter(Boolean).forEach(l=>{ + if(l.startsWith('//')) return; + const parts = l.split('|').map(p=>p.trim()); + const title = parts[0] || ''; + if(!title) return; + map[title] = { + levelId: parts[1] || 'unknown', + percent: parts[2] || '100' + }; + }); + return map; + } + function loadItems(){ if(cachedItems) return Promise.resolve(cachedItems); return fetch('data.txt').then(r=>r.text()).then(txt=>{ @@ -27,17 +43,45 @@ }); } + function loadLevelMeta(){ + if(cachedLevelMeta) return Promise.resolve(cachedLevelMeta); + return fetch('level-ids.txt').then(r=>r.text()).then(txt=>{ + cachedLevelMeta = parseLevelMeta(txt); + return cachedLevelMeta; + }).catch(()=>{ + cachedLevelMeta = {}; + return cachedLevelMeta; + }); + } + + function fetchLevelIdFromApi(title){ + const url = `https://gdbrowser.com/api/search/${encodeURIComponent(title)}?diff=-2&demonFilter=5&count=10`; + return fetch(url).then(r=>r.json()).then(results=>{ + if(!Array.isArray(results) || !results.length) return null; + const exact = results.find(item=>String(item.name||'').toLowerCase() === String(title||'').toLowerCase()); + const match = exact || results[0]; + if(!match || !match.id) return null; + return String(match.id); + }).catch(()=>null); + } + if(page==='roulette'){ const spinBtn = qs('roulette-spin'); const statusEl = qs('roulette-status'); const titleEl = qs('roulette-title'); const rankEl = qs('roulette-rank'); + const idEl = qs('roulette-level-id'); + const noteEl = qs('roulette-note'); const openEl = qs('roulette-open'); - function showPick(item){ + function showPick(item, meta){ statusEl.textContent = 'Your demon is:'; titleEl.textContent = item.title; rankEl.textContent = `Rank: #${item.position}`; + idEl.textContent = `Level ID: ${meta.levelId || 'unknown'}`; + noteEl.textContent = meta.source === 'api' + ? 'Level ID was looked up from the Geometry Dash community API.' + : 'Level ID came from your local level-ids.txt file.'; if(item.url){ openEl.hidden = false; openEl.href = item.url; @@ -50,19 +94,37 @@ statusEl.textContent = 'Spinning...'; titleEl.textContent = 'Choosing a demon'; rankEl.textContent = 'Rank: -'; + idEl.textContent = 'Level ID: -'; + noteEl.textContent = 'Checking your local file and API if needed.'; openEl.hidden = true; - loadItems().then(items=>{ + Promise.all([loadItems(), loadLevelMeta()]).then(([items, metaMap])=>{ if(!items.length){ statusEl.textContent = 'No demons found.'; titleEl.textContent = 'Add demons to data.txt'; + idEl.textContent = 'Level ID: -'; + noteEl.textContent = 'No list data was found.'; return; } const item = items[Math.floor(Math.random()*items.length)]; - window.setTimeout(()=>showPick(item), 350); + const localMeta = metaMap[item.title] || {levelId:'unknown', percent:'100'}; + if(localMeta.levelId && localMeta.levelId !== 'unknown'){ + window.setTimeout(()=>showPick(item, {levelId: localMeta.levelId, percent: localMeta.percent, source: 'file'}), 350); + return; + } + fetchLevelIdFromApi(item.title).then(levelId=>{ + const meta = { + levelId: levelId || 'unknown', + percent: localMeta.percent || '100', + source: levelId ? 'api' : 'file' + }; + window.setTimeout(()=>showPick(item, meta), 350); + }); }).catch(err=>{ statusEl.textContent = 'Could not load the list.'; titleEl.textContent = 'Run the site on a local server'; rankEl.textContent = 'Rank: -'; + idEl.textContent = 'Level ID: -'; + noteEl.textContent = 'The local file or API lookup failed.'; console.error(err); }); }); diff --git a/data.txt b/data.txt index e1ec169..1e66c6f 100644 --- a/data.txt +++ b/data.txt @@ -47,4 +47,4 @@ new|46|The Halucination|https://youtu.be/tYbXsNkO9HE?si=b8hmXS98BAjDlPzF new|47|CONVULSION|https://youtu.be/qeRKuyU3eGI?si=qJN026t6RcGjU3iW new|48|Solar Flare|https://youtu.be/eHQNgty8ypY?si=ZdUIODdeDYLAXeHI new|49|LIMBO|https://youtu.be/kXYMbaMVOZg?si=fWm4ngeSxBhFZadA -new|50|The Catacombs|https://youtu.be/T9-75lfKVQg?si=zEH6vP1617n5666I \ No newline at end of file +new|50|The Catacombs|https://youtu.be/T9-75lfKVQg?si=zEH6vP1617n5666I diff --git a/level-ids.txt b/level-ids.txt new file mode 100644 index 0000000..4fc1643 --- /dev/null +++ b/level-ids.txt @@ -0,0 +1,52 @@ +// format: title|levelId|percent +// replace "unknown" with the real Geometry Dash level ID when you have it +Flamewall|unknown|100 +Thinking Space II|unknown|100 +Amethyst|unknown|100 +Tidal Wave|unknown|100 +Orbit|unknown|100 +BOOBAWAMBA|unknown|100 +Nullscapes|unknown|100 +Quantuese Processing|unknown|100 +Subsuming Vortex|unknown|100 +Andromeda|unknown|100 +Every End|unknown|100 +Silent Clubstep|unknown|100 +Anathema|unknown|100 +Acheron|unknown|100 +Ashley Wave Trials|unknown|100 +Avernus|unknown|100 +Menace|unknown|100 +Spectre|unknown|100 +Abyss Of Darkness|unknown|100 +Defeated Circles|unknown|100 +Tunnel Of Despair|unknown|100 +Subterminal Point|unknown|100 +KOCMOC|unknown|100 +Slaughterhouse|unknown|100 +Kyouki|unknown|100 +The Lightning Rod|unknown|100 +Based After Based|unknown|100 +CHIL|unknown|100 +Sakupen Circles|unknown|100 +Deimos (ItsHybrid)|unknown|100 +Eyes In The Water|unknown|100 +Voltage|unknown|100 +Firework|unknown|100 +Silentlocked|unknown|100 +poocubed|unknown|100 +KOSETSU|unknown|100 +Through The Gates|unknown|100 +Saul Goodman|unknown|100 +The Salt Factory|unknown|100 +Snowbound|unknown|100 +CONVOLSION|unknown|100 +The Apocalyptic Trilogy|unknown|100 +MINUSdry|unknown|100 +Sevvend Clubstep|unknown|100 +Edge Of Destiny|unknown|100 +The Halucination|unknown|100 +CONVULSION|unknown|100 +Solar Flare|unknown|100 +LIMBO|unknown|100 +The Catacombs|unknown|100 diff --git a/roulette.html b/roulette.html index be19cee..b81f2aa 100644 --- a/roulette.html +++ b/roulette.html @@ -17,12 +17,26 @@
+
+

Rules

+

How Roulette Works

+ +
+

Random Challenge

Demon Roulette

-

Spin the list and get a random demon from your current FEDL rankings.

+

Spin the FEDL list to get a random demon for your roulette run.

@@ -31,6 +45,8 @@

Press spin to choose a demon.

No demon selected

Rank: -

+

Level ID: -

+

Level IDs come from your local file first, then API lookup if needed.

diff --git a/styles.css b/styles.css index 0deb2db..d917215 100644 --- a/styles.css +++ b/styles.css @@ -33,6 +33,7 @@ table.levels-table tr:hover td{background:rgba(255,255,255,0.01)} /* small helpers reused */ .small{font-size:0.9rem} +.roulette-rules{margin-bottom:18px} .roulette-panel{margin-top:18px;padding:24px 26px;background:linear-gradient(135deg,rgba(92,197,255,0.12),rgba(7,19,38,0.96));border:1px solid rgba(92,197,255,0.16)} .roulette-head{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;margin-bottom:18px} .roulette-kicker{margin:0 0 8px 0;text-transform:uppercase;letter-spacing:0.14em;font-size:0.72rem;font-weight:700;color:var(--accent)} @@ -42,6 +43,8 @@ table.levels-table tr:hover td{background:rgba(255,255,255,0.01)} .roulette-status{margin:0 0 8px 0} .roulette-card h3{margin:0 0 8px 0;font-size:1.5rem} .roulette-rank{margin:0 0 16px 0;font-size:1rem;color:#dce8f5} +.roulette-meta{margin:0 0 10px 0;font-size:1rem;color:#dce8f5} +.roulette-note{margin:10px 0 16px 0} .roulette-open{color:#042} .discord-panel{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:18px;padding:26px 28px;background:linear-gradient(135deg,rgba(88,101,242,0.18),rgba(7,19,38,0.95));border:1px solid rgba(88,101,242,0.22)} .discord-copy{flex:1}