mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
Compare commits
3
Commits
ca76bff96d
...
601851d164
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
601851d164 | ||
|
|
cc103cde03 | ||
|
|
38276dca93 |
@@ -4,6 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Page not found. Head back to the portfolio.">
|
||||
<link rel="canonical" href="https://milesallen.site/404.html">
|
||||
<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">
|
||||
@@ -102,6 +104,20 @@
|
||||
el.addEventListener('mouseenter',function(){dot.classList.add('hover');ring.classList.add('hover');});
|
||||
el.addEventListener('mouseleave',function(){dot.classList.remove('hover');ring.classList.remove('hover');});
|
||||
});
|
||||
|
||||
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;revealCursor();}}
|
||||
else{pos=(key===seq[0])?1:0;}
|
||||
});
|
||||
function revealCursor(){
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CommandMove: Cut & Paste on Mac | Miles Wolf Allen</title>
|
||||
<meta name="description" content="How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.">
|
||||
<link rel="canonical" href="https://milesallen.site/blog/command-move-devlog.html">
|
||||
<meta property="og:title" content="CommandMove: Cut & Paste on Mac | Miles Wolf Allen">
|
||||
<meta property="og:description" content="How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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="../projects/style.css">
|
||||
<style>
|
||||
.article-header{padding:100px 0 40px;}
|
||||
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
||||
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
||||
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
||||
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
||||
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
||||
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
||||
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
.article-body{max-width:720px;}
|
||||
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
||||
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
||||
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
||||
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
||||
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
||||
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
||||
.article-body ul li{margin-bottom:6px;}
|
||||
.article-body ul li::marker{color:var(--accent);}
|
||||
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
||||
.back-link:hover{color:var(--accent);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</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">FEDL</a>
|
||||
<a href="omniemu.html">OmniEmu</a>
|
||||
<a href="command-move.html">CommandMove</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="article-header">
|
||||
<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>
|
||||
<a href="../index.html#blog">Devlog</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<span>CommandMove</span>
|
||||
</div>
|
||||
<div class="article-eyebrow">// DEVLOG 003</div>
|
||||
<div class="article-date">2026</div>
|
||||
<h1>CommandMove: Cut & Paste on Mac</h1>
|
||||
<p class="article-tagline">macOS doesn't have Cmd+X for files. I fixed that. Global event taps, AppleScript, and a lot of Accessibility permissions.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Swift</span>
|
||||
<span class="article-tag">macOS</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="article-body">
|
||||
<h2>The Frustration</h2>
|
||||
<p>If you've ever used a Mac and tried to move files in Finder, you know the pain. You select files, press Cmd+C, navigate to a new folder, press Cmd+V... and the files are <strong>copied</strong>, not moved. The originals stay where they were.</p>
|
||||
<p>On Windows, Cmd+X (or Ctrl+X) cuts files, and Cmd+V moves them. It's been that way since the 90s. macOS only supports copy-paste for files. There is no cut. I don't know why Apple decided this was a good idea, but here we are.</p>
|
||||
<p>Every time I had to move files on my Mac, I'd copy them, navigate to the destination, paste, go back to the original, select the originals, and delete them. Four steps instead of two. Multiply that by hundreds of file operations and you start to lose your mind.</p>
|
||||
|
||||
<h2>The Solution</h2>
|
||||
<p>CommandMove is a menu bar app that intercepts Cmd+X and Cmd+V in Finder and makes them do what they should have always done: cut and move files.</p>
|
||||
<p>Press Cmd+X on selected files in Finder, navigate to any folder, press Cmd+V, and the files move. Just like Windows. Just like it should be.</p>
|
||||
|
||||
<h2>How It Works</h2>
|
||||
<p>Under the hood, CommandMove uses three macOS technologies:</p>
|
||||
<ul>
|
||||
<li><strong>Global Event Tap:</strong> Intercepts Cmd+X and Cmd+V keystrokes while Finder is the frontmost app</li>
|
||||
<li><strong>AppleScript:</strong> Queries Finder for the selected items and current folder</li>
|
||||
<li><strong>FileManager:</strong> Performs the actual move operation using <code>FileManager.moveItem</code></li>
|
||||
</ul>
|
||||
<p>The event tap is the trickiest part. macOS is very protective about who can intercept keyboard events. You need Accessibility permissions, which means the user has to go into System Settings > Privacy & Security > Accessibility and manually enable CommandMove. macOS will prompt for this on first launch, but it's still a friction point.</p>
|
||||
|
||||
<h2>The Accessibility Hurdle</h2>
|
||||
<p>Building for macOS means respecting the security model. Global event taps are powerful but regulated for good reason — they can theoretically capture any keystroke. Apple's solution is the Accessibility permission system, which requires explicit user consent.</p>
|
||||
<p>The first time you launch CommandMove, it pops up a dialog explaining why it needs Accessibility access. Some users are wary of this, and I don't blame them. The app is open source, so anyone can verify what it does. But "please give me permission to read your keyboard" is always going to be a hard sell.</p>
|
||||
|
||||
<h2>Extra Features</h2>
|
||||
<p>Once I had the core cut-and-paste working, I kept adding things:</p>
|
||||
<ul>
|
||||
<li>Right-click "Cut Files" action in Finder via a bundled Quick Action</li>
|
||||
<li>Menu bar controls for cutting, pasting, clearing the clipboard, and toggling login launch</li>
|
||||
<li>Auto-launch at login so it's always running</li>
|
||||
<li>Conflict handling — if a file with the same name exists at the destination, it creates a numbered copy automatically</li>
|
||||
</ul>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<ul>
|
||||
<li>Swift is a beautiful language once you get past the learning curve</li>
|
||||
<li>AppleScript is from 1993 and it shows, but it still works</li>
|
||||
<li>macOS Accessibility APIs are powerful but poorly documented</li>
|
||||
<li>The best utility apps solve one problem extremely well</li>
|
||||
<li>Open source builds trust, especially when you're asking for Accessibility permissions</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>The simplest solutions are often the ones that should have existed from the start.</blockquote>
|
||||
|
||||
<a href="../index.html#blog" class="back-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Back to Devlog
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
fetch('https://api.github.com/repos/mileswolfallen2/')
|
||||
.then(r => r.json())
|
||||
.then(d => {})
|
||||
.catch(() => {});
|
||||
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));
|
||||
</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);
|
||||
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
heroTl
|
||||
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
||||
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
||||
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
||||
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
||||
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
||||
document.querySelectorAll('.article-body h2').forEach(title => {
|
||||
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
||||
});
|
||||
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
||||
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
||||
});
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</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>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Running the FEDL | Miles Wolf Allen</title>
|
||||
<meta name="description" content="The story behind FEDL — running the Geometry Dash demon list, 3,800+ verified runs, and how a Discord conversation turned into a community.">
|
||||
<link rel="canonical" href="https://milesallen.site/blog/fedl-devlog.html">
|
||||
<meta property="og:title" content="Running the FEDL | Miles Wolf Allen">
|
||||
<meta property="og:description" content="The story behind FEDL — running the Geometry Dash demon list, 3,800+ verified runs, and how a Discord conversation turned into a community.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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="../projects/style.css">
|
||||
<style>
|
||||
.article-header{padding:100px 0 40px;}
|
||||
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
||||
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
||||
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
||||
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
||||
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
||||
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
||||
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
.article-body{max-width:720px;}
|
||||
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
||||
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
||||
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
||||
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
||||
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
||||
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
||||
.article-body ul li{margin-bottom:6px;}
|
||||
.article-body ul li::marker{color:var(--accent);}
|
||||
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
||||
.back-link:hover{color:var(--accent);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</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">FEDL</a>
|
||||
<a href="omniemu.html">OmniEmu</a>
|
||||
<a href="command-move.html">CommandMove</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="article-header">
|
||||
<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>
|
||||
<a href="../index.html#blog">Devlog</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<span>Running the FEDL</span>
|
||||
</div>
|
||||
<div class="article-eyebrow">// DEVLOG 002</div>
|
||||
<div class="article-date">2025</div>
|
||||
<h1>Running the FEDL</h1>
|
||||
<p class="article-tagline">3,800+ verified runs, ranked levels, and a community that never sleeps. Here's how a Discord conversation turned into a full-time project.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Node.js</span>
|
||||
<span class="article-tag">Community</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="article-body">
|
||||
<h2>How It Started</h2>
|
||||
<p>The FEDL didn't start with a plan. It started with a complaint.</p>
|
||||
<p>I was in a Discord server with a bunch of Geometry Dash players, and we were talking about the demon list — the community's ranking of the hardest levels in the game. The conversation kept circling back to one thing: there was a visible bias towards the top players. When the best players in the world verified a level, it shot up the rankings. When a lesser-known player struggled through the same level for months, it barely moved.</p>
|
||||
<blockquote>Someone said "we should just make our own list" and I thought "yeah, we should."</blockquote>
|
||||
<p>So I did. I spent a weekend building the first version of the FEDL — a simple list site with ranked levels and a submission form. I thought maybe 20 people would use it. I was very wrong.</p>
|
||||
|
||||
<h2>The Time Sink</h2>
|
||||
<p>Let me be honest: maintaining the FEDL is a massive time sink. We've had 3,839 verified runs submitted and approved. That's 3,839 individual submissions that needed to be reviewed, verified against video evidence, and either approved or rejected with feedback.</p>
|
||||
<p>There are 341 ranked levels, each with their own difficulty rating, creator credits, and verification history. The leaderboard tracks unique players, their best runs, and their progression over time. Behind all of that is a Node.js backend that processes submissions, a database that stores everything, and a frontend that needs to stay fast and accurate.</p>
|
||||
<p>Some weeks I spend 20+ hours on FEDL. Reviewing runs, updating rankings, fixing bugs, responding to community feedback. There were nights I'd be awake at 3am debugging a submission pipeline because someone in Australia submitted a run at a weird time and the system broke.</p>
|
||||
|
||||
<h2>Why It's Worth It</h2>
|
||||
<p>Here's the thing about building something for a community: you get back way more than you put in.</p>
|
||||
<p>The FEDL has over 3,800 YouTube videos made about it. Players screenshot their rankings and share them. People write guides about how to progress through the list. There are Discord bots that pull FEDL data. Speedrunners use it as a benchmark. It became part of the Geometry Dash ecosystem in a way I never expected.</p>
|
||||
<p>When someone messages me saying "I just beat my first FEDL level and it feels amazing," that's why I do it. The code is just code. The community is what matters.</p>
|
||||
|
||||
<h2>The Bias Fix</h2>
|
||||
<p>Going back to that original Discord conversation about bias — I think we actually fixed it. The FEDL levels are ranked by difficulty, not by who verified them. A level by an unknown player sits right next to a level by a top player if they're equally hard. The submission process is blind — I review the video evidence without knowing who submitted it (when possible).</p>
|
||||
<p>It's not perfect. Nothing ever is. But it's a fairer system than what existed before, and that matters.</p>
|
||||
|
||||
<h2>Technical Deep Dive</h2>
|
||||
<ul>
|
||||
<li>Backend: Node.js with Express, PostgreSQL database</li>
|
||||
<li>Frontend: Vanilla JS with GSAP animations</li>
|
||||
<li>API: RESTful endpoints for run submissions, level data, and leaderboard queries</li>
|
||||
<li>Video verification: Automated checks + manual review pipeline</li>
|
||||
<li>Stats: Real-time FEDL API tracking runs, levels, and unique players</li>
|
||||
</ul>
|
||||
|
||||
<h2>What's Next</h2>
|
||||
<p>The FEDL keeps growing. More players, more levels, more submissions. I'm working on better mobile support, a proper stats dashboard, and maybe even a mobile app someday. The community drives the project forward, and I'm just the person keeping the lights on.</p>
|
||||
|
||||
<blockquote>The best projects aren't the ones you plan. They're the ones you can't stop working on.</blockquote>
|
||||
|
||||
<a href="../index.html#blog" class="back-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Back to Devlog
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
fetch('https://api.github.com/repos/mileswolfallen2/')
|
||||
.then(r => r.json())
|
||||
.then(d => {})
|
||||
.catch(() => {});
|
||||
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));
|
||||
</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);
|
||||
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
heroTl
|
||||
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
||||
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
||||
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
||||
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
||||
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
||||
document.querySelectorAll('.article-body h2').forEach(title => {
|
||||
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
||||
});
|
||||
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
||||
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
||||
});
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</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>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,265 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Building Nova AI | Miles Wolf Allen</title>
|
||||
<meta name="description" content="How I built Nova AI — a local AI assistant with desktop GUI, web interface, and CLI. No cloud, no subscriptions, no data leaving your machine.">
|
||||
<link rel="canonical" href="https://milesallen.site/blog/nova-devlog.html">
|
||||
<meta property="og:title" content="Building Nova AI | Miles Wolf Allen">
|
||||
<meta property="og:description" content="How I built Nova AI — a local AI assistant with desktop GUI, web interface, and CLI. No cloud, no subscriptions, no data leaving your machine.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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="../projects/style.css">
|
||||
<style>
|
||||
.article-header{padding:100px 0 40px;}
|
||||
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
||||
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
||||
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
||||
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
||||
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
||||
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
||||
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
.article-body{max-width:720px;}
|
||||
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
||||
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
||||
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
||||
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
||||
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
||||
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
||||
.article-body ul li{margin-bottom:6px;}
|
||||
.article-body ul li::marker{color:var(--accent);}
|
||||
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
||||
.back-link:hover{color:var(--accent);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</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">FEDL</a>
|
||||
<a href="omniemu.html">OmniEmu</a>
|
||||
<a href="command-move.html">CommandMove</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="article-header">
|
||||
<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>
|
||||
<a href="../index.html#blog">Devlog</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<span>Building Nova AI</span>
|
||||
</div>
|
||||
<div class="article-eyebrow">// DEVLOG 004</div>
|
||||
<div class="article-date">2025</div>
|
||||
<h1>Building Nova AI</h1>
|
||||
<p class="article-tagline">Local AI that doesn't phone home. Desktop GUI, web interface, CLI — no subscriptions, no cloud, no nonsense.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Python</span>
|
||||
<span class="article-tag">AI</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="article-body">
|
||||
<h2>The Problem with AI Right Now</h2>
|
||||
<p>Every AI assistant today wants your data. ChatGPT, Claude, Gemini — they all send your conversations to their servers, store your prompts, and use your data to train their models. Some of them charge $20/month for the privilege.</p>
|
||||
<p>I wanted an AI that runs entirely on my machine. No cloud. No subscriptions. No data leaving my laptop. Just a local model that I can talk to, ask questions, and get answers from — without worrying about where my data goes.</p>
|
||||
|
||||
<h2>What Nova AI Is</h2>
|
||||
<p>The vision was always Jarvis. Not in a sci-fi movie way, but in a practical way — an AI that lives on your machine, knows your setup, and is always ready to help. You say something, it responds. No loading screens, no API rate limits, no "I'm sorry, I can't do that." Just a fast, local assistant that's always there.</p>
|
||||
<p>Nova AI is a local AI assistant with three interfaces:</p>
|
||||
<ul>
|
||||
<li><strong>Desktop GUI:</strong> A native desktop app for daily use</li>
|
||||
<li><strong>Web Interface:</strong> Access it from any browser on your network</li>
|
||||
<li><strong>CLI:</strong> For power users who live in the terminal</li>
|
||||
</ul>
|
||||
<p>All three interfaces connect to the same local backend. Your conversations stay on your machine. The model runs on your hardware. No API keys, no rate limits, no terms of service changes that break your workflow.</p>
|
||||
|
||||
<h2>Why Three Interfaces</h2>
|
||||
<p>Different situations call for different tools. When I'm at my desk, I use the desktop app. It's clean, fast, and has a nice chat interface. When I'm on my phone or another device on the same network, I open the web interface. And when I'm in the terminal working on code, I use the CLI to ask quick questions without context-switching.</p>
|
||||
<p>The web interface was especially important to me. I wanted to be able to access Nova from my phone while I'm away from my desk, but without setting up any cloud infrastructure. Just a local web server that's accessible on my LAN.</p>
|
||||
|
||||
<h2>The Tech Stack</h2>
|
||||
<ul>
|
||||
<li>Backend: Python with FastAPI</li>
|
||||
<li>AI Engine: Local model inference (no external API calls)</li>
|
||||
<li>Desktop: Native GUI framework</li>
|
||||
<li>Web: Vanilla frontend, WebSocket for streaming responses</li>
|
||||
<li>CLI: Python with Rich for pretty terminal output</li>
|
||||
</ul>
|
||||
|
||||
<h2>Privacy by Design</h2>
|
||||
<p>The biggest selling point of Nova isn't the features — it's the privacy. Every conversation, every prompt, every response stays on your machine. There's no telemetry, no analytics, no phone-home calls. The app doesn't even check for updates automatically.</p>
|
||||
<p>In a world where every app wants to collect your data, Nova is refreshingly different. It's an AI assistant that respects your privacy because it was designed that way from the start.</p>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<ul>
|
||||
<li>Local AI is getting more practical every month</li>
|
||||
<li>The hardest part isn't the AI — it's building good interfaces for it</li>
|
||||
<li>Users care more about privacy than most companies think</li>
|
||||
<li>Multiple interfaces for the same backend is more work but worth it</li>
|
||||
<li>The best AI is the one that's actually available when you need it</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>The best AI is the one that doesn't need an internet connection.</blockquote>
|
||||
|
||||
<a href="../index.html#blog" class="back-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Back to Devlog
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
fetch('https://api.github.com/repos/mileswolfallen2/')
|
||||
.then(r => r.json())
|
||||
.then(d => {})
|
||||
.catch(() => {});
|
||||
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));
|
||||
</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);
|
||||
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
heroTl
|
||||
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
||||
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
||||
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
||||
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
||||
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
||||
document.querySelectorAll('.article-body h2').forEach(title => {
|
||||
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
||||
});
|
||||
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
||||
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
||||
});
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</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>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Why I Built OmniEmu | Miles Wolf Allen</title>
|
||||
<meta name="description" content="The story behind OmniEmu 2.0 — why I built a cross-platform emulator manager because I was tired of opening 15 different apps.">
|
||||
<link rel="canonical" href="https://milesallen.site/blog/omniemu-devlog.html">
|
||||
<meta property="og:title" content="Why I Built OmniEmu | Miles Wolf Allen">
|
||||
<meta property="og:description" content="The story behind OmniEmu 2.0 — why I built a cross-platform emulator manager because I was tired of opening 15 different apps.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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="../projects/style.css">
|
||||
<style>
|
||||
.article-header{padding:100px 0 40px;}
|
||||
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
||||
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
||||
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
||||
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
||||
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
||||
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
||||
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
.article-body{max-width:720px;}
|
||||
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
||||
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
||||
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
||||
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
||||
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
||||
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
||||
.article-body ul li{margin-bottom:6px;}
|
||||
.article-body ul li::marker{color:var(--accent);}
|
||||
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
||||
.back-link:hover{color:var(--accent);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</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">FEDL</a>
|
||||
<a href="omniemu.html">OmniEmu</a>
|
||||
<a href="command-move.html">CommandMove</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="article-header">
|
||||
<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>
|
||||
<a href="../index.html#blog">Devlog</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<span>Why I Built OmniEmu</span>
|
||||
</div>
|
||||
<div class="article-eyebrow">// DEVLOG 001</div>
|
||||
<div class="article-date">2026</div>
|
||||
<h1>Why I Built OmniEmu</h1>
|
||||
<p class="article-tagline">I got tired of opening 15 different emulator apps. So I built one that manages them all.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">TypeScript</span>
|
||||
<span class="article-tag">Electron</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="article-body">
|
||||
<h2>The Problem</h2>
|
||||
<p>I play a lot of retro games. Like, a lot. And over the years I've collected a ridiculous number of emulators — Dolphin for GameCube, PCSX2 for PS2, PPSSPP for PSP, melonDS for DS, RetroArch for everything else. The problem wasn't that these emulators were bad. Most of them are incredible. The problem was that I had 15+ apps installed, each with their own UI, their own update cycle, and their own way of handling things.</p>
|
||||
<p>Every time I wanted to play a game, I had to remember which emulator handled which system, open that specific app, navigate its interface, find my ROM, and then launch it. On a Tuesday night when I just wanted to play something, this was exhausting.</p>
|
||||
|
||||
<h2>The Idea</h2>
|
||||
<p>The idea was simple: one app to rule them all. A single place where I could see all my emulators, all my ROMs, and launch anything with one click. No more guessing which emulator handles PS2. No more opening RetroArch just to find out it doesn't have the right core installed.</p>
|
||||
<p>But it went further than just launching. I wanted the app to <strong>install</strong> emulators for me. On Mac especially, setting up emulators is a pain. You download a DMG, drag it to Applications, figure out which core to install, configure paths... it's a whole thing. I wanted one-click install. Download, configure, done.</p>
|
||||
|
||||
<h2>The Mac Problem</h2>
|
||||
<p>This is where OmniEmu really shines. On Windows, you can just download an exe and run it. On Mac? It's a nightmare. Half the emulators don't have native Mac builds. The ones that do require Homebrew or manual compilation. RPCS3 doesn't even work on Mac. And don't get me started on trying to get Wii U emulators running.</p>
|
||||
<p>I built OmniEmu specifically because there was nothing out there that handled this gracefully. EmuDeck is great for Steam Deck. LaunchBox is Windows-only and costs money. RetroArch is... RetroArch. Nobody was solving the "I just want to click install and play" problem on Mac.</p>
|
||||
|
||||
<h2>What It Became</h2>
|
||||
<p>What started as "I'll just make a quick launcher" turned into a full Electron app with TypeScript, a custom ROM library scanner, game art scraping from SteamGridDB, RetroAchievements integration, controller navigation, cloud save sync via Syncthing, and support for 20+ emulators across three platforms.</p>
|
||||
<p>Sometimes the best projects come from the most selfish reasons. I just wanted to play my games without opening 15 apps. Now everyone can.</p>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<ul>
|
||||
<li>Electron gets a lot of hate, but it genuinely works for cross-platform desktop apps</li>
|
||||
<li>AppleScript is both powerful and infuriating at the same time</li>
|
||||
<li>Building for Mac means dealing with Accessibility permissions, code signing, and notarization</li>
|
||||
<li>The hardest part isn't the code — it's keeping up with emulator updates across 20+ projects</li>
|
||||
<li>User feedback is worth more than any design document</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>The best tool is the one you build because nothing else does what you need.</blockquote>
|
||||
|
||||
<a href="../index.html#blog" class="back-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Back to Devlog
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
fetch('https://api.github.com/repos/mileswolfallen2/')
|
||||
.then(r => r.json())
|
||||
.then(d => {})
|
||||
.catch(() => {});
|
||||
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));
|
||||
</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);
|
||||
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
heroTl
|
||||
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
||||
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
||||
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
||||
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
||||
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
||||
document.querySelectorAll('.article-body h2').forEach(title => {
|
||||
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
||||
});
|
||||
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
||||
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
||||
});
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</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>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+322
@@ -4,6 +4,20 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Miles Wolf Allen | Developer & 3D Artist</title>
|
||||
<meta name="description" content="Portfolio of Miles Wolf Allen — developer, 3D artist, and builder of emulators, games, and tools. Check out my projects on GitHub.">
|
||||
<meta name="keywords" content="Miles Wolf Allen, developer, portfolio, emulator, Geometry Dash, FEDL, OmniEmu, Swift, TypeScript">
|
||||
<meta name="author" content="Miles Wolf Allen">
|
||||
<link rel="canonical" href="https://milesallen.site/">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Miles Wolf Allen | Developer & 3D Artist">
|
||||
<meta property="og:description" content="Portfolio of Miles Wolf Allen — developer, 3D artist, and builder of emulators, games, and tools.">
|
||||
<meta property="og:url" content="https://milesallen.site/">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<meta property="og:site_name" content="Miles Wolf Allen">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Miles Wolf Allen | Developer & 3D Artist">
|
||||
<meta name="twitter:description" content="Portfolio of Miles Wolf Allen — developer, 3D artist, and builder of emulators, games, and tools.">
|
||||
<meta name="twitter:image" content="https://milesallen.site/og.png">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<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">
|
||||
@@ -306,6 +320,111 @@
|
||||
|
||||
.reveal{opacity:0;transform:translateY(24px);}
|
||||
|
||||
.progress-bar{position:fixed;top:0;left:0;height:3px;background:var(--accent);z-index:10001;width:0;transition:width .1s linear;box-shadow:0 0 8px var(--accent);}
|
||||
|
||||
body.light{
|
||||
--bg:#f5f5f0;--text:#111;--muted:#888;
|
||||
}
|
||||
body.light .scattered-nav a{color:#888;}
|
||||
body.light .scattered-nav a:hover{color:#111;border-color:var(--accent);background:rgba(255,61,154,0.1);}
|
||||
body.light .proj-card{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .proj-desc{color:#555;}
|
||||
body.light .stat-strip .cell{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .about-card{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .about-card p{color:#444;}
|
||||
body.light .contact-card{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .contact-row span.k{color:#999;}
|
||||
body.light .hl-cell{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .hl-label{color:#888;}
|
||||
body.light .chip{border-color:rgba(0,0,0,0.15);}
|
||||
body.light .chip.learning{color:#aaa;}
|
||||
body.light .section-sub{color:#888;}
|
||||
body.light .marquee{background:var(--tape);}
|
||||
body.light .marquee span{color:#000;}
|
||||
body.light .toast{background:#eee;border-color:#333;}
|
||||
body.light .toast .title{color:#333;}
|
||||
body.light .toast{color:#333;}
|
||||
|
||||
.theme-toggle{
|
||||
position:fixed;top:20px;right:20px;z-index:600;
|
||||
background:none;border:2px solid rgba(255,255,255,0.15);
|
||||
width:40px;height:40px;display:flex;align-items:center;justify-content:center;
|
||||
font-size:16px;cursor:none;transition:all .3s;color:var(--text);
|
||||
}
|
||||
.theme-toggle:hover{border-color:var(--accent);background:rgba(255,61,154,0.1);}
|
||||
@media(max-width:720px){.theme-toggle{display:none;}}
|
||||
|
||||
.shortcut-modal{
|
||||
position:fixed;inset:0;z-index:10002;background:rgba(0,0,0,0.85);
|
||||
display:none;align-items:center;justify-content:center;backdrop-filter:blur(6px);
|
||||
}
|
||||
.shortcut-modal.open{display:flex;}
|
||||
.shortcut-card{
|
||||
background:#111;border:2px solid var(--accent);padding:32px;max-width:400px;width:90%;
|
||||
font-family:var(--font-mono);font-size:12px;
|
||||
}
|
||||
.shortcut-card h3{font-family:var(--font-pixel);font-size:11px;color:var(--accent);margin-bottom:20px;text-transform:uppercase;}
|
||||
.shortcut-row{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.08);color:var(--muted);}
|
||||
.shortcut-row kbd{background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.15);padding:2px 8px;font-family:var(--font-mono);font-size:11px;color:var(--text);}
|
||||
.shortcut-card .close-hint{margin-top:16px;font-size:10px;color:var(--muted);text-align:center;}
|
||||
|
||||
.bg-gradient{
|
||||
position:fixed;inset:0;pointer-events:none;z-index:-1;
|
||||
background:radial-gradient(ellipse at 20% 50%,rgba(255,61,154,0.06) 0%,transparent 50%),
|
||||
radial-gradient(ellipse at 80% 20%,rgba(255,201,60,0.04) 0%,transparent 50%),
|
||||
radial-gradient(ellipse at 50% 80%,rgba(255,61,154,0.03) 0%,transparent 50%);
|
||||
transition:filter 2s ease;
|
||||
}
|
||||
|
||||
.proj-title{position:relative;}
|
||||
.proj-card:hover .proj-title{
|
||||
animation:titleGlitch .3s steps(2) infinite;
|
||||
}
|
||||
@keyframes titleGlitch{
|
||||
0%{text-shadow:2px 0 var(--accent),-2px 0 var(--tape);}
|
||||
25%{text-shadow:-2px 0 var(--red),2px 0 var(--accent);}
|
||||
50%{text-shadow:2px 0 var(--tape),-2px 0 var(--red);}
|
||||
75%{text-shadow:-2px 0 var(--accent),2px 0 var(--tape);}
|
||||
100%{text-shadow:2px 0 var(--red),-2px 0 var(--accent);}
|
||||
}
|
||||
|
||||
.section-title .corrupt{opacity:0;position:absolute;left:0;top:0;}
|
||||
.section-title{position:relative;}
|
||||
|
||||
.blog-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:20px;}
|
||||
.blog-card{
|
||||
background:rgba(255,255,255,0.02);border:2px solid rgba(255,255,255,0.12);
|
||||
padding:24px;transition:all .3s;text-decoration:none;color:inherit;display:block;
|
||||
}
|
||||
.blog-card:nth-child(odd){transform:rotate(-1deg) translateY(4px);}
|
||||
.blog-card:nth-child(even){transform:rotate(0.8deg) translateY(-6px);}
|
||||
.blog-card:hover{transform:rotate(0deg) translateY(-5px);border-color:var(--accent);box-shadow:0 0 25px rgba(255,61,154,0.15);}
|
||||
.blog-date{font-family:var(--font-pixel);font-size:8px;color:var(--tape);margin-bottom:10px;text-transform:uppercase;}
|
||||
.blog-title{font-family:var(--font-mono);font-weight:700;font-size:16px;margin-bottom:8px;text-transform:uppercase;}
|
||||
.blog-excerpt{font-size:13px;color:#999;line-height:1.7;font-family:var(--font-mono);margin-bottom:14px;}
|
||||
.blog-tags{display:flex;gap:6px;flex-wrap:wrap;}
|
||||
.blog-tag{font-family:var(--font-pixel);font-size:7px;padding:4px 8px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
|
||||
@media(max-width:720px){
|
||||
.blog-grid{grid-template-columns:1fr;}
|
||||
.blog-card{transform:none!important;padding:20px;}
|
||||
}
|
||||
|
||||
.parallax-card{will-change:transform;}
|
||||
|
||||
.easter-toast{
|
||||
position:fixed;inset:0;z-index:10003;background:rgba(0,0,0,0.9);
|
||||
display:flex;align-items:center;justify-content:center;pointer-events:none;
|
||||
}
|
||||
.easter-text{
|
||||
font-family:var(--font-pixel);font-size:clamp(14px,3vw,24px);color:var(--accent);
|
||||
text-align:center;max-width:600px;padding:20px;
|
||||
}
|
||||
.easter-text .typed-char{opacity:0;display:inline-block;animation:typeIn .05s forwards;}
|
||||
@keyframes typeIn{to{opacity:1;}}
|
||||
.easter-text .cursor-blink{display:inline-block;width:3px;height:1em;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:middle;}
|
||||
@keyframes blink{0%,100%{opacity:1;}50%{opacity:0;}}
|
||||
|
||||
.mobile-nav{
|
||||
display:none;position:fixed;bottom:0;left:0;right:0;z-index:500;
|
||||
background:rgba(0,0,0,0.92);border-top:2px solid rgba(255,255,255,0.12);
|
||||
@@ -392,6 +511,24 @@
|
||||
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<div class="bg-gradient" id="bgGradient"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go to About</span><kbd>1</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to Skills</span><kbd>2</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to Projects</span><kbd>3</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to Highlights</span><kbd>4</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to Devlog</span><kbd>5</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to Contact</span><kbd>6</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="shortcut-row"><span>Konami code</span><kbd>↑↑↓↓←→←→BA</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vignette"></div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="signal" id="signal"></div>
|
||||
@@ -605,6 +742,38 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="blog">
|
||||
<div class="eyebrow">LEVEL 06 // DEVLOG</div>
|
||||
<h2 class="section-title reveal">Devlog</h2>
|
||||
<p class="section-sub reveal">things I've learned, broken, and fixed</p>
|
||||
<div class="blog-grid">
|
||||
<a class="blog-card reveal" href="blog/omniemu-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">Why I Built OmniEmu</div>
|
||||
<div class="blog-excerpt">I got tired of opening 15 different emulator apps. So I built one that manages them all. Here's how it started.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">TypeScript</span><span class="blog-tag">Electron</span></div>
|
||||
</a>
|
||||
<a class="blog-card reveal" href="blog/command-move-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">CommandMove: Cut & Paste on Mac</div>
|
||||
<div class="blog-excerpt">macOS doesn't have Cmd+X for files. I fixed that. Global event taps, AppleScript, and a lot of Accessibility permissions.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Swift</span><span class="blog-tag">macOS</span></div>
|
||||
</a>
|
||||
<a class="blog-card reveal" href="blog/fedl-devlog.html">
|
||||
<div class="blog-date">2025</div>
|
||||
<div class="blog-title">Running the FEDL</div>
|
||||
<div class="blog-excerpt">What it takes to maintain a Geometry Dash demon list. 3,800+ verified runs, ranked levels, and a community that never sleeps.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Node.js</span><span class="blog-tag">Community</span></div>
|
||||
</a>
|
||||
<a class="blog-card reveal" href="blog/nova-devlog.html">
|
||||
<div class="blog-date">2025</div>
|
||||
<div class="blog-title">Building Nova AI</div>
|
||||
<div class="blog-excerpt">Local AI that doesn't phone home. Desktop GUI, web interface, CLI — no subscriptions, no cloud, no nonsense.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Python</span><span class="blog-tag">AI</span></div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact">
|
||||
<div class="eyebrow">LEVEL 05 // REACH OUT</div>
|
||||
<h2 class="section-title reveal">Get In Touch</h2>
|
||||
@@ -739,6 +908,159 @@ function setActive(id){
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='1')document.getElementById('about').scrollIntoView({behavior:'smooth'});
|
||||
if(e.key==='2')document.getElementById('skills').scrollIntoView({behavior:'smooth'});
|
||||
if(e.key==='3')document.getElementById('projects').scrollIntoView({behavior:'smooth'});
|
||||
if(e.key==='4')document.getElementById('highlights').scrollIntoView({behavior:'smooth'});
|
||||
if(e.key==='5')document.getElementById('blog').scrollIntoView({behavior:'smooth'});
|
||||
if(e.key==='6')document.getElementById('contact').scrollIntoView({behavior:'smooth'});
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var grad=document.getElementById('bgGradient');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight);
|
||||
var hue=pct*30;
|
||||
grad.style.filter='hue-rotate('+hue+'deg)';
|
||||
},{passive:true});
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var titles=document.querySelectorAll('h2.section-title');
|
||||
var glitchChars='!@#$%^&*()_+{}|:<>?/~`';
|
||||
titles.forEach(function(title){
|
||||
var original=title.textContent;
|
||||
setInterval(function(){
|
||||
if(Math.random()>0.92){
|
||||
var arr=original.split('');
|
||||
var i=Math.floor(Math.random()*arr.length);
|
||||
arr[i]=glitchChars[Math.floor(Math.random()*glitchChars.length)];
|
||||
title.textContent=arr.join('');
|
||||
setTimeout(function(){title.textContent=original;},100);
|
||||
}
|
||||
},200);
|
||||
});
|
||||
})();
|
||||
|
||||
(function(){
|
||||
var cards=document.querySelectorAll('.proj-card');
|
||||
if('IntersectionObserver' in window){
|
||||
var obs=new IntersectionObserver(function(entries){
|
||||
entries.forEach(function(e){
|
||||
if(e.isIntersecting){e.target.style.opacity='1';e.target.style.transform='';obs.unobserve(e.target);}
|
||||
});
|
||||
},{threshold:0.1});
|
||||
cards.forEach(function(c){c.style.opacity='0';obs.observe(c);});
|
||||
}
|
||||
})();
|
||||
|
||||
gsap.utils.toArray('.proj-card, .blog-card, .hl-cell').forEach(function(card){
|
||||
card.classList.add('parallax-card');
|
||||
gsap.to(card,{
|
||||
y:'random(-12,12)',x:'random(-5,5)',rotation:'random(-1.5,1.5)',
|
||||
ease:'none',
|
||||
scrollTrigger:{trigger:card,start:'top bottom',end:'bottom top',scrub:1}
|
||||
});
|
||||
});
|
||||
|
||||
(function(){
|
||||
var logo=document.querySelector('.sn-logo');
|
||||
var clickCount=0;
|
||||
var resetTimer;
|
||||
var messages=[
|
||||
'You found the secret.',
|
||||
'147 repos. 91 stars. Infinite bugs.',
|
||||
'Built at 2am. Still awake.',
|
||||
'Coffee count: unknown. Productivity: questionable.',
|
||||
'This website has more features than most of my projects.',
|
||||
'Congratulations. You have too much free time.',
|
||||
'The matrix has you.',
|
||||
'Reality is broken. Have a nice day.'
|
||||
];
|
||||
var msgIndex=0;
|
||||
if(!logo)return;
|
||||
logo.addEventListener('click',function(e){
|
||||
e.preventDefault();
|
||||
clickCount++;
|
||||
clearTimeout(resetTimer);
|
||||
resetTimer=setTimeout(function(){clickCount=0;},2000);
|
||||
if(clickCount>=10){
|
||||
clickCount=0;
|
||||
showEasterEgg();
|
||||
}
|
||||
});
|
||||
function showEasterEgg(){
|
||||
var overlay=document.createElement('div');
|
||||
overlay.className='easter-toast';
|
||||
var textDiv=document.createElement('div');
|
||||
textDiv.className='easter-text';
|
||||
overlay.appendChild(textDiv);
|
||||
document.body.appendChild(overlay);
|
||||
var msg=messages[msgIndex%messages.length];
|
||||
msgIndex++;
|
||||
var cursor=document.createElement('span');
|
||||
cursor.className='cursor-blink';
|
||||
textDiv.appendChild(cursor);
|
||||
var i=0;
|
||||
function typeChar(){
|
||||
if(i<msg.length){
|
||||
var span=document.createElement('span');
|
||||
span.className='typed-char';
|
||||
span.style.animationDelay=(i*0.04)+'s';
|
||||
span.textContent=msg[i];
|
||||
textDiv.insertBefore(span,cursor);
|
||||
i++;
|
||||
setTimeout(typeChar,40);
|
||||
}else{
|
||||
setTimeout(function(){
|
||||
gsap.to(overlay,{opacity:0,duration:0.8,onComplete:function(){overlay.remove();}});
|
||||
},2500);
|
||||
}
|
||||
}
|
||||
typeChar();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CommandMove | Miles Wolf Allen</title>
|
||||
<meta name="description" content="CommandMove — true cut-and-paste for Finder on macOS. Select files, Cmd+X, navigate, Cmd+V.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/command-move.html">
|
||||
<meta property="og:title" content="CommandMove | Miles Wolf Allen">
|
||||
<meta property="og:description" content="True cut-and-paste for Finder on macOS. Select files, Cmd+X, navigate, Cmd+V.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -120,6 +137,29 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">How It Compares</h2>
|
||||
<p class="section-subtitle">CommandMove vs alternatives</p>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Feature</th><th>CommandMove</th><th>macOS Finder</th><th>Path Finder</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Cmd+X cut files</td><td class="check">Yes</td><td class="cross">No</td><td class="check">Yes</td></tr>
|
||||
<tr><td>Cmd+V move files</td><td class="check">Yes</td><td class="cross">No</td><td class="check">Yes</td></tr>
|
||||
<tr><td>Right-click cut</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td></tr>
|
||||
<tr><td>Menu bar controls</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td></tr>
|
||||
<tr><td>Auto-launch at login</td><td class="check">Yes</td><td class="cross">N/A</td><td class="cross">No</td></tr>
|
||||
<tr><td>Conflict handling</td><td class="check">Auto-numbered copies</td><td class="cross">N/A</td><td class="check">Prompt</td></tr>
|
||||
<tr><td>Free & open source</td><td class="check">Yes</td><td class="check">Built-in</td><td class="cross">$99</td></tr>
|
||||
<tr><td>Lightweight</td><td class="check">Yes</td><td class="check">Yes</td><td class="cross">Heavy</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">How It Works</h2>
|
||||
@@ -257,6 +297,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -278,5 +322,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Decompilations | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Decompilations — reverse engineering and decompilation projects for game engines.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/decompilations.html">
|
||||
<meta property="og:title" content="Decompilations | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Reverse engineering and decompilation projects for game engines.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -244,6 +261,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -265,5 +286,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FEDL | Miles Wolf Allen</title>
|
||||
<meta name="description" content="FEDL — the demon list for Geometry Dash. Ranked levels, run submissions, and community leaderboards.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/fedl.html">
|
||||
<meta property="og:title" content="FEDL | Miles Wolf Allen">
|
||||
<meta property="og:description" content="The demon list for Geometry Dash. Ranked levels, run submissions, and community leaderboards.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -75,6 +92,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">Live Demo</h2>
|
||||
<p class="section-subtitle">Browse the list right now</p>
|
||||
<div style="position:relative;width:calc(100% + 10vw);margin:0 -5vw;padding-bottom:56.25%;border:2px solid rgba(255,255,255,0.12);overflow:hidden;">
|
||||
<iframe src="https://fedl.site" style="position:absolute;top:0;left:0;width:100%;height:100%;border:none;" loading="lazy" allowfullscreen></iframe>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">What It Includes</h2>
|
||||
@@ -314,6 +340,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -335,5 +365,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FEDL Geode Mod | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Geode Mod — a mod for Geometry Dash built with the Geode mod loader.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/geode-mod.html">
|
||||
<meta property="og:title" content="Geode Mod | Miles Wolf Allen">
|
||||
<meta property="og:description" content="A mod for Geometry Dash built with the Geode mod loader.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -276,6 +293,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -297,5 +318,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Issue Tracker | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Issue Tracker — a clean, minimal issue tracking app for managing bugs and tasks.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/issue-tracker.html">
|
||||
<meta property="og:title" content="Issue Tracker | Miles Wolf Allen">
|
||||
<meta property="og:description" content="A clean, minimal issue tracking app for managing bugs and tasks.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -241,6 +258,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -262,5 +283,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Moon Gaming | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Moon Gaming — a browser-based gaming platform with retro games and leaderboards.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/moon-gaming.html">
|
||||
<meta property="og:title" content="Moon Gaming | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Browser-based gaming platform with retro games and leaderboards.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -262,6 +279,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -283,5 +304,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Music App | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Music App — a music player that doesn't interrupt you with ads. Inspired by YouTube Music without the annoying parts.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/music-app.html">
|
||||
<meta property="og:title" content="Music App | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Music player without ads. Inspired by YouTube Music without the annoying parts.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -261,6 +278,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -282,5 +303,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Noto | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Noto — note-taking app with AI built in. Built with Flutter and powered by Cortex AI.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/noto.html">
|
||||
<meta property="og:title" content="Noto | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Note-taking app with AI built in. Built with Flutter, powered by Cortex AI.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -307,6 +324,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -328,5 +349,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nova AI | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Nova AI — AI assistant that runs on your machine. Desktop GUI, web interface, and CLI. No cloud, no subscriptions.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/nova.html">
|
||||
<meta property="og:title" content="Nova AI | Miles Wolf Allen">
|
||||
<meta property="og:description" content="AI assistant that runs on your machine. Desktop GUI, web interface, and CLI. No cloud.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -290,6 +307,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -311,5 +332,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OmniEmu 2.0 | Miles Wolf Allen</title>
|
||||
<meta name="description" content="OmniEmu 2.0 — cross-platform emulator manager, game launcher, and ROM library for Windows, macOS, and Linux. Install 20+ emulators with one click.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/omniemu.html">
|
||||
<meta property="og:title" content="OmniEmu 2.0 | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Cross-platform emulator manager, game launcher, and ROM library. Install 20+ emulators with one click.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -177,6 +194,30 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">How It Compares</h2>
|
||||
<p class="section-subtitle">OmniEmu vs other emulator managers</p>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Feature</th><th>OmniEmu</th><th>EmuDeck</th><th>LaunchBox</th><th>RetroArch</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Cross-platform</td><td class="check">Win/Mac/Linux</td><td class="check">Win/Mac/Linux/SteamOS</td><td class="cross">Windows only</td><td class="check">Win/Mac/Linux</td></tr>
|
||||
<tr><td>One-click emulator install</td><td class="check">Yes</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td></tr>
|
||||
<tr><td>ROM library browsing</td><td class="check">Yes</td><td class="cross">No</td><td class="check">Yes</td><td class="cross">No</td></tr>
|
||||
<tr><td>Auto game art scraping</td><td class="check">Yes</td><td class="check">Yes</td><td class="check">Paid</td><td class="cross">No</td></tr>
|
||||
<tr><td>RetroAchievements</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td><td class="check">Yes</td></tr>
|
||||
<tr><td>Controller navigation</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td><td class="check">Yes</td></tr>
|
||||
<tr><td>Cloud save sync</td><td class="check">Yes</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td></tr>
|
||||
<tr><td>Decomp project installer</td><td class="check">Yes</td><td class="cross">No</td><td class="cross">No</td><td class="cross">No</td></tr>
|
||||
<tr><td>Free & open source</td><td class="check">Yes</td><td class="check">Yes</td><td class="cross">Paid</td><td class="check">Yes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="section-divider"></div>
|
||||
<h2 class="section-title">Tech Stack</h2>
|
||||
@@ -325,6 +366,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -346,5 +391,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rhythm Game | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Rhythm Game — hit keys to the beat with real-time notes and keyboard controls.">
|
||||
<link rel="canonical" href="https://milesallen.site/projects/rhythm-game.html">
|
||||
<meta property="og:title" content="Rhythm Game | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Hit keys to the beat. Song selection, real-time notes, and keyboard controls.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<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">
|
||||
@@ -12,6 +17,18 @@
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
@@ -255,6 +272,10 @@
|
||||
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});}});
|
||||
document.body.style.cursor='auto';
|
||||
var style=document.createElement('style');style.id='konami-cursor';
|
||||
style.textContent='*{cursor:auto !important;}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -276,5 +297,44 @@
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(){
|
||||
var bar=document.getElementById('progressBar');
|
||||
window.addEventListener('scroll',function(){
|
||||
var pct=window.scrollY/(document.body.scrollHeight-window.innerHeight)*100;
|
||||
bar.style.width=pct+'%';
|
||||
},{passive:true});
|
||||
})();
|
||||
(function(){
|
||||
var btn=document.getElementById('themeToggle');
|
||||
var saved=localStorage.getItem('theme');
|
||||
if(saved==='light')document.body.classList.add('light');
|
||||
function updateIcon(){btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';}
|
||||
updateIcon();
|
||||
btn.addEventListener('click',function(){
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
updateIcon();
|
||||
});
|
||||
})();
|
||||
(function(){
|
||||
var modal=document.getElementById('shortcutModal');
|
||||
function toggleModal(){modal.classList.toggle('open');}
|
||||
window.addEventListener('keydown',function(e){
|
||||
if(e.key==='?'){e.preventDefault();toggleModal();}
|
||||
if(e.key==='Escape')modal.classList.remove('open');
|
||||
if(e.key==='t'||e.key==='T'){
|
||||
if(document.activeElement&&document.activeElement.tagName==='INPUT')return;
|
||||
document.body.classList.toggle('light');
|
||||
localStorage.setItem('theme',document.body.classList.contains('light')?'light':'dark');
|
||||
var btn=document.getElementById('themeToggle');
|
||||
btn.textContent=document.body.classList.contains('light')?'\u263E':'\u2600';
|
||||
}
|
||||
if(e.key==='h'||e.key==='H')window.location.href='../index.html';
|
||||
if(e.key==='g'||e.key==='G')window.location.href='https://github.com/mileswolfallen2';
|
||||
});
|
||||
modal.addEventListener('click',function(e){if(e.target===modal)modal.classList.remove('open');});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+61
-1
@@ -121,9 +121,20 @@ section{padding:48px 0;}
|
||||
position:relative;transform:rotate(-2deg);
|
||||
}
|
||||
|
||||
.section-title{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);letter-spacing:.01em;margin-bottom:10px;transform:rotate(-1deg);transform-origin:left center;}
|
||||
.section-title{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);letter-spacing:.01em;margin-bottom:10px;transform:rotate(-1deg);transform-origin:left center;position:relative;}
|
||||
.section-subtitle{color:var(--muted);font-size:14px;margin-bottom:28px;line-height:1.6;font-family:var(--font-mono);}
|
||||
|
||||
.feature-card h3:hover{
|
||||
animation:titleGlitch .3s steps(2) infinite;
|
||||
}
|
||||
@keyframes titleGlitch{
|
||||
0%{text-shadow:2px 0 var(--accent),-2px 0 var(--tape);}
|
||||
25%{text-shadow:-2px 0 var(--red),2px 0 var(--accent);}
|
||||
50%{text-shadow:2px 0 var(--tape),-2px 0 var(--red);}
|
||||
75%{text-shadow:-2px 0 var(--accent),2px 0 var(--tape);}
|
||||
100%{text-shadow:2px 0 var(--red),-2px 0 var(--accent);}
|
||||
}
|
||||
|
||||
/* FEATURE CARDS */
|
||||
.feature-grid{display:flex;flex-direction:column;gap:16px;margin-left:-2vw;margin-right:-2vw;}
|
||||
.feature-card{
|
||||
@@ -206,6 +217,55 @@ footer a{color:var(--accent);}
|
||||
.reveal{opacity:0;transform:translateY(24px);}
|
||||
.reveal.visible{opacity:1;transform:none;transition:opacity .6s ease,transform .6s ease;}
|
||||
|
||||
/* PROGRESS BAR */
|
||||
.progress-bar{position:fixed;top:0;left:0;height:3px;background:var(--accent);z-index:10001;width:0;transition:width .1s linear;box-shadow:0 0 8px var(--accent);}
|
||||
|
||||
/* THEME TOGGLE */
|
||||
body.light{--bg:#f5f5f0;--text:#111;--muted:#888;}
|
||||
body.light .nav-links a{color:#888;}
|
||||
body.light .project-hero h1{color:#111;}
|
||||
body.light .feature-card{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .feature-card p{color:#555;}
|
||||
body.light .stat-item{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .stat-value{color:#111;}
|
||||
body.light .tech-badge{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.15);}
|
||||
body.light .desc-text{color:#555;}
|
||||
body.light .section-title{color:#111;}
|
||||
body.light .section-subtitle{color:#888;}
|
||||
body.light .tagline{color:#555;}
|
||||
body.light .toast{background:#eee;border-color:#333;color:#333;}
|
||||
body.light .toast .title{color:#333;}
|
||||
body.light footer{color:#888;border-color:rgba(0,0,0,0.15);}
|
||||
body.light .breadcrumb{color:#888;}
|
||||
|
||||
.theme-toggle{
|
||||
position:fixed;top:20px;right:20px;z-index:600;
|
||||
background:none;border:2px solid rgba(255,255,255,0.15);
|
||||
width:40px;height:40px;display:flex;align-items:center;justify-content:center;
|
||||
font-size:16px;cursor:none;transition:all .3s;color:var(--text);
|
||||
}
|
||||
.theme-toggle:hover{border-color:var(--accent);background:rgba(255,61,154,0.1);}
|
||||
@media(max-width:640px){.theme-toggle{display:none;}}
|
||||
|
||||
/* SHORTCUT MODAL */
|
||||
.shortcut-modal{
|
||||
position:fixed;inset:0;z-index:10002;background:rgba(0,0,0,0.85);
|
||||
display:none;align-items:center;justify-content:center;backdrop-filter:blur(6px);
|
||||
}
|
||||
.shortcut-modal.open{display:flex;}
|
||||
.shortcut-card{
|
||||
background:#111;border:2px solid var(--accent);padding:32px;max-width:400px;width:90%;
|
||||
font-family:var(--font-mono);font-size:12px;
|
||||
}
|
||||
.shortcut-card h3{font-family:var(--font-pixel);font-size:11px;color:var(--accent);margin-bottom:20px;text-transform:uppercase;}
|
||||
.shortcut-row{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.08);color:var(--muted);}
|
||||
.shortcut-row kbd{background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.15);padding:2px 8px;font-family:var(--font-mono);font-size:11px;color:var(--text);}
|
||||
.shortcut-card .close-hint{margin-top:16px;font-size:10px;color:var(--muted);text-align:center;}
|
||||
|
||||
/* GLITCH TITLE */
|
||||
.section-title{position:relative;}
|
||||
.feature-card h3{position:relative;}
|
||||
|
||||
/* MOBILE NAV */
|
||||
.mobile-nav{
|
||||
display:none;position:fixed;bottom:0;left:0;right:0;z-index:500;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /node_modules/
|
||||
Disallow: /.git/
|
||||
|
||||
Sitemap: https://milesallen.site/sitemap.xml
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://milesallen.site/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/omniemu.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/fedl.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/noto.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/nova.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/music-app.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/rhythm-game.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/geode-mod.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/decompilations.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/issue-tracker.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/moon-gaming.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/projects/command-move.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
Reference in New Issue
Block a user