Files
miles-web/projects/fedl.html
T
2026-07-26 01:13:05 -05:00

339 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FEDL | Miles Wolf Allen</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Space+Mono:wght@400;700&family=Inter:wght@400;500;600;800&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="cursor-dot" id="cursorDot"></div>
<div class="cursor-ring" id="cursorRing"></div>
<div class="flash" id="flash"></div>
<div class="toast-stack" id="toastStack"></div>
<nav class="mobile-nav">
<a href="../index.html">Home</a>
<a href="fedl.html" class="active">FEDL</a>
<a href="omniemu.html">OmniEmu</a>
<a href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
</nav>
<div class="container">
<nav>
<a href="../index.html" class="nav-logo"><span class="logo-dot"></span> milesallen</a>
<div class="nav-links">
<a href="../index.html#about">About</a>
<a href="../index.html#skills">Skills</a>
<a href="../index.html#projects">Projects</a>
<a href="../index.html#contact">Contact</a>
</div>
</nav>
<section class="project-hero">
<div class="breadcrumb">
<a href="../index.html">Home</a>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
<span>FEDL</span>
</div>
<h1>FEDL</h1>
<p class="tagline">A Geometry Dash demon list site with ranked levels, run submissions, admin moderation, accounts, and community features.</p>
<div class="project-meta-bar">
<a href="https://github.com/mileswolfallen2/fedl" class="meta-chip primary" target="_blank">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77a5.44 5.44 0 00-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22"/></svg>
View on GitHub
</a>
<a href="https://fedl.site" class="meta-chip" target="_blank">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
Live Site
</a>
<span class="meta-chip">HTML / JS / CSS</span>
</div>
</section>
<div class="stats-bar">
<div class="stat-item">
<div class="stat-value accent" id="fedl-levels">--</div>
<div class="stat-label">Levels</div>
</div>
<div class="stat-item">
<div class="stat-value green" id="fedl-runs">--</div>
<div class="stat-label">Runs Submitted</div>
</div>
<div class="stat-item">
<div class="stat-value orange" id="fedl-commits">196</div>
<div class="stat-label">Commits</div>
</div>
<div class="stat-item">
<div class="stat-value" id="fedl-stars">2</div>
<div class="stat-label">Stars</div>
</div>
</div>
<section class="reveal">
<div class="section-divider"></div>
<h2 class="section-title">What It Includes</h2>
<p class="section-subtitle">Full-featured list site with frontend and backend</p>
<div class="feature-grid">
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
Ranked List Browser
</h3>
<p>Browse the ranked demon list with search, filters, and video playback for each level.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
Run Submissions
</h3>
<p>Submit runs for review. Admins can approve, reject, or bulk-approve submissions.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
Accounts & Auth
</h3>
<p>Signup, login, and synced user state. Bearer token authentication with session management.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
Community
</h3>
<p>Community posts, bug reports, and private direct messages between users.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
Rank Guesser
</h3>
<p>A guessing game where you try to identify the rank of demon levels on the list.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
Demon Roulette
</h3>
<p>Randomly pick a demon level to play. Uses level ID lookup for fast selection.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
Admin Panel
</h3>
<p>Admin moderation page for list editing, run management, and AREDL/Pointercrate imports.</p>
</div>
<div class="feature-card">
<h3>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 10h-1.26A8 8 0 109 20h9a5 5 0 000-10z"/></svg>
Live Updates
</h3>
<p>Server-Sent Events for real-time list data updates across connected clients.</p>
</div>
</div>
</section>
<section class="reveal">
<div class="section-divider"></div>
<h2 class="section-title">Tech Stack</h2>
<div class="tech-stack">
<span class="tech-badge">HTML (49.8%)</span>
<span class="tech-badge">JavaScript (29.8%)</span>
<span class="tech-badge">CSS (20.4%)</span>
<span class="tech-badge">Node.js</span>
<span class="tech-badge">Server-Sent Events</span>
<span class="tech-badge">localStorage</span>
<span class="tech-badge">JSON</span>
</div>
</section>
<section class="reveal">
<div class="section-divider"></div>
<h2 class="section-title">Local Setup</h2>
<code-block>
<span class="comment"># Make sure Node.js is installed, then:</span><br>
<span class="cmd">node server/server.js</span><br><br>
<span class="comment"># Open the site at:</span><br>
http://127.0.0.1:8090/fedl/<br><br>
<span class="comment"># Expose on local network:</span><br>
HOST=0.0.0.0 PORT=8090 <span class="cmd">node server/server.js</span>
</code-block>
</section>
<footer>
Built by Miles · 2026 · Powered by too much coffee and the Konami code
</footer>
</div>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
const API = 'https://server.fedl.site/fedl/api';
fetch(`${API}/list`)
.then(r => r.json())
.then(data => {
const items = data.items || [];
document.getElementById('fedl-levels').textContent = items.length;
})
.catch(() => {
document.getElementById('fedl-levels').textContent = '150+';
});
fetch(`${API}/runs`)
.then(r => r.json())
.then(data => {
const runs = Array.isArray(data) ? data : [];
document.getElementById('fedl-runs').textContent = runs.length;
})
.catch(() => {
document.getElementById('fedl-runs').textContent = '--';
});
fetch('https://api.github.com/repos/mileswolfallen2/fedl')
.then(r => r.json())
.then(data => {
if (data.stargazers_count != null) {
document.getElementById('fedl-stars').textContent = data.stargazers_count;
}
if (data.default_branch) {
fetch(`https://api.github.com/repos/mileswolfallen2/fedl/commits?sha=${data.default_branch}&per_page=1`)
.then(r => {
const total = r.headers.get('x-total-count');
if (total) document.getElementById('fedl-commits').textContent = total;
})
.catch(() => {});
}
})
.catch(() => {});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
// Hero entrance
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
heroTl
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
.from('.project-hero h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
.from('.tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
.from('.meta-chip', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3')
.from('.stats-bar', { y: 30, opacity: 0, duration: 0.6 }, '-=0.2')
.from('.stat-item', { y: 20, opacity: 0, scale: 0.9, duration: 0.4, stagger: 0.08, ease: 'back.out(1.5)' }, '-=0.3');
// Nav entrance
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
// Section titles
document.querySelectorAll('.section-title').forEach(title => {
gsap.from(title, {
scrollTrigger: { trigger: title, start: 'top 85%' },
y: 30, opacity: 0, duration: 0.7, ease: 'power3.out'
});
});
// Section dividers
document.querySelectorAll('.section-divider').forEach(div => {
gsap.from(div, {
scrollTrigger: { trigger: div, start: 'top 90%' },
width: 0, duration: 0.6, ease: 'power2.out'
});
});
// Feature cards stagger
document.querySelectorAll('.feature-grid').forEach(grid => {
gsap.from(grid.querySelectorAll('.feature-card'), {
scrollTrigger: { trigger: grid, start: 'top 85%' },
y: 50, opacity: 0, scale: 0.9, duration: 0.6, stagger: 0.1, ease: 'power3.out'
});
});
// Tech badges
document.querySelectorAll('.tech-stack').forEach(stack => {
gsap.from(stack.querySelectorAll('.tech-badge'), {
scrollTrigger: { trigger: stack, start: 'top 88%' },
y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.05, ease: 'back.out(1.5)'
});
});
// Tables
document.querySelectorAll('.table-wrapper').forEach(wrapper => {
gsap.from(wrapper, {
scrollTrigger: { trigger: wrapper, start: 'top 85%' },
y: 40, opacity: 0, duration: 0.7, ease: 'power3.out'
});
});
// Code blocks
document.querySelectorAll('code-block').forEach(block => {
gsap.from(block, {
scrollTrigger: { trigger: block, start: 'top 88%' },
y: 30, opacity: 0, duration: 0.6, ease: 'power3.out'
});
});
// Footer
gsap.from('footer', {
scrollTrigger: { trigger: 'footer', start: 'top 95%' },
opacity: 0, duration: 0.8
});
window.addEventListener('load', () => ScrollTrigger.refresh());
</script>
<script>
var toastQueue=document.getElementById('toastStack');var shownToasts={};
function showToast(id,title,body,icon){
if(shownToasts[id])return;shownToasts[id]=true;
var t=document.createElement('div');t.className='toast';
t.innerHTML='<span class="icon">'+(icon||'!')+'</span><span><span class="title">'+title+'</span>'+body+'</span>';
toastQueue.appendChild(t);
gsap.to(t,{x:0,duration:.6,ease:'power3.out'});
setTimeout(function(){gsap.to(t,{x:'-150%',opacity:0,duration:.4,ease:'power2.in',onComplete:function(){t.remove();}});},3000);
}
(function(){
var seq=['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
var pos=0;
window.addEventListener('keydown',function(e){
var key=e.key.length===1?e.key.toLowerCase():e.key;
if(key===seq[pos]){pos++;if(pos===seq.length){pos=0;triggerKonami();}}
else{pos=(key===seq[0])?1:0;}
});
function triggerKonami(){
showToast('konami','SECRET FOUND','Konami Code Activated','*');
var flash=document.getElementById('flash');
gsap.fromTo(flash,{opacity:.5},{opacity:0,duration:.5});
gsap.to('body',{x:'random(-5,5)',y:'random(-5,5)',duration:.05,repeat:10,ease:'sine.inOut',onComplete:function(){gsap.set('body',{x:0,y:0});}});
}
})();
</script>
<script>
(function(){
var dot=document.getElementById('cursorDot');
var ring=document.getElementById('cursorRing');
if(!dot||!ring)return;
var mx=0,my=0,rx=0,ry=0;
document.addEventListener('mousemove',function(e){mx=e.clientX;my=e.clientY;dot.style.left=mx+'px';dot.style.top=my+'px';});
(function loop(){
rx+=(mx-rx)*0.15;ry+=(my-ry)*0.15;
ring.style.left=rx+'px';ring.style.top=ry+'px';
requestAnimationFrame(loop);
})();
document.querySelectorAll('a,button,.meta-chip,.tech-badge,.feature-card').forEach(function(el){
el.addEventListener('mouseenter',function(){dot.classList.add('hover');ring.classList.add('hover');});
el.addEventListener('mouseleave',function(){dot.classList.remove('hover');ring.classList.remove('hover');});
});
})();
</script>
</body>
</html>