mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
Compare commits
14
Commits
b053be846d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2d90a662e | ||
|
|
8dfbbd22a8 | ||
|
|
70f77f4283 | ||
|
|
6efbc83175 | ||
|
|
eeb6be0de9 | ||
|
|
755815e61a | ||
|
|
eeeac7c4f7 | ||
|
|
43ccb48009 | ||
|
|
e3611c053c | ||
|
|
d27792b813 | ||
|
|
d8a9d74912 | ||
|
|
3d731ebd35 | ||
|
|
9be085d251 | ||
|
|
295d2c51a7 |
@@ -0,0 +1 @@
|
||||
share-server/node_modules
|
||||
@@ -0,0 +1,374 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Avengers: Doomsday Watchlist</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #0d0f12;
|
||||
--card-bg: #15171c;
|
||||
--border-color: #24282e;
|
||||
--text-main: #c6cbd2;
|
||||
--text-muted: #7a828c;
|
||||
--accent: #b85a5a;
|
||||
--accent-hover: #9c4646;
|
||||
--success: #5aa877;
|
||||
--hover-bg: #1a1d23;
|
||||
--inner-bg: #191c22;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-main);
|
||||
margin: 0;
|
||||
padding: 2.5rem 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 580px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 2rem 2rem 1.75rem;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #e8eaed;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
p.subtitle {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
background-color: #20242b;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.5rem;
|
||||
height: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background-color: var(--accent);
|
||||
width: 0%;
|
||||
border-radius: 999px;
|
||||
transition: width 0.35s ease;
|
||||
}
|
||||
|
||||
.counter {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--hover-bg);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.7rem 0.5rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border: 1.5px solid #4a515a;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin-right: 0.85rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:hover {
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
background-color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::after {
|
||||
content: '✓';
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
user-select: none;
|
||||
flex-grow: 1;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked + label {
|
||||
text-decoration: line-through;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.why-btn {
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.7rem;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.03em;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.why-btn:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.why-btn[aria-expanded="true"] {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
background: rgba(184, 90, 90, 0.08);
|
||||
}
|
||||
|
||||
.why {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
margin: 0 0.5rem;
|
||||
padding: 0 0.6rem;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
border-left: 2px solid var(--border-color);
|
||||
transition: max-height 0.25s ease, opacity 0.25s ease, margin 0.25s ease, padding 0.25s ease;
|
||||
}
|
||||
|
||||
.why.open {
|
||||
max-height: 240px;
|
||||
opacity: 1;
|
||||
margin: 0 0.5rem 0.6rem;
|
||||
padding: 0.35rem 0.6rem 0.35rem 0.9rem;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 1.75rem;
|
||||
padding: 0.6rem;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-reset:hover {
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1>Avengers: Doomsday</h1>
|
||||
<p class="subtitle">a quieter fall checklist to the big one</p>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
<div class="counter" id="counterText">0 / 8 Completed</div>
|
||||
|
||||
<ul id="checklist">
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-0">
|
||||
<label for="item-0">Loki (Seasons 1 & 2)</label>
|
||||
<button class="why-btn" data-target="why-0" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-0">The multiverse, the TVA, and the war of the timelines all come from here — Doomsday wouldn't exist without it.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-1">
|
||||
<label for="item-1">Fantastic Four: First Steps</label>
|
||||
<button class="why-btn" data-target="why-1" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-1">The FF's first proper MCU outing, and the family Doomsday quietly revolves around.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-2">
|
||||
<label for="item-2">Doctor Strange in the Multiverse of Madness</label>
|
||||
<button class="why-btn" data-target="why-2" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-2">First real look at incursions and the cracks between timelines — exactly what breaks before Doomsday.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-3">
|
||||
<label for="item-3">Deadpool & Wolverine</label>
|
||||
<button class="why-btn" data-target="why-3" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-3">Shows who decides what survives, and quietly drops the OGs back into the MCU.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-4">
|
||||
<label for="item-4">X-Men, X2, & X-Men: Days of Future Past</label>
|
||||
<button class="why-btn" data-target="why-4" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-4">Your only crash course in the mutants Doomsday is counting on showing up.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-5">
|
||||
<label for="item-5">Captain America: Civil War</label>
|
||||
<button class="why-btn" data-target="why-5" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-5">Explains why the Avengers are already fractured — Doomsday happens on the back of that split.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-6">
|
||||
<label for="item-6">Captain America: Brave New World</label>
|
||||
<button class="why-btn" data-target="why-6" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-6">Introduces adamantium and a new Cap — the science and the stakes behind the Doomsday story.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="item-row">
|
||||
<input type="checkbox" id="item-7">
|
||||
<label for="item-7">Thunderbolts*</label>
|
||||
<button class="why-btn" data-target="why-7" aria-expanded="false">why</button>
|
||||
</div>
|
||||
<div class="why" id="why-7">The misfits' crew Doomsday pulls in — this is where you meet them before the chaos.</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn-reset" onclick="resetChecklist()">Reset Progress</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const COOKIE_NAME = "doomsday_checklist_state";
|
||||
const checkboxes = document.querySelectorAll('#checklist input[type="checkbox"]');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const counterText = document.getElementById('counterText');
|
||||
|
||||
function setCookie(name, value, days = 365) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = "expires=" + date.toUTCString();
|
||||
document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/;SameSite=Strict";
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for(let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
let completedCount = 0;
|
||||
const stateArray = [];
|
||||
|
||||
checkboxes.forEach((cb) => {
|
||||
stateArray.push(cb.checked ? 1 : 0);
|
||||
if (cb.checked) completedCount++;
|
||||
});
|
||||
|
||||
const percentage = (completedCount / checkboxes.length) * 100;
|
||||
progressBar.style.width = percentage + '%';
|
||||
counterText.innerText = `${completedCount} / ${checkboxes.length} Completed`;
|
||||
|
||||
setCookie(COOKIE_NAME, stateArray.join(','));
|
||||
}
|
||||
|
||||
function loadState() {
|
||||
const savedData = getCookie(COOKIE_NAME);
|
||||
if (savedData) {
|
||||
const stateArray = savedData.split(',');
|
||||
checkboxes.forEach((cb, index) => {
|
||||
if (stateArray[index] !== undefined) {
|
||||
cb.checked = stateArray[index] === '1';
|
||||
}
|
||||
});
|
||||
}
|
||||
updateState();
|
||||
}
|
||||
|
||||
function resetChecklist() {
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
updateState();
|
||||
}
|
||||
|
||||
checkboxes.forEach(cb => {
|
||||
cb.addEventListener('change', updateState);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.why-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const target = document.getElementById(btn.dataset.target);
|
||||
if (!target) return;
|
||||
const open = target.classList.toggle('open');
|
||||
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', loadState);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
<!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">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2026-02-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "CommandMove: Cut & Paste on Mac | Miles Wolf Allen",
|
||||
"description": "How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Miles Wolf Allen",
|
||||
"url": "https://milesallen.site/"
|
||||
},
|
||||
"datePublished": "2026-02-15",
|
||||
"url": "https://milesallen.site/blog/command-move-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/blog/command-move-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<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/png" href="../favicon.png">
|
||||
<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>
|
||||
<div class="footer-links" style="margin-bottom: 12px;">
|
||||
<a href="../uses.html" style="color:var(--muted);text-decoration:none;">Uses</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../now.html" style="color:var(--muted);text-decoration:none;">Now</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../colophon.html" style="color:var(--muted);text-decoration:none;">Colophon</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../experiments.html" style="color:var(--accent);text-decoration:none;">Internet History</a>
|
||||
</div>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
|
||||
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
|
||||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||||
</script>
|
||||
<script src="../projects/script.js"></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>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+673
@@ -0,0 +1,673 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dr. Stone · miles wolf allen</title>
|
||||
<meta name="description" content="A comprehensive episode & event breakdown of Dr. Stone — every season, scientific discovery, invention, character development, and conflict, plus the show's stats.">
|
||||
<meta name="author" content="Miles Wolf Allen">
|
||||
<link rel="canonical" href="https://milesallen.site/dr-stone.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Dr. Stone · miles wolf allen">
|
||||
<meta property="og:description" content="Season-by-season episode & event breakdown of Dr. Stone — the technology ladder, major characters, factions, mysteries, and stats.">
|
||||
<meta property="og:url" content="https://milesallen.site/dr-stone.html">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Dr. Stone · miles wolf allen">
|
||||
<meta name="twitter:description" content="A comprehensive episode & event breakdown of Dr. Stone, plus every stat worth knowing.">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--page:#eef1f5;
|
||||
--card:#ffffff;
|
||||
--border:#e3e8ee;
|
||||
--text:#14161a;
|
||||
--muted:#647182;
|
||||
--accent:#1d9bf0;
|
||||
--stone:#7ac74f;
|
||||
--stone2:#58a63a;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{
|
||||
background:var(--page);color:var(--text);
|
||||
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
line-height:1.6;
|
||||
display:flex;flex-direction:column;align-items:center;
|
||||
min-height:100vh;padding:40px 20px 64px;
|
||||
overflow-x:hidden;position:relative;
|
||||
}
|
||||
body::after{
|
||||
content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(rgba(20,22,26,.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(20,22,26,.045) 1px, transparent 1px);
|
||||
background-size:44px 44px;
|
||||
-webkit-mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
}
|
||||
#bgCanvas{position:fixed;inset:0;z-index:-2;pointer-events:none;display:block;}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
|
||||
.page-head{width:100%;max-width:760px;text-align:center;margin-bottom:24px;}
|
||||
.crumbs{font-size:12px;color:var(--muted);}
|
||||
.crumbs a{color:var(--accent);font-weight:600;}
|
||||
.crumbs a:hover{text-decoration:underline;}
|
||||
.page-title{font-size:30px;font-weight:900;letter-spacing:-.02em;margin-top:8px;}
|
||||
.page-sub{font-size:14px;color:var(--muted);margin-top:6px;max-width:640px;margin-left:auto;margin-right:auto;}
|
||||
.kid{display:inline-block;margin-left:6px;font-size:12px;font-weight:800;color:#fff;background:linear-gradient(135deg,var(--stone),var(--stone2));border-radius:999px;padding:2px 9px;vertical-align:middle;letter-spacing:.02em;}
|
||||
|
||||
.wrap{width:100%;max-width:760px;display:flex;flex-direction:column;gap:22px;}
|
||||
|
||||
.hero-card{
|
||||
background:linear-gradient(150deg,#1b2a1c,#2c4a2e 55%,#171f17);
|
||||
border-radius:20px;overflow:hidden;position:relative;color:#eaf5ea;
|
||||
box-shadow:0 1px 2px rgba(16,24,40,.05),0 20px 44px rgba(16,24,40,.22);
|
||||
display:flex;gap:24px;padding:24px;
|
||||
}
|
||||
.hero-card::before{
|
||||
content:'';position:absolute;inset:0;pointer-events:none;
|
||||
background:
|
||||
radial-gradient(420px 260px at 15% 10%, rgba(122,199,79,.22), transparent 60%),
|
||||
radial-gradient(380px 280px at 90% 90%, rgba(122,199,79,.14), transparent 60%);
|
||||
}
|
||||
.hero-poster{position:relative;flex-shrink:0;width:190px;}
|
||||
.hero-poster img{width:190px;height:auto;border-radius:14px;display:block;border:2px solid rgba(255,255,255,.12);box-shadow:0 14px 34px rgba(0,0,0,.45);}
|
||||
.hero-body{position:relative;min-width:0;flex:1;}
|
||||
.hero-eyebrow{font-size:11px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--stone);}
|
||||
.hero-title{font-size:30px;font-weight:900;letter-spacing:-.02em;margin-top:4px;line-height:1.1;}
|
||||
.hero-sub{font-size:16px;font-weight:600;color:#bcd7bd;margin-top:6px;}
|
||||
.hero-desc{font-size:13.5px;color:#cfe0cf;margin-top:10px;line-height:1.7;}
|
||||
.hero-desc b,.hero-desc strong{color:#fff;}
|
||||
.hero-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:14px;}
|
||||
.hero-chip{font-size:11px;font-weight:700;color:#dff0df;border:1px solid rgba(122,199,79,.4);background:rgba(122,199,79,.12);border-radius:999px;padding:4px 11px;}
|
||||
.hero-chip.stone{background:linear-gradient(135deg,var(--stone),var(--stone2));color:#16220f;border-color:transparent;}
|
||||
|
||||
.stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;}
|
||||
.stat{
|
||||
background:var(--card);border:1px solid var(--border);border-radius:16px;
|
||||
padding:18px 18px 16px;box-shadow:0 1px 2px rgba(16,24,40,.05);
|
||||
transition:transform .18s ease,border-color .18s ease;
|
||||
}
|
||||
.stat:hover{transform:translateY(-3px);border-color:var(--accent);}
|
||||
.stat .k{font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);}
|
||||
.stat .v{font-size:24px;font-weight:900;letter-spacing:-.02em;margin-top:5px;line-height:1.1;}
|
||||
.stat .s{font-size:11px;color:var(--muted);margin-top:3px;line-height:1.5;}
|
||||
.stat.wide{grid-column:span 3;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;}
|
||||
.stat.wide .v{font-size:20px;}
|
||||
.stat.wide .s{flex:1;min-width:180px;}
|
||||
|
||||
.band{
|
||||
background:var(--card);border:1px solid var(--border);border-radius:20px;
|
||||
padding:26px 28px;box-shadow:0 1px 2px rgba(16,24,40,.05),0 12px 30px rgba(16,24,40,.08);
|
||||
}
|
||||
.band h2{
|
||||
font-size:18px;font-weight:800;letter-spacing:-.01em;margin-bottom:4px;
|
||||
display:flex;align-items:center;gap:10px;
|
||||
}
|
||||
.band h2::before{content:'';width:5px;height:20px;border-radius:99px;background:var(--accent);flex-shrink:0;}
|
||||
.band h3{font-size:15px;font-weight:800;letter-spacing:-.01em;margin:22px 0 4px;display:flex;align-items:center;gap:8px;}
|
||||
.band h3::before{content:'';width:4px;height:15px;border-radius:99px;background:var(--stone);flex-shrink:0;}
|
||||
.band .lead{font-size:13.5px;color:var(--muted);margin-bottom:8px;}
|
||||
.band h2:first-of-type{margin-top:0;}
|
||||
.band h3:first-of-type{margin-top:6px;}
|
||||
|
||||
p.para{font-size:13.5px;color:#2a2f37;margin:8px 0;line-height:1.7;}
|
||||
p.para b,p.para strong{color:var(--text);font-weight:700;}
|
||||
ul.clean{margin:8px 0 8px 4px;padding:0;list-style:none;}
|
||||
ul.clean li{font-size:13.5px;color:#2a2f37;margin:7px 0;padding-left:20px;position:relative;line-height:1.65;}
|
||||
ul.clean li::before{content:'';position:absolute;left:2px;top:9px;width:8px;height:8px;border-radius:2px;background:var(--stone);opacity:.45;}
|
||||
ul.clean li b,ul.clean li strong{color:var(--text);font-weight:700;}
|
||||
|
||||
.arc{
|
||||
border:1px solid var(--border);border-radius:16px;margin-top:16px;overflow:hidden;
|
||||
background:#fbfcfe;
|
||||
}
|
||||
.arc-head{display:flex;align-items:center;gap:12px;padding:14px 18px;cursor:pointer;background:linear-gradient(180deg,#f6fafe,var(--card));border-bottom:1px solid var(--border);}
|
||||
.arc-head:hover{background:#f0f6fb;}
|
||||
.arc-tag{font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#fff;background:linear-gradient(135deg,var(--stone),var(--stone2));border-radius:8px;padding:4px 10px;flex-shrink:0;}
|
||||
.arc-name{font-size:14.5px;font-weight:800;flex:1;letter-spacing:-.01em;}
|
||||
.arc-meta{font-size:11.5px;color:var(--muted);font-weight:600;flex-shrink:0;}
|
||||
.arc-arrow{font-size:13px;color:var(--muted);transition:transform .2s;flex-shrink:0;}
|
||||
.arc[aria-expanded="true"] .arc-arrow{transform:rotate(180deg);}
|
||||
.arc-body{display:none;padding:18px 20px 20px;}
|
||||
|
||||
.tbl-wrap{overflow-x:auto;margin:10px 0;}
|
||||
table.tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
||||
table.tbl th,table.tbl td{border-bottom:1px solid var(--border);padding:9px 12px;text-align:left;vertical-align:top;line-height:1.5;}
|
||||
table.tbl tr:last-child td{border-bottom:none;}
|
||||
table.tbl th{background:#eaf1f8;font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:#4a5568;white-space:nowrap;}
|
||||
table.tbl td{color:#2a2f37;}
|
||||
table.tbl td b,table.tbl td strong{color:var(--text);font-weight:700;}
|
||||
|
||||
.toc{display:flex;flex-wrap:wrap;gap:8px;}
|
||||
.toc a{font-size:12px;font-weight:700;color:var(--text);border:1px solid var(--border);background:var(--card);padding:7px 14px;border-radius:999px;transition:all .15s;}
|
||||
.toc a:hover{border-color:var(--accent);background:#f4f8fc;transform:translateY(-1px);}
|
||||
|
||||
.back{width:100%;max-width:760px;text-align:center;margin-top:8px;}
|
||||
.back a{display:inline-block;font-size:14px;font-weight:700;color:var(--accent);border:1px solid var(--border);background:var(--card);padding:10px 20px;border-radius:999px;transition:all .15s;}
|
||||
.back a:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(29,155,240,.15);}
|
||||
.btns{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;}
|
||||
.back .secure{margin-left:10px;color:var(--muted);border-color:var(--border);}
|
||||
.back .secure:hover{border-color:var(--muted);}
|
||||
|
||||
.no-grav{margin-top:34px;font-size:13px;color:var(--muted);text-align:center;}
|
||||
.no-grav a{color:var(--accent);font-weight:600;}
|
||||
.no-grav a:hover{text-decoration:underline;}
|
||||
.src-note{font-size:11.5px;color:var(--muted);text-align:center;margin-top:10px;line-height:1.6;}
|
||||
|
||||
.anim [data-anim]{opacity:0;}
|
||||
|
||||
@media(max-width:640px){
|
||||
body{padding:32px 16px 48px;}
|
||||
.page-title{font-size:24px;}
|
||||
.hero-card{flex-direction:column;align-items:flex-start;}
|
||||
.hero-poster{width:140px;}
|
||||
.hero-poster img{width:140px;}
|
||||
.hero-title{font-size:24px;}
|
||||
.stat-grid{grid-template-columns:repeat(2,1fr);}
|
||||
.stat.wide{grid-column:span 2;}
|
||||
.band{padding:20px 18px;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas" aria-hidden="true"></canvas>
|
||||
|
||||
<div class="page-head">
|
||||
<div class="crumbs"><a href="share.html">↩ miles wolf allen</a> · <a href="top-shows.html">my top shows</a></div>
|
||||
<div class="page-title" data-anim="txt">Dr. Stone<span class="kid">#2</span></div>
|
||||
<div class="page-sub" data-anim="txt">a comprehensive episode & event breakdown — every season, discovery, invention, and conflict, plus the stats.</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<div class="hero-card" data-anim="fade">
|
||||
<div class="hero-poster">
|
||||
<img src="https://image.tmdb.org/t/p/w400/xbZQ3fDl0y5mt0ARwfeyrgQ4JTw.jpg" alt="Dr. Stone poster" loading="lazy">
|
||||
</div>
|
||||
<div class="hero-body">
|
||||
<div class="hero-eyebrow">anime · adventure · sci-fi</div>
|
||||
<div class="hero-title">Dr. Stone</div>
|
||||
<div class="hero-sub">"The world of stone belongs to science."</div>
|
||||
<p class="hero-desc">One fateful day, a blinding green light petrifies <strong>all of humanity</strong> — billions of people frozen as statues for <strong>3,700 years</strong>. When teenage genius <strong>Senku Ishigami</strong> finally breaks free of his stone shell, he vows to rebuild civilization from absolute zero, one scientific breakthrough at a time. From stone tools to gunpowder, electricity, a cellphone, and a ship that crosses the Pacific, the Kingdom of Science climbs a two-million-year technological ladder to answer the greatest question of all: <strong>who petrified humanity — and why?</strong></p>
|
||||
<div class="hero-chips">
|
||||
<span class="hero-chip stone">my #2 show</span>
|
||||
<span class="hero-chip">TMS / 8PAN</span>
|
||||
<span class="hero-chip">2019–2026</span>
|
||||
<span class="hero-chip">science</span>
|
||||
<span class="hero-chip">4 seasons + special</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-grid" data-anim="fade">
|
||||
<div class="stat">
|
||||
<div class="k">total runtime</div>
|
||||
<div class="v">~38 hrs</div>
|
||||
<div class="s">95 episodes × 24 min (94 TV + Ryusui special).</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="k">MAL score</div>
|
||||
<div class="v">8.26</div>
|
||||
<div class="s">scored by ~1.19M users — popularity rank #49.</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="k">MAL members</div>
|
||||
<div class="v">~1.98M</div>
|
||||
<div class="s">with 31.7K favorites on Season 1.</div>
|
||||
</div>
|
||||
<div class="stat wide">
|
||||
<div class="k">netflix hours streamed</div>
|
||||
<div class="v">300M+</div>
|
||||
<div class="s">cumulative global hours viewed across the seasons tracked by Netflix (S1 alone passed ~119M hours / ~12.5M views).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="band" data-anim="fade">
|
||||
<h2>Jump to an Arc</h2>
|
||||
<div class="toc">
|
||||
<a href="#s1">Season 1</a>
|
||||
<a href="#s2">Season 2 · Stone Wars</a>
|
||||
<a href="#ryusui">Ryusui Special</a>
|
||||
<a href="#s3">Season 3 · New World</a>
|
||||
<a href="#tech">Tech Ladder</a>
|
||||
<a href="#characters">Characters</a>
|
||||
<a href="#factions">Factions</a>
|
||||
<a href="#mysteries">Mysteries</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="band" data-anim="fade">
|
||||
<h2>The Petrification Event</h2>
|
||||
<p class="para"><strong>September 2019 — Humanity Turns to Stone.</strong> The story begins with humanity suddenly being struck by a mysterious green light. Every human on Earth is petrified, and approximately <strong>7 billion people</strong> are turned to stone. <strong>Senku Ishigami</strong> survives consciously inside his stone shell by continuously counting, and <strong>Taiju Ooki</strong> also remains conscious alongside him.</p>
|
||||
<p class="para">Civilization collapses completely and nature gradually reclaims human cities and infrastructure. Senku eventually estimates that approximately <strong>3,700 years</strong> have passed. The cause of the petrification remains one of the central mysteries of the series.</p>
|
||||
</div>
|
||||
|
||||
<!-- ============ SEASON 1 ============ -->
|
||||
<div class="band" id="s1" data-anim="fade">
|
||||
<h2>Season 1 — Dr. Stone</h2>
|
||||
<p class="lead">Rebuilding civilization from scratch, from the first revival fluid to the brink of the Stone Wars.</p>
|
||||
|
||||
<h3>Arc 1 · Stone World / Stone Formula (Episodes 1–2)</h3>
|
||||
<p class="para"><b>Episode 1 — Stone World.</b> Senku awakens in the Stone World and immediately begins reconstructing civilization from scratch. Escaping his stone shell, he establishes that ~3,700 years have passed, creates a primitive shelter, and begins experimenting with chemistry. He discovers that <strong>nitric acid</strong> can break down the stone casing, eventually developing the <strong>revival fluid</strong> using nitric acid and alcohol — and decides that restoring humanity must begin with science. <em>Major concept: chemical revival via a nitric-acid-based process.</em></p>
|
||||
<p class="para"><b>Episode 2 — Two Nations of the Stone World.</b> Senku revives Taiju — physically powerful but lacking Senku's knowledge, making them complementary partners. They hunt for food, encounter wild animals, discover that the modern world has become wilderness, and find <strong>Yuzuriha's</strong> petrified body. They ultimately revive <strong>Tsukasa Shishio</strong>, one of the strongest fighters of their generation — though Tsukasa's worldview immediately creates tension with Senku.</p>
|
||||
|
||||
<h3>Arc 2 · Tsukasa Empire Conflict (Episodes 3–5)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 2</span>
|
||||
<span class="arc-name">Tsukasa Empire Conflict</span>
|
||||
<span class="arc-meta">eps 3–5</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para"><b>Episode 3 — Weapons of Science.</b> Tsukasa demonstrates his enormous physical strength and begins destroying the stone statues of adults, believing the old world's adults represent corruption. His goal is a new civilization consisting only of young people. Senku recognizes Tsukasa as his greatest threat, and the two sides come to represent fundamentally different philosophies: <strong>Senku: restore everyone</strong> vs. <strong>Tsukasa: build a new world without the old generation</strong>. Senku begins developing weapons using chemistry.</p>
|
||||
<p class="para"><b>Episode 4 — Fire and Gunpowder.</b> Senku produces <strong>gunpowder</strong>, one of the first major technological jumps of the Stone World, using it to threaten Tsukasa. He also creates a large signal fire to communicate with potential survivors — and unexpectedly, another signal appears in the distance, proving other humans are alive.</p>
|
||||
<p class="para"><b>Episode 5 — Stone World vs. Tsukasa.</b> Tsukasa discovers Senku's plans and threatens Yuzuriha to force out the revival-fluid formula. Senku refuses, and the confrontation ends with Tsukasa apparently killing him. Before dying, Senku secretly tells Taiju and Yuzuriha to continue the mission.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 3 · Ishigami Village (Episodes 6–13)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 3</span>
|
||||
<span class="arc-name">Ishigami Village</span>
|
||||
<span class="arc-meta">eps 6–13</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para"><b>Episodes 6–7 — Senku's New Beginning.</b> Senku survives because a portion of the petrification casing remains around his neck, and discovers the signal fire came from <strong>Ishigami Village</strong> — a society preserved for generations by the descendants of astronauts who survived the original event. Meanwhile, Taiju and Yuzuriha infiltrate Tsukasa's group and secretly continue Senku's mission. Senku meets <strong>Kohaku</strong> and begins investigating the village's history.</p>
|
||||
<p class="para"><b>Episode 8 — Stone Roadmap.</b> Senku introduces modern technology to the village, establishing an industrial base: glassmaking, metalworking, agriculture, chemical production, medicine, and electricity. He also meets <strong>Chrome</strong>, a self-taught village scientist who becomes one of the Kingdom's most important members.</p>
|
||||
<p class="para"><b>Episodes 9–10 — Building the Kingdom of Science.</b> Senku discovers the villagers have never tasted many modern foods (most famously <strong>ramen</strong>) and uses this to gain support. He assembles his specialists: Chrome (experimentation), Kohaku (combat/scouting), Suika (reconnaissance), Kinro & Ginro (guards), Kaseki (master craftsman), and Ruri (village priestess). The Kingdom of Science begins to take shape.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 4 · Sulfa Drug / Village Chief (Episodes 11–18)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 4</span>
|
||||
<span class="arc-name">Sulfa Drug / Village Chief</span>
|
||||
<span class="arc-meta">eps 11–18</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para"><b>Episodes 11–12 — The Grand Bout (part 1).</b> Ruri is seriously ill, and Senku determines she needs a powerful medicine — the <strong>sulfa drug</strong>, an early antibiotic. Producing it requires an enormous industrial chain: glass, sulfuric acid, nitric acid, alcohol, chemical reagents, and laboratory equipment. This becomes one of the first demonstrations of the series' central idea: <strong>modern technology depends on thousands of smaller technologies.</strong></p>
|
||||
<p class="para"><b>Episodes 13–14 — Electricity.</b> Senku and Kaseki construct a generator, giving the village electricity, light, electrical equipment, and communication tech. The creation of the <strong>light bulb</strong> represents an enormous technological milestone.</p>
|
||||
<p class="para"><b>Episodes 15–16 — The Grand Bout.</b> The village's martial tournament is held to determine its next chief; Senku enters for strategic rather than personal reasons, and ultimately becomes chief — but his primary objective remains obtaining the resources to manufacture Ruri's medicine.</p>
|
||||
<p class="para"><b>Episodes 17–18 — The Hundred Tales.</b> Ruri reveals the village's history: it was founded by <strong>Byakuya Ishigami</strong>, Senku's adoptive father and one of the astronauts aboard the ISS when humanity was petrified. The astronauts survived (they were in space during the event), returned to Earth, and established a new settlement. Byakuya created the <strong>Hundred Tales</strong>, stories designed to preserve knowledge and history — establishing a direct link between Senku and the village's founders.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 5 · Cellphone / Tsukasa Conflict (Episodes 19–24)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 5</span>
|
||||
<span class="arc-name">Cellphone / Tsukasa Conflict</span>
|
||||
<span class="arc-meta">eps 19–24</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para"><b>Episodes 19–20 — The Cellphone Project.</b> Senku realizes the Kingdom needs long-distance communication and builds a primitive <strong>cellphone</strong>, requiring copper wire, batteries, glass, vacuum tubes, electrical components, magnets, speakers, and microphones. It becomes one of the Kingdom's most important strategic weapons.</p>
|
||||
<p class="para"><b>Episodes 21–22 — Preparing for War.</b> The Kingdom prepares for a direct confrontation with Tsukasa, developing communication equipment, weapons (katanas), mechanical equipment, and reconnaissance systems. Meanwhile, Tsukasa's forces become increasingly organized, and <strong>Hyoga</strong> emerges as one of Tsukasa's strongest warriors.</p>
|
||||
<p class="para"><b>Episodes 23–24 — The Stone Wars Begin.</b> The Kingdom establishes its strategy for defeating Tsukasa without destroying humanity — winning through <strong>science + communication + strategy + manpower</strong> rather than brute force. The season ends with both factions preparing for the coming Stone Wars.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ SEASON 2 ============ -->
|
||||
<div class="band" id="s2" data-anim="fade">
|
||||
<h2>Season 2 — Dr. Stone: Stone Wars</h2>
|
||||
<p class="lead">The Kingdom of Science takes the offensive — and wins the war through information as much as firepower.</p>
|
||||
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">eps 1–3</span>
|
||||
<span class="arc-name">The Communications War</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">The Kingdom begins its offensive. Senku's <strong>cellphone network</strong> lets him communicate with Taiju and Yuzuriha, embedded inside Tsukasa's Empire, and <strong>Gen Asagiri</strong> becomes an important psychological weapon. Imitating voices, Gen impersonates American singer <strong>Lillian Weinberg</strong>, convincing Tsukasa's soldiers that the United States is still technologically advanced and may rescue them — making some soldiers question Tsukasa's ideology. <em>Strategic importance: information can be as powerful as physical weapons.</em></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">eps 4–6</span>
|
||||
<span class="arc-name">Steam Gorilla & Chrome's Escape</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">The Kingdom develops a primitive motorized vehicle, the <strong>Steam Gorilla</strong>, combining steam power, mechanical engineering, metalworking, wheels, and weaponry — the Kingdom's primary mobile weapon. Meanwhile, Chrome is captured by Tsukasa's forces; rather than waiting for rescue, he uses scientific reasoning to escape, creating an improvised electrical system and exploiting the materials available to him. It proves Chrome has evolved from a village experimenter into a genuine scientist.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">eps 7–9</span>
|
||||
<span class="arc-name">The Battle for the Miracle Cave</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">The Kingdom seeks control of the <strong>Cave of Miracles</strong>, the critical source of nitric acid — the key to producing revival fluid. The battle grows dangerous as <strong>Hyoga</strong> confronts the Kingdom. Senku realizes continuing the war could destroy the remaining human population, and proposes a different solution: <strong>revive Tsukasa's sister, Mirai.</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">eps 10–11</span>
|
||||
<span class="arc-name">Tsukasa and Senku</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">Senku successfully revives <strong>Mirai Shishio</strong>, and Tsukasa finally sees that Senku isn't trying to destroy everything he believes in. However, <strong>Hyoga betrays Tsukasa</strong>, and the two sides temporarily unite against him. Senku and Tsukasa defeat Hyoga, but Tsukasa is critically injured. Because the technology to properly revive him doesn't yet exist, Senku places Tsukasa into <strong>cryogenic preservation</strong>. The Stone Wars end with the Kingdom of Science effectively victorious.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ RYUSUI ============ -->
|
||||
<div class="band" id="ryusui" data-anim="fade">
|
||||
<h2>Dr. Stone: Ryusui Special</h2>
|
||||
<p class="lead">The Age of Exploration begins.</p>
|
||||
<p class="para">The Kingdom of Science gains a new objective: <strong>explore the world.</strong> Senku determines they need a ship, a captain, navigation and maps, oceanic transportation, large-scale food production, and fuel. He revives <strong>Ryusui Nanami</strong>, an extremely wealthy and experienced sailor, who immediately becomes one of the Kingdom's most important members.</p>
|
||||
<p class="para">The Kingdom creates a <strong>hot-air balloon</strong>, allowing them to survey large areas, map terrain, and search for resources — specifically <strong>oil</strong> — and to understand the geography of the Stone World. The balloon expedition becomes the beginning of humanity's return to global exploration.</p>
|
||||
</div>
|
||||
|
||||
<!-- ============ SEASON 3 ============ -->
|
||||
<div class="band" id="s3" data-anim="fade">
|
||||
<h2>Season 3 — Dr. Stone: New World</h2>
|
||||
<p class="lead">Humanity takes to the sea and across the Pacific.</p>
|
||||
|
||||
<h3>Arc 1 · Age of Exploration (Episodes 1–6)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 1</span>
|
||||
<span class="arc-name">Age of Exploration</span>
|
||||
<span class="arc-meta">eps 1–6</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para"><b>Agricultural Revolution.</b> Realizing exploration requires enormous quantities of food, the Kingdom industrializes agriculture, notably developing <strong>bread production</strong> and experimenting with wheat cultivation and food preservation. This marks a major transition: science is no longer only about survival — it now supports large-scale civilization.</p>
|
||||
<p class="para"><b>Oil Discovery.</b> The Kingdom searches for oil — essential as fuel, transportation, chemical feedstock, industrial energy, and future machinery. They locate a deposit, but finding it is only the beginning; they still need a way to extract, refine, transport, and use it.</p>
|
||||
<p class="para"><b>The Perseus.</b> Kaseki and the Kingdom construct a massive sailing ship, <strong>the Perseus</strong>, their primary exploration vessel. It contains engines, navigation systems, communication equipment, scientific laboratories, storage, weapons, and even <strong>sonar</strong> — allowing humanity to cross the Pacific Ocean.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 2 · Treasure Island (Episodes 7–14)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 2</span>
|
||||
<span class="arc-name">Treasure Island</span>
|
||||
<span class="arc-meta">eps 7–14</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">The Kingdom reaches <strong>Treasure Island</strong>, an important place connected to the astronauts who survived the original petrification. The island contains the resources for virtually unlimited revival fluid, most importantly <strong>platinum</strong> — an essential component in Senku's plan for the mass production of nitric acid.</p>
|
||||
<p class="para"><b>The Petrification Weapon.</b> The expedition quickly turns into a disaster. A mysterious enemy uses a strange device capable of petrifying humans — the <strong>Medusa</strong>. The Kingdom discovers the enemy can issue commands involving distance and time, and the petrification weapon becomes one of the greatest mysteries in the series.</p>
|
||||
<p class="para"><b>Episodes 11–14 — Treasure Island Society.</b> Senku, Kohaku, Gen, and Soyuz investigate the island, discovering it has developed its own society controlled by <strong>Minister Ibara</strong>, who maintains power through the threat of the Medusa. The team discovers Soyuz has a connection to the island's ruling family, and finds evidence connecting Treasure Island to the original astronauts.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 3 · The Mobile Lab (Episodes 15–18)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 3</span>
|
||||
<span class="arc-name">The Mobile Lab</span>
|
||||
<span class="arc-meta">eps 15–18</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">The Kingdom constructs a <strong>mobile laboratory</strong>, letting Senku run experiments even while moving around the island, and develops increasingly advanced equipment — most importantly a primitive <strong>drone</strong> used for reconnaissance and eventually for stealing the Medusa.</p>
|
||||
<p class="para">Two of Treasure Island's strongest warriors become major opponents: <strong>Moz</strong> (exceptional physical ability) and <strong>Kirisame</strong> (an elite warrior trusted to operate the Medusa). The Kingdom must defeat or outmaneuver both to gain control of the island.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arc 4 · The Petrification of Treasure Island (Episodes 19–22)</h3>
|
||||
<div class="arc" aria-expanded="false">
|
||||
<div class="arc-head">
|
||||
<span class="arc-tag">arc 4</span>
|
||||
<span class="arc-name">Final Battle for the Island</span>
|
||||
<span class="arc-meta">eps 19–22</span>
|
||||
<span class="arc-arrow">▾</span>
|
||||
</div>
|
||||
<div class="arc-body">
|
||||
<p class="para">Ibara attempts to use the Medusa to eliminate his enemies. The Kingdom realizes the weapon's capabilities can potentially be turned against its user, and Senku creates a highly precise strategy involving <strong>timing, distance, revival fluid, communication, positioning, and the Medusa.</strong></p>
|
||||
<p class="para"><b>The Final Battle.</b> Ibara attempts to petrify the entire island. Senku executes a carefully timed revival-fluid attack, and the strategy succeeds — Ibara is defeated and the Kingdom obtains the Medusa.</p>
|
||||
<p class="para"><b>End of Season 3.</b> The Kingdom now possesses one of the most powerful technologies in the Stone World, along with platinum, the Medusa, Treasure Island, new allies, a functioning global expedition force, and knowledge about the origins of the petrification technology. The next stage of the journey is no longer simply about rebuilding civilization — it is about discovering <strong>who petrified humanity, and why.</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ TECH LADDER ============ -->
|
||||
<div class="band" id="tech" data-anim="fade">
|
||||
<h2>Major Scientific Progression</h2>
|
||||
<p class="para">The technological development of Dr. Stone can be viewed as a <strong>technological ladder</strong> — each step built on the ones before it.</p>
|
||||
<div class="tbl-wrap">
|
||||
<table class="tbl">
|
||||
<thead><tr><th>Technology</th><th>Approx. Episode</th><th>Purpose</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Revival Fluid</td><td>S1E1</td><td>Revive humans</td></tr>
|
||||
<tr><td>Basic Fire</td><td>S1E1</td><td>Cooking and survival</td></tr>
|
||||
<tr><td>Glass</td><td>S1</td><td>Laboratory equipment</td></tr>
|
||||
<tr><td>Gunpowder</td><td>S1E4</td><td>Weaponry</td></tr>
|
||||
<tr><td>Ramen</td><td>S1</td><td>Food / morale</td></tr>
|
||||
<tr><td>Sulfuric Acid</td><td>S1</td><td>Chemical industry</td></tr>
|
||||
<tr><td>Electricity</td><td>S1</td><td>Industrial power</td></tr>
|
||||
<tr><td>Light Bulb</td><td>S1</td><td>Artificial lighting</td></tr>
|
||||
<tr><td>Sulfa Drug</td><td>S1</td><td>Medical treatment</td></tr>
|
||||
<tr><td>Magnetism</td><td>S1</td><td>Electrical technology</td></tr>
|
||||
<tr><td>Cellphone</td><td>S1</td><td>Long-distance communication</td></tr>
|
||||
<tr><td>Katanas</td><td>S1</td><td>Advanced weapons</td></tr>
|
||||
<tr><td>Steam Gorilla</td><td>S2</td><td>Transportation / warfare</td></tr>
|
||||
<tr><td>Steam Engine</td><td>S2</td><td>Mechanization</td></tr>
|
||||
<tr><td>Hot-Air Balloon</td><td>Ryusui</td><td>Exploration</td></tr>
|
||||
<tr><td>Bread Production</td><td>S3</td><td>Industrial food production</td></tr>
|
||||
<tr><td>Oil Discovery</td><td>S3</td><td>Future fuel supply</td></tr>
|
||||
<tr><td>Perseus</td><td>S3</td><td>Ocean exploration</td></tr>
|
||||
<tr><td>Sonar</td><td>S3</td><td>Underwater navigation</td></tr>
|
||||
<tr><td>Mobile Lab</td><td>S3</td><td>Field science</td></tr>
|
||||
<tr><td>Drone</td><td>S3</td><td>Reconnaissance</td></tr>
|
||||
<tr><td>Mass-Production Revival System</td><td>S3</td><td>Large-scale revival</td></tr>
|
||||
<tr><td>Medusa</td><td>S3</td><td>Petrification technology</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ CHARACTERS ============ -->
|
||||
<div class="band" id="characters" data-anim="fade">
|
||||
<h2>Major Characters & Their Roles</h2>
|
||||
<div class="tbl-wrap">
|
||||
<table class="tbl">
|
||||
<thead><tr><th>Character</th><th>Role in the Kingdom of Science</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>Senku Ishigami</b></td><td>Leader, scientist, engineer</td></tr>
|
||||
<tr><td><b>Taiju Ooki</b></td><td>Strength, construction, physical labor</td></tr>
|
||||
<tr><td><b>Yuzuriha Ogawa</b></td><td>Craftsmanship, sewing, infiltration</td></tr>
|
||||
<tr><td><b>Tsukasa Shishio</b></td><td>Former enemy, combat specialist</td></tr>
|
||||
<tr><td><b>Chrome</b></td><td>Inventor and developing scientist</td></tr>
|
||||
<tr><td><b>Kohaku</b></td><td>Warrior, scout, reconnaissance</td></tr>
|
||||
<tr><td><b>Suika</b></td><td>Reconnaissance and intelligence</td></tr>
|
||||
<tr><td><b>Kaseki</b></td><td>Master craftsman</td></tr>
|
||||
<tr><td><b>Gen Asagiri</b></td><td>Psychology, deception, communications</td></tr>
|
||||
<tr><td><b>Kinro</b></td><td>Guard and warrior</td></tr>
|
||||
<tr><td><b>Ginro</b></td><td>Guard and warrior</td></tr>
|
||||
<tr><td><b>Ruri</b></td><td>Village leader and historian</td></tr>
|
||||
<tr><td><b>Byakuya Ishigami</b></td><td>Founder of Ishigami Village</td></tr>
|
||||
<tr><td><b>Ryusui Nanami</b></td><td>Captain, navigator, explorer</td></tr>
|
||||
<tr><td><b>Francois</b></td><td>Chef and logistics specialist</td></tr>
|
||||
<tr><td><b>Soyuz</b></td><td>Treasure Island connection</td></tr>
|
||||
<tr><td><b>Mirai Shishio</b></td><td>Tsukasa's sister</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ FACTIONS ============ -->
|
||||
<div class="band" id="factions" data-anim="fade">
|
||||
<h2>The Major Factions</h2>
|
||||
|
||||
<h3>Kingdom of Science</h3>
|
||||
<p class="para"><b>Leader:</b> Senku Ishigami. <b>Goal:</b> Rebuild civilization through science and revive humanity. <b>Major strengths:</b> Science, engineering, medicine, manufacturing, communication, strategic planning.</p>
|
||||
|
||||
<h3>Tsukasa Empire</h3>
|
||||
<p class="para"><b>Leader:</b> Tsukasa Shishio. <b>Original goal:</b> Create a new civilization without the adults of the old world. <b>Major strengths:</b> Physical combat, hunting, survival, military organization. Eventually, Tsukasa's interests align with Senku's larger goal.</p>
|
||||
|
||||
<h3>Ishigami Village</h3>
|
||||
<p class="para">A descendant community created by the astronauts who survived the original petrification. Its important legacy is the <b>Hundred Tales</b>, preserving knowledge of the past and providing the Kingdom of Science with its first major population base.</p>
|
||||
|
||||
<h3>Treasure Island</h3>
|
||||
<p class="para">An isolated society descended from the original survivors, whose political structure revolves around the Medusa. The island becomes critical because of its <b>platinum</b>, <b>petrification technology</b>, connection to the astronauts, and historical information.</p>
|
||||
</div>
|
||||
|
||||
<!-- ============ MYSTERIES ============ -->
|
||||
<div class="band" id="mysteries" data-anim="fade">
|
||||
<h2>Major Mysteries</h2>
|
||||
<p class="para">Several questions drive the story throughout the anime:</p>
|
||||
<ul class="clean">
|
||||
<li><b>Who caused the Petrification Event?</b> The identity of the person or entity responsible remains one of the central mysteries.</li>
|
||||
<li><b>What exactly is Medusa?</b> The petrification devices are vastly more advanced than anything humanity has reconstructed.</li>
|
||||
<li><b>Why was humanity petrified?</b> The motivation behind the event is initially unknown.</li>
|
||||
<li><b>Why did Senku and Taiju remain conscious?</b> Their ability to maintain consciousness inside the stone becomes important to understanding the petrification process.</li>
|
||||
<li><b>What happened to the rest of the world?</b> The Stone World contains clues suggesting humanity's survival was far more complicated than initially believed.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- ============ PROGRESSION ============ -->
|
||||
<div class="band" data-anim="fade">
|
||||
<h2>The Overall Story Progression</h2>
|
||||
<p class="para">The anime's technological progression can essentially be summarized as:</p>
|
||||
<p class="para" style="font-weight:700;color:var(--text);"><strong>Survival → Chemistry → Medicine → Electricity → Communication → Industry → Mechanization → Transportation → Ocean Exploration → Global Exploration → Petrification Technology</strong></p>
|
||||
<p class="para">The scale of the story continually expands. At the beginning, Senku is trying to make fire. By Season 3, the Kingdom of Science has <strong>ships, engines, electricity, medicine, industrial chemistry, communications, aircraft, drones, advanced weapons, a global exploration program, and a device capable of petrifying humanity.</strong> That progression is one of the defining features of Dr. Stone.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="back" data-anim="fade">
|
||||
<div class="btns">
|
||||
<a href="top-shows.html">← back to my top shows</a>
|
||||
<a class="secure" href="share.html">my profile</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-grav" data-anim="fade">made by <a href="index.html">miles</a>. not ai</div>
|
||||
<div class="src-note" data-anim="fade">stats: MyAnimeList (8.26 score, ~1.19M voters, ~1.98M members) & Netflix engagement reports via FlixPatrol (cumulative hours & views for seasons tracked on Netflix). runtime: 95 episodes × 24 min ≈ 38 hrs.</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
|
||||
(function bg(){
|
||||
var canvas = document.getElementById('bgCanvas');
|
||||
if(!canvas) return;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var orbs = [], parts = [];
|
||||
var COLORS = [[122,199,79],[29,155,240],[34,211,153],[168,85,247]];
|
||||
function resize(){
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
seed();
|
||||
}
|
||||
function seed(){
|
||||
orbs = []; parts = [];
|
||||
var w = canvas.width, h = canvas.height;
|
||||
var oCount = Math.min(7, Math.round(w / 200) + 3);
|
||||
for(var i=0;i<oCount;i++){
|
||||
orbs.push({
|
||||
x: Math.random()*w, y: Math.random()*h,
|
||||
r: 110 + Math.random()*220,
|
||||
c: COLORS[i % COLORS.length],
|
||||
sp: 0.10 + Math.random()*0.22,
|
||||
a: 0.14 + Math.random()*0.12,
|
||||
ox: Math.random()*1000, oy: Math.random()*1000
|
||||
});
|
||||
}
|
||||
var pCount = Math.min(80, Math.round(w*h/18000));
|
||||
for(var j=0;j<pCount;j++){
|
||||
parts.push({
|
||||
x: Math.random()*w, y: Math.random()*h,
|
||||
r: 0.6 + Math.random()*1.6,
|
||||
sp: 0.05 + Math.random()*0.25,
|
||||
ph: Math.random()*Math.PI*2, oy: Math.random()*100
|
||||
});
|
||||
}
|
||||
}
|
||||
var t = 0;
|
||||
function frame(){
|
||||
requestAnimationFrame(frame);
|
||||
if(reduced) return;
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
t += 0.008;
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
for(var i=0;i<orbs.length;i++){
|
||||
var o = orbs[i];
|
||||
var nx = o.x + Math.sin(t*o.sp + o.ox) * (o.r*0.9);
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.oy) * (o.r*0.7) * 0.8;
|
||||
var g = ctx.createRadialGradient(nx,ny,0,nx,ny,o.r);
|
||||
g.addColorStop(0, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+','+o.a+')');
|
||||
g.addColorStop(1, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+',0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(nx,ny,o.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.fillStyle = 'rgba(40,50,70,0.35)';
|
||||
for(var p=0;p<parts.length;p++){
|
||||
var pt = parts[p];
|
||||
var px = pt.x + Math.sin(t*pt.sp + pt.ph) * 40;
|
||||
var py = pt.y + Math.cos(t*pt.sp*0.7 + pt.ph*1.7 + pt.oy) * 26;
|
||||
var tw = 0.4 + 0.6*Math.abs(Math.sin(t*0.6 + pt.ph));
|
||||
ctx.globalAlpha = tw*0.6;
|
||||
ctx.beginPath(); ctx.arc(px%canvas.width, py%canvas.height, pt.r, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize(); frame();
|
||||
})();
|
||||
|
||||
(function load(){
|
||||
var body = document.body;
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
body.classList.add('anim');
|
||||
var done = function(){ body.classList.remove('anim'); };
|
||||
if (!window.anime || reduced) { done(); return; }
|
||||
var tl = anime.timeline({ complete: done });
|
||||
tl
|
||||
.add({ targets:'[data-anim="txt"]', translateY:[26,0], opacity:[0,1],
|
||||
duration:560, delay:anime.stagger(90), easing:'easeOutExpo' })
|
||||
.add({ targets:'[data-anim="fade"]', translateY:[24,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(70), easing:'easeOutQuint' });
|
||||
})();
|
||||
|
||||
document.querySelectorAll('.arc-head').forEach(function(head){
|
||||
head.addEventListener('click', function(){
|
||||
var arc = head.parentElement;
|
||||
var body = arc.querySelector('.arc-body');
|
||||
var isOpen = arc.getAttribute('aria-expanded') === 'true';
|
||||
arc.setAttribute('aria-expanded', String(!isOpen));
|
||||
if(!window.anime){
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
return;
|
||||
}
|
||||
if(isOpen){
|
||||
anime({ targets: body, opacity:[1,0], height:[body.scrollHeight,0],
|
||||
duration:220, easing:'easeInOutQuad',
|
||||
complete: function(){ body.style.display='none'; body.style.opacity=''; body.style.height=''; } });
|
||||
} else {
|
||||
body.style.display='block';
|
||||
body.style.opacity=0;
|
||||
body.style.height=0;
|
||||
anime({ targets: body, opacity:[0,1], height:[0, body.scrollHeight],
|
||||
duration:260, easing:'easeOutQuad',
|
||||
complete: function(){ body.style.height=''; } });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+6
-1
@@ -51,7 +51,7 @@
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;overflow-x:hidden;}
|
||||
body{background:var(--bg);color:var(--text);font-family:var(--font-body);overflow-x:hidden;}
|
||||
body{background:var(--bg);color:var(--text);font-family:var(--font-body);overflow-x:clip;}
|
||||
::selection{background:var(--accent);color:#000;}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
|
||||
@@ -1493,6 +1493,11 @@
|
||||
<div class="proj-title">Moon Gaming</div>
|
||||
<div class="proj-desc">Game hub with 1,500+ commits. Browse, play, and explore a growing library of browser games I made at 2am.</div>
|
||||
</a>
|
||||
<a class="proj-card reveal" href="avengers%20chaklist.html">
|
||||
<div class="proj-top"><span class="proj-tag lang">HTML</span><span class="proj-meta">* checklist * cookie saved</span></div>
|
||||
<div class="proj-title">Avengers: Doomsday Watchlist</div>
|
||||
<div class="proj-desc">Road to Doomsday checklist. Track what to watch before the big one, with a little why for each pick.</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+539
@@ -0,0 +1,539 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Miles Wolf Allen | Developer & 3D Artist</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--cream: #f5f0e8;
|
||||
--black: #111111;
|
||||
--yellow: #f6e548;
|
||||
--pink: #ff6ca8;
|
||||
--blue: #9fd9ff;
|
||||
--orange: #ff7147;
|
||||
--border: 2px solid var(--black);
|
||||
--shadow: 6px 6px 0 var(--black);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--cream);
|
||||
color: var(--black);
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: min(1180px, calc(100% - 40px));
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 0;
|
||||
border-bottom: var(--border);
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: flex;
|
||||
gap: 26px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 770px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr .8fr;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
position: relative;
|
||||
padding: 100px 0 130px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 800px;
|
||||
font-size: clamp(68px, 10vw, 150px);
|
||||
line-height: .82;
|
||||
letter-spacing: -9px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
max-width: 430px;
|
||||
margin-top: 38px;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
margin-top: 32px;
|
||||
padding: 16px 22px;
|
||||
border: var(--border);
|
||||
background: var(--yellow);
|
||||
box-shadow: var(--shadow);
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
transition: .2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translate(4px, 4px);
|
||||
box-shadow: 2px 2px 0 var(--black);
|
||||
}
|
||||
|
||||
.sticker {
|
||||
position: absolute;
|
||||
top: 130px;
|
||||
right: 4%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--pink);
|
||||
border: var(--border);
|
||||
transform: rotate(12deg);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-art {
|
||||
min-height: 450px;
|
||||
border: var(--border);
|
||||
background:
|
||||
linear-gradient(135deg, transparent 35%, var(--black) 36% 39%, transparent 40%),
|
||||
var(--blue);
|
||||
box-shadow: var(--shadow);
|
||||
position: relative;
|
||||
transform: rotate(3deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-art::before {
|
||||
content: "MAKE IT MATTER";
|
||||
position: absolute;
|
||||
top: 28%;
|
||||
left: -12%;
|
||||
width: 130%;
|
||||
padding: 18px;
|
||||
background: var(--orange);
|
||||
border-block: var(--border);
|
||||
font-size: clamp(28px, 4vw, 56px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -3px;
|
||||
transform: rotate(-14deg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hero-art::after {
|
||||
content: "✳";
|
||||
position: absolute;
|
||||
right: 15%;
|
||||
bottom: 12%;
|
||||
font-size: 130px;
|
||||
}
|
||||
|
||||
.marquee {
|
||||
border-block: var(--border);
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 14px 0;
|
||||
font-size: 22px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.marquee span {
|
||||
display: inline-block;
|
||||
animation: move 18s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 880px;
|
||||
font-size: clamp(36px, 6vw, 78px);
|
||||
line-height: .95;
|
||||
letter-spacing: -4px;
|
||||
}
|
||||
|
||||
.services {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 370px;
|
||||
padding: 26px;
|
||||
border: var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card:nth-child(1) { background: var(--yellow); }
|
||||
.card:nth-child(2) { background: var(--pink); }
|
||||
.card:nth-child(3) { background: var(--blue); }
|
||||
|
||||
.card-number {
|
||||
font-family: "DM Mono", monospace;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 34px;
|
||||
line-height: .95;
|
||||
letter-spacing: -2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 17px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.work {
|
||||
background: var(--orange);
|
||||
border-block: var(--border);
|
||||
}
|
||||
|
||||
.work-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 22px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.project {
|
||||
min-height: 310px;
|
||||
padding: 25px;
|
||||
border: var(--border);
|
||||
background: var(--cream);
|
||||
box-shadow: var(--shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.project:nth-child(2) { background: var(--blue); }
|
||||
.project:nth-child(3) { background: var(--pink); }
|
||||
.project:nth-child(4) { background: var(--yellow); }
|
||||
|
||||
.project h3 {
|
||||
font-size: 42px;
|
||||
letter-spacing: -3px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.project p {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.testimonial {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: clamp(32px, 5vw, 65px);
|
||||
line-height: .98;
|
||||
letter-spacing: -3px;
|
||||
}
|
||||
|
||||
cite {
|
||||
display: block;
|
||||
margin-top: 28px;
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.contact {
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact h2 {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
font-size: clamp(56px, 10vw, 140px);
|
||||
line-height: .82;
|
||||
letter-spacing: -8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.contact .button {
|
||||
color: var(--black);
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.page-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
margin-top: 26px;
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 25px 0;
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
border-top: 1px solid #555;
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
nav ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: block;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
letter-spacing: -5px;
|
||||
}
|
||||
|
||||
.sticker {
|
||||
top: 34px;
|
||||
right: 0;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hero-art {
|
||||
margin: 70px 12px 0 0;
|
||||
min-height: 330px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.services,
|
||||
.work-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: block;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<a href="#top" class="logo">MILES</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#work">Work</a></li>
|
||||
<li><a href="about-me.html">About</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Independent developer & creative technologist</p>
|
||||
<h1>Build things that refuse to whisper.</h1>
|
||||
<p class="hero-copy">
|
||||
I design weird tools, playful software, and 3D experiments that turn ideas into experiences people actually remember.
|
||||
</p>
|
||||
<a href="#contact" class="button">Start a conversation ↗</a>
|
||||
</div>
|
||||
|
||||
<div class="hero-art" aria-hidden="true"></div>
|
||||
<div class="sticker">Good ideas<br>move fast</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="marquee">
|
||||
<span>
|
||||
Developer ✳ 3D Artist ✳ Emulator Builder ✳ Game Maker ✳ Toolsmith ✳ Developer ✳ 3D Artist ✳ Emulator Builder ✳ Game Maker ✳ Toolsmith ✳
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<section id="services">
|
||||
<p class="section-label">01 / What I do</p>
|
||||
|
||||
<p class="intro">
|
||||
I build the kinds of projects that feel like a point of view, not just a product.
|
||||
</p>
|
||||
|
||||
<div class="services">
|
||||
<article class="card">
|
||||
<span class="card-number">01</span>
|
||||
<h3>Prototype<br>Systems</h3>
|
||||
<p>
|
||||
Explore, test, and ship weird ideas fast — from game mechanics to emulator tooling and browser experiments.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<span class="card-number">02</span>
|
||||
<h3>Creative<br>Code</h3>
|
||||
<p>
|
||||
Turn visuals, motion, and interaction into expressive interfaces with attention to feeling and clarity.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<span class="card-number">03</span>
|
||||
<h3>3D + UI<br>Direction</h3>
|
||||
<p>
|
||||
Shape side projects, concepts, and production-facing systems with a strong visual and technical identity.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="work" id="work">
|
||||
<div class="container">
|
||||
<p class="section-label">02 / Selected work</p>
|
||||
|
||||
<div class="work-grid">
|
||||
<article class="project">
|
||||
<h3>FEDL</h3>
|
||||
<p>Emulator engineering / Game tooling</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>OmniEmu</h3>
|
||||
<p>Platform experimentation / Browser emulation</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>Gelectron</h3>
|
||||
<p>Creative software / UI prototypes</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>Nova</h3>
|
||||
<p>Game concept / Iterative design</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="container">
|
||||
<section>
|
||||
<div class="testimonial">
|
||||
<blockquote>
|
||||
“The work feels intentional, curious, and impossible to ignore — it turns complex ideas into something people instantly understand.”
|
||||
</blockquote>
|
||||
<cite>— Personal note from building around the internet</cite>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="contact" id="contact">
|
||||
<div class="container">
|
||||
<h2>Make your next idea impossible to ignore.</h2>
|
||||
<a href="mailto:[email protected]" class="button">
|
||||
[email protected] ↗
|
||||
</a>
|
||||
|
||||
<div class="page-links">
|
||||
<a href="projects/">Projects</a>
|
||||
<a href="about-me.html">About</a>
|
||||
<a href="blog/">Blog</a>
|
||||
<a href="https://github.com/mileswolfallen2">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<span>© 2026 Miles Wolf Allen</span>
|
||||
<span>Built with intent, not templates.</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
+12446
File diff suppressed because one or more lines are too long
+1
-1
@@ -16,7 +16,7 @@
|
||||
html{scroll-behavior:smooth;overflow-x:hidden;}
|
||||
body{
|
||||
background:var(--bg);color:var(--text);font-family:var(--font-body);
|
||||
line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased;
|
||||
line-height:1.6;overflow-x:clip;-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
|
||||
body::before{
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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.">
|
||||
<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: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="theme-color" content="#ffc700">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* ================= SICK AGENCY STYLE SYSTEM ================= */
|
||||
:root{
|
||||
--yellow:#ffc700;
|
||||
--orange:#ff4e27;
|
||||
--cobalt:#0029ff;
|
||||
--sienna:#4d170c;
|
||||
--black:#000000;
|
||||
--white:#ffffff;
|
||||
--morganite:'Anton','Bebas Neue',Impact,sans-serif;
|
||||
--thunder:'Josefin Sans',ui-sans-serif,sans-serif;
|
||||
--sentient:'Spectral',Georgia,serif;
|
||||
--serif:Georgia,'Times New Roman',times,serif;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;overflow-x:hidden;}
|
||||
body{background:var(--yellow);color:var(--black);font-family:var(--sentient);overflow-x:hidden;-webkit-font-smoothing:antialiased;}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
img{display:block;max-width:100%;}
|
||||
|
||||
/* Full-bleed color bands stacked with zero gap. No shadows, no gradients,
|
||||
no rounded corners except pills. Type is the visual. */
|
||||
.band{width:100%;}
|
||||
.yellow{background:var(--yellow);color:var(--black);}
|
||||
.orange{background:var(--orange);color:var(--white);}
|
||||
.cobalt{background:var(--cobalt);color:var(--white);}
|
||||
|
||||
/* Sticker badges: circular, contrasting color, pill radius, CTA punctuation */
|
||||
.sticker{
|
||||
position:absolute;z-index:5;display:grid;place-items:center;align-content:center;
|
||||
width:140px;height:140px;border-radius:50%;
|
||||
font-family:var(--serif);font-size:11px;line-height:1.2;text-align:center;
|
||||
letter-spacing:.05em;text-transform:uppercase;
|
||||
transform:rotate(6deg);
|
||||
}
|
||||
.sticker b{font-size:20px;font-family:var(--morganite);font-weight:400;letter-spacing:0;display:block;line-height:1;}
|
||||
.sticker small{display:block;margin-top:4px;font-size:9px;}
|
||||
|
||||
/* ================= NAV ================= */
|
||||
.nav{
|
||||
position:sticky;top:0;z-index:50;background:var(--yellow);color:var(--black);
|
||||
display:flex;align-items:center;justify-content:space-between;gap:16px;
|
||||
padding:14px 5vw;border-bottom:3px solid var(--black);
|
||||
}
|
||||
.nav-brand{
|
||||
font-family:var(--morganite);font-weight:400;font-size:34px;line-height:1;
|
||||
letter-spacing:.02em;display:flex;align-items:center;gap:10px;
|
||||
}
|
||||
.nav-brand .swatch{width:18px;height:18px;border-radius:50%;background:var(--cobalt);display:inline-block;}
|
||||
.nav-links{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
||||
.nav-links a{
|
||||
font-family:var(--sentient);font-size:13px;letter-spacing:-.01em;
|
||||
padding:7px 16px;border-radius:999px;border:2px solid var(--black);
|
||||
transition:background .2s,color .2s;
|
||||
}
|
||||
.nav-links a:hover{background:var(--black);color:var(--yellow);}
|
||||
.nav-links a.cta{background:var(--cobalt);border-color:var(--cobalt);color:var(--white);}
|
||||
.nav-links a.cta:hover{background:var(--black);border-color:var(--black);color:var(--yellow);}
|
||||
@media(max-width:640px){
|
||||
.nav{flex-wrap:wrap;}
|
||||
.nav-links{width:100%;justify-content:flex-start;order:3;}
|
||||
}
|
||||
|
||||
/* ================= HERO ================= */
|
||||
.hero{position:relative;overflow:hidden;padding:70px 5vw 90px;}
|
||||
.hero .sticker{right:6vw;top:30px;}
|
||||
.hero-title{
|
||||
font-family:var(--morganite);font-weight:400;
|
||||
font-size:clamp(96px,22vw,229px);line-height:.7;
|
||||
letter-spacing:.01em;text-transform:uppercase;
|
||||
margin-top:24px;
|
||||
}
|
||||
.hero-title .kick{
|
||||
display:block;font-family:var(--thunder);font-weight:300;
|
||||
font-size:clamp(28px,4.5vw,46px);line-height:1;letter-spacing:.02em;
|
||||
margin-bottom:20px;text-transform:none;
|
||||
}
|
||||
.hero-title .loud{display:block;}
|
||||
.hero-sub{
|
||||
margin-top:70px;max-width:480px;
|
||||
font-size:16px;line-height:1.44;letter-spacing:-.015em;
|
||||
}
|
||||
.hero-sub em{font-weight:600;}
|
||||
.hero-status{
|
||||
display:inline-flex;align-items:center;gap:10px;margin-top:24px;
|
||||
font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.1em;
|
||||
border:1px solid var(--black);border-radius:999px;padding:6px 16px;
|
||||
}
|
||||
.hero-status i{width:9px;height:9px;border-radius:50%;background:var(--cobalt);display:inline-block;animation:pulse 1.6s ease-in-out infinite;}
|
||||
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.25;}}
|
||||
.hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:34px;}
|
||||
|
||||
.btn{
|
||||
display:inline-block;font-family:var(--sentient);font-size:16px;
|
||||
padding:9px 26px;border-radius:999px;border:2px solid var(--cobalt);
|
||||
color:var(--cobalt);transition:background .2s,color .2s;
|
||||
}
|
||||
.btn:hover{background:var(--cobalt);color:var(--white);}
|
||||
.btn.primary{background:var(--cobalt);border-color:var(--cobalt);color:var(--white);}
|
||||
.btn.primary:hover{background:var(--black);border-color:var(--black);}
|
||||
.btn.on-orange{border-color:var(--black);color:var(--black);}
|
||||
.btn.primary.on-orange{background:var(--black);border-color:var(--black);color:var(--yellow);}
|
||||
|
||||
.hook{
|
||||
text-align:center;padding:9vw 6vw;position:relative;
|
||||
}
|
||||
.hook-text{
|
||||
font-family:var(--thunder);font-weight:300;
|
||||
font-size:clamp(30px,5vw,56px);line-height:1.1;max-width:1100px;margin:0 auto;
|
||||
}
|
||||
.hook-text em{font-family:var(--serif);font-weight:400;}
|
||||
|
||||
/* ================= MARQUEE ================= */
|
||||
.marquee{overflow:hidden;white-space:nowrap;border-top:3px solid var(--black);border-bottom:3px solid var(--black);padding:16px 0;}
|
||||
.marquee .track{display:inline-flex;animation:scrollLeft 22s linear infinite;}
|
||||
.marquee span{
|
||||
font-family:var(--morganite);font-weight:400;font-size:32px;letter-spacing:.02em;
|
||||
margin:0 30px;text-transform:uppercase;flex-shrink:0;
|
||||
}
|
||||
@keyframes scrollLeft{0%{transform:translateX(0);}100%{transform:translateX(-50%);}}
|
||||
|
||||
/* ================= STAT STRIP ================= */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);border-top:1px solid rgba(255,255,255,.35);}
|
||||
.stat{border-right:1px solid rgba(255,255,255,.35);border-bottom:1px solid rgba(255,255,255,.35);padding:60px 5vw;text-align:center;}
|
||||
.stat:last-child{border-right:none;}
|
||||
.stat-num{font-family:var(--morganite);font-weight:400;font-size:clamp(64px,10vw,110px);line-height:.7;color:var(--white);}
|
||||
.stat-label{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--white);margin-top:16px;}
|
||||
@media(max-width:760px){.stats{grid-template-columns:1fr 1fr;}.stat:nth-child(2){border-right:none;}.stat:nth-child(1),.stat:nth-child(2){border-bottom:1px solid rgba(255,255,255,.35);}}
|
||||
|
||||
/* ================= SECTION FRAME ================= */
|
||||
.section{position:relative;padding:110px 5vw 120px;}
|
||||
.section.orange,.section.cobalt{position:relative;}
|
||||
.sec-label{
|
||||
font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.16em;
|
||||
border:1px solid currentColor;border-radius:999px;display:inline-block;padding:5px 16px;margin-bottom:40px;
|
||||
}
|
||||
.sec-title{
|
||||
font-family:var(--morganite);font-weight:400;
|
||||
font-size:clamp(100px,18vw,229px);line-height:.66;letter-spacing:.01em;text-transform:uppercase;
|
||||
max-width:92vw;margin-bottom:48px;
|
||||
}
|
||||
.sec-sub{font-size:12px;font-family:var(--serif);text-transform:uppercase;letter-spacing:.14em;max-width:520px;line-height:1.6;margin-bottom:70px;}
|
||||
|
||||
/* ================= ABOUT ================= */
|
||||
.about-grid{display:grid;grid-template-columns:auto 1fr;gap:80px;align-items:start;}
|
||||
.avatar-frame{border:3px solid var(--black);outline:3px solid var(--white);transform:rotate(-3deg);width:240px;}
|
||||
.avatar-frame img{width:240px;filter:contrast(1.1);}
|
||||
.avatar-name{font-family:var(--morganite);font-weight:400;font-size:30px;text-transform:uppercase;margin-top:16px;}
|
||||
.avatar-tag{font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:rgba(0,0,0,.55);margin-top:2px;}
|
||||
.about-body p{font-size:17px;line-height:1.5;letter-spacing:-.015em;max-width:560px;margin-bottom:20px;}
|
||||
.badge-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:26px;}
|
||||
.badge{padding:5px 18px;border-radius:999px;border:2px solid var(--black);font-family:var(--serif);font-size:13px;letter-spacing:-.01em;background:var(--cobalt);color:var(--white);transform:rotate(-2deg);}
|
||||
.badge:nth-child(2){background:var(--orange);color:var(--white);transform:rotate(1deg);}
|
||||
.badge:nth-child(3){background:var(--white);color:var(--black);transform:rotate(-1deg);}
|
||||
.about-cta{display:flex;gap:12px;flex-wrap:wrap;margin-top:30px;align-items:center;}
|
||||
.hairline{height:2px;background:var(--black);margin:60px 0 40px;width:100%;}
|
||||
.page-links{display:flex;gap:24px;flex-wrap:wrap;}
|
||||
.page-links a{font-family:var(--serif);font-size:13px;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--black);}
|
||||
.page-links a:hover{color:var(--cobalt);border-color:var(--cobalt);}
|
||||
@media(max-width:820px){.about-grid{grid-template-columns:1fr;gap:40px;}.avatar-frame,.avatar-frame img{width:200px;}}
|
||||
|
||||
/* ================= SKILLS ================= */
|
||||
.or-white{color:var(--white);}
|
||||
.sec-label.flat{border-color:var(--white);}
|
||||
.skill-group-label{font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.16em;margin-bottom:24px;color:var(--white);}
|
||||
.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));margin-bottom:60px;}
|
||||
.skill{
|
||||
border:2px solid var(--white);border-right:none;border-bottom:none;
|
||||
padding:26px 14px;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;min-height:130px;
|
||||
}
|
||||
.skill:nth-child(4n){border-right:2px solid var(--white);}
|
||||
.skill .si{width:30px;height:30px;filter:invert(1);}
|
||||
.skill span{font-family:var(--serif);font-size:13px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.skill.alt1{background:var(--black);color:var(--yellow);}
|
||||
.skill.alt2{background:var(--yellow);color:var(--black);}
|
||||
.skill.alt3{background:var(--white);color:var(--black);}
|
||||
.skill.alt1 .si{filter:none;}
|
||||
.skill.alt3 .si{filter:invert(0);}
|
||||
.skill.alt2 .si{filter:invert(0);}
|
||||
.skill.learn{border-style:dashed;opacity:.75;}
|
||||
|
||||
/* ================= PROJECTS ================= */
|
||||
.project-grid{display:grid;grid-template-columns:1fr 1fr;gap:30px;}
|
||||
.proj-card{
|
||||
border:3px solid var(--black);display:block;padding:34px;
|
||||
transform:rotate(-1deg);transition:transform .25s,background .25s;
|
||||
}
|
||||
.proj-card:nth-child(odd){transform:rotate(1deg);}
|
||||
.proj-card:hover{transform:rotate(-1deg) scale(1.01);}
|
||||
.proj-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:18px;}
|
||||
.proj-tag{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.1em;background:var(--yellow);color:var(--black);border-radius:999px;padding:4px 14px;border:2px solid var(--black);}
|
||||
.proj-tag.on-orange{background:var(--orange);color:var(--white);}
|
||||
.proj-meta{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:rgba(0,0,0,.5);}
|
||||
.proj-title{font-family:var(--morganite);font-weight:400;font-size:clamp(36px,4vw,56px);line-height:.8;text-transform:uppercase;margin-bottom:16px;}
|
||||
.proj-desc{font-size:15px;line-height:1.5;letter-spacing:-.01em;max-width:44ch;color:rgba(0,0,0,.78);}
|
||||
@media(max-width:800px){.project-grid{grid-template-columns:1fr;}}
|
||||
|
||||
/* ================= HIGHLIGHTS ================= */
|
||||
.hl-block{margin-bottom:80px;}
|
||||
.hl-block h3{font-family:var(--morganite);font-weight:400;font-size:clamp(40px,6vw,64px);text-transform:uppercase;margin-bottom:28px;color:var(--yellow);}
|
||||
.hl-row{display:grid;grid-template-columns:repeat(4,1fr);border-top:2px solid var(--yellow);}
|
||||
.hl-cell{border-right:2px solid var(--yellow);border-bottom:2px solid var(--yellow);padding:40px 30px;}
|
||||
.hl-cell:last-child{border-right:none;}
|
||||
.hl-num{font-family:var(--morganite);font-weight:400;font-size:clamp(52px,7vw,84px);line-height:.7;color:var(--white);}
|
||||
.hl-label{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--white);margin-top:16px;}
|
||||
@media(max-width:760px){.hl-row{grid-template-columns:1fr 1fr;}.hl-cell:nth-child(2){border-right:none;}}
|
||||
|
||||
/* ================= GITHUB STATS ================= */
|
||||
.gh-stats{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-start;}
|
||||
.gh-stats img{border:2px solid var(--black);height:165px;width:auto;}
|
||||
|
||||
/* ================= BLOG ================= */
|
||||
.blog-grid{display:grid;grid-template-columns:1fr 1fr;gap:30px;}
|
||||
.blog-card{border:3px solid var(--white);display:block;padding:30px;transform:rotate(-1deg);transition:transform .25s,border-color .25s;}
|
||||
.blog-card:nth-child(even){transform:rotate(1deg);}
|
||||
.blog-card:hover{transform:rotate(0);border-color:var(--yellow);}
|
||||
.blog-date{font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:var(--yellow);margin-bottom:16px;}
|
||||
.blog-title{font-family:var(--morganite);font-weight:400;font-size:clamp(30px,3vw,40px);text-transform:uppercase;line-height:.85;margin-bottom:14px;}
|
||||
.blog-excerpt{font-size:15px;line-height:1.5;letter-spacing:-.01em;color:rgba(255,255,255,.78);margin-bottom:18px;max-width:46ch;}
|
||||
.blog-tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px;}
|
||||
.blog-tag{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--yellow);color:var(--yellow);border-radius:999px;padding:4px 14px;}
|
||||
|
||||
/* ================= CONTACT ================= */
|
||||
.contact-card{border:3px solid var(--yellow);padding:44px;transform:rotate(-1deg);max-width:640px;position:relative;}
|
||||
.contact-card .sticker{right:-30px;top:-40px;background:var(--yellow);color:var(--cobalt);transform:rotate(10deg);}
|
||||
.contact-list{display:flex;flex-direction:column;}
|
||||
.contact-row{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:20px 6px;border-bottom:2px solid var(--yellow);font-size:16px;transition:padding-left .2s;flex-wrap:wrap;}
|
||||
.contact-row:last-child{border-bottom:none;}
|
||||
.contact-row span.k{font-family:var(--serif);font-size:11px;text-transform:uppercase;letter-spacing:.14em;opacity:.7;}
|
||||
.contact-row:hover{padding-left:18px;color:var(--yellow);}
|
||||
|
||||
/* ================= FOOTER ================= */
|
||||
footer{background:var(--black);color:var(--white);padding:70px 5vw 80px;text-align:center;}
|
||||
.foot-brand{font-family:var(--morganite);font-weight:400;font-size:clamp(48px,10vw,110px);line-height:.7;color:var(--yellow);text-transform:uppercase;margin-bottom:30px;}
|
||||
.foot-links{display:flex;gap:18px;flex-wrap:wrap;justify-content:center;margin-bottom:30px;}
|
||||
.foot-links a{font-family:var(--serif);font-size:12px;text-transform:uppercase;letter-spacing:.1em;border-radius:999px;border:1px solid var(--white);padding:7px 18px;}
|
||||
.foot-links a:hover{background:var(--yellow);color:var(--black);border-color:var(--yellow);}
|
||||
.foot-note{color:rgba(255,255,255,.55);font-size:13px;line-height:1.7;}
|
||||
.foot-note a{color:var(--yellow);border-bottom:1px solid var(--yellow);}
|
||||
|
||||
@media(prefers-reduced-motion:reduce){*{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav">
|
||||
<a class="nav-brand" href="#top"><span class="swatch"></span>MILES</a>
|
||||
<div class="nav-links">
|
||||
<a href="#about">About</a>
|
||||
<a href="#skills">Skills</a>
|
||||
<a href="#projects">Projects</a>
|
||||
<a href="#blog">Devlog</a>
|
||||
<a class="cta" href="#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- ============ HERO — YELLOW ============ -->
|
||||
<section class="band yellow hero" id="top">
|
||||
<span class="sticker" style="background:var(--cobalt);color:var(--white);"><b>USUALLY</b>ONLINE <small>@mileswolfallen2</small></span>
|
||||
<div class="hero-title">
|
||||
<span class="kick">Hi, I'm —</span>
|
||||
<span class="loud">MILES<br>WOLF<br>ALLEN</span>
|
||||
</div>
|
||||
<p class="hero-sub">I make games, tools, and websites. I'm still in school and building a full Electron replacement — <em>send help</em>. I also work at Diskette Labs.</p>
|
||||
<div class="hero-status"><i></i> usually online</div>
|
||||
<div class="hero-actions">
|
||||
<a class="btn primary" href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
|
||||
<a class="btn" href="https://replit.com/@MilesAllen1" target="_blank">Replit</a>
|
||||
<a class="btn" href="https://codepen.io/mileswolfallen2-the-typescripter" target="_blank">CodePen</a>
|
||||
<a class="btn" href="https://diskettelabs.com/" target="_blank">Diskette Labs</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ HOOK — ORANGE ============ -->
|
||||
<section class="band orange hook">
|
||||
<p class="hook-text">I believe pancakes are better than waffles, cereal is soup, and software should <em>feel good to use.</em></p>
|
||||
</section>
|
||||
|
||||
<!-- ============ MARQUEE — YELLOW ============ -->
|
||||
<div class="band yellow marquee">
|
||||
<div class="track">
|
||||
<span>* building emulators *</span><span>* send help *</span><span>* 160 repos and counting *</span><span>* still debugging *</span><span>* geometry dash addict *</span><span>* usually online *</span><span>* ↑ ↑ ↓ ↓ ← → ← → B A *</span>
|
||||
<span>* building emulators *</span><span>* send help *</span><span>* 160 repos and counting *</span><span>* still debugging *</span><span>* geometry dash addict *</span><span>* usually online *</span><span>* ↑ ↑ ↓ ↓ ← → ← → B A *</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ STATS — ORANGE ============ -->
|
||||
<section class="band orange stats">
|
||||
<div class="stat"><div class="stat-num">160</div><div class="stat-label">Repos online</div></div>
|
||||
<div class="stat"><div class="stat-num">91</div><div class="stat-label">Stars earned</div></div>
|
||||
<div class="stat"><div class="stat-num">13</div><div class="stat-label">Watching me</div></div>
|
||||
<div class="stat"><div class="stat-num">37</div><div class="stat-label">I watch</div></div>
|
||||
</section>
|
||||
|
||||
<!-- ============ ABOUT — YELLOW ============ -->
|
||||
<section class="band yellow section" id="about">
|
||||
<span class="sec-label">Level 01 // Identity</span>
|
||||
<h2 class="sec-title">Who's Miles?</h2>
|
||||
<p class="sec-sub">the human behind the commits</p>
|
||||
<div class="about-grid">
|
||||
<div class="avatar-block" style="text-align:center;">
|
||||
<div class="avatar-frame"><img src="https://avatars.githubusercontent.com/u/115095719?v=4" alt="Miles Wolf Allen" loading="lazy" width="240" height="240"></div>
|
||||
<div class="avatar-name">Miles Wolf Allen</div>
|
||||
<div class="avatar-tag">@mileswolfallen2</div>
|
||||
</div>
|
||||
<div class="about-body">
|
||||
<p>I build things on the internet. Some of them are games, some are tools, and some are just because I thought it would be fun. 160 repos, 91 stars, and a growing list of things I'm figuring out as I go.</p>
|
||||
<div class="badge-row">
|
||||
<span class="badge">Pull Shark x2</span>
|
||||
<span class="badge">YOLO</span>
|
||||
<span class="badge">Quickdraw</span>
|
||||
</div>
|
||||
<div class="about-cta">
|
||||
<a class="btn primary" href="https://diskettelabs.com/" target="_blank">Member of Diskette Labs</a>
|
||||
<a class="btn" href="about-me.html">More About Me</a>
|
||||
</div>
|
||||
<div class="hairline"></div>
|
||||
<div class="page-links">
|
||||
<a href="uses.html">Uses</a>
|
||||
<a href="now.html">Now</a>
|
||||
<a href="colophon.html">Colophon</a>
|
||||
<a href="experiments.html" style="color:var(--cobalt);border-color:var(--cobalt);">Internet History</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ SKILLS — COBALT ============ -->
|
||||
<section class="band cobalt section" id="skills">
|
||||
<span class="sec-label flat">Level 02 // Loadout</span>
|
||||
<h2 class="sec-title or-white">My Loadout</h2>
|
||||
<p class="sec-sub or-white">what I actually know how to use</p>
|
||||
|
||||
<div class="skill-group-label">Ready to use</div>
|
||||
<div class="skill-grid">
|
||||
<div class="skill"><svg class="si" viewBox="0 0 24 24" fill="#fff"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 3h8v8H3zm10 0h8v8h-8zM3 13h8v8H3zm13 0h2v3h3v2h-3v3h-2v-3h-3v-2h3z"/></svg><span>JavaScript</span></div>
|
||||
<div class="skill alt1"><span>Node.js</span><span style="font-size:11px;">Backend JS</span></div>
|
||||
<div class="skill alt2"><span>HTML5</span></div>
|
||||
<div class="skill alt3"><span>CSS3</span></div>
|
||||
<div class="skill"><svg class="si" viewBox="0 0 24 24" fill="#fff"><path d="M11.5 18h1v-5.5H17v-1h-4.5V6h-1v5.5H7v1h4.5z"/><path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 18.5A8.5 8.5 0 1 1 12 3.5a8.5 8.5 0 0 1 0 17z"/></svg><span>Python</span></div>
|
||||
<div class="skill alt1"><span>Git</span></div>
|
||||
<div class="skill alt2"><span>Blender</span></div>
|
||||
<div class="skill alt3"><span>Unity</span></div>
|
||||
<div class="skill"><span>Unreal</span></div>
|
||||
<div class="skill alt1"><span>Cloudflare</span></div>
|
||||
<div class="skill alt2"><span>Linux</span></div>
|
||||
<div class="skill alt3"><span>Netlify</span></div>
|
||||
<div class="skill"><span>Bash</span></div>
|
||||
<div class="skill alt1"><span>GitHub</span></div>
|
||||
<div class="skill alt2"><span>Electron</span></div>
|
||||
<div class="skill alt3"><span>Gelectron</span></div>
|
||||
</div>
|
||||
|
||||
<div class="skill-group-label">Currently unlocking</div>
|
||||
<div class="skill-grid">
|
||||
<div class="skill learn"><span>PHP</span></div>
|
||||
<div class="skill learn alt1"><span>.NET</span></div>
|
||||
<div class="skill learn alt2"><span>Nix</span></div>
|
||||
<div class="skill learn alt3"><span>Yarn</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ PROJECTS — YELLOW ============ -->
|
||||
<section class="band yellow section" id="projects">
|
||||
<span class="sec-label">Level 03 // Builds</span>
|
||||
<h2 class="sec-title">The Projects</h2>
|
||||
<p class="sec-sub">stuff I've made (and some I forked)</p>
|
||||
|
||||
<div class="project-grid">
|
||||
<a class="proj-card" href="projects/omniemu.html">
|
||||
<div class="proj-top"><span class="proj-tag">Active</span><span class="proj-meta">TypeScript / v0.3.2</span></div>
|
||||
<div class="proj-title">OmniEmu 2.0</div>
|
||||
<div class="proj-desc">All your emulators in one place. Install 20+ emulators, scan your ROMs, and launch games without opening 15 different apps.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/gelectron.html">
|
||||
<div class="proj-top"><span class="proj-tag">Early</span><span class="proj-meta">Rust + Node.js</span></div>
|
||||
<div class="proj-title">Gelectron</div>
|
||||
<div class="proj-desc">Drop-in replacement for Electron using native web views (WKWebView / WebView2 / WebKitGTK) instead of bundling Chromium.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="https://vanilla.milesallen.site/" target="_blank">
|
||||
<div class="proj-top"><span class="proj-tag">Unreleased</span><span class="proj-meta">Vanilla JS</span></div>
|
||||
<div class="proj-title">Vanilla</div>
|
||||
<div class="proj-desc">A clean, minimal web experiment. Pure vanilla JS, no frameworks, no cruft.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/fedl.html">
|
||||
<div class="proj-top"><span class="proj-tag on-orange">Live</span><span class="proj-meta">2196 commits</span></div>
|
||||
<div class="proj-title">FEDL</div>
|
||||
<div class="proj-desc">The demon list for Geometry Dash. Ranked levels, run submissions, and a community of people way better at the game than me.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/command-move.html">
|
||||
<div class="proj-top"><span class="proj-tag">Swift</span><span class="proj-meta">macOS / MIT</span></div>
|
||||
<div class="proj-title">CommandMove</div>
|
||||
<div class="proj-desc">True cut-and-paste for Finder on macOS. Select files, Cmd+X, navigate, Cmd+V. What Apple forgot to build.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/noto.html">
|
||||
<div class="proj-top"><span class="proj-tag">Active</span><span class="proj-meta">Flutter / AI</span></div>
|
||||
<div class="proj-title">Noto</div>
|
||||
<div class="proj-desc">Note-taking app with AI built in. Built with Flutter, powered by Cortex AI, and actually good at organizing my chaos.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/nova.html">
|
||||
<div class="proj-top"><span class="proj-tag">Python</span><span class="proj-meta">Desktop / Web / CLI</span></div>
|
||||
<div class="proj-title">Nova AI</div>
|
||||
<div class="proj-desc">AI assistant that runs on your machine. Desktop GUI, web interface, and CLI. No cloud, no subscriptions, just vibes.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/music-app.html">
|
||||
<div class="proj-top"><span class="proj-tag">JS</span><span class="proj-meta">Apache-2.0</span></div>
|
||||
<div class="proj-title">Music App</div>
|
||||
<div class="proj-desc">Music player that doesn't interrupt you with ads. Inspired by YouTube Music but without the annoying parts.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/rhythm-game.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">MPL-2.0</span></div>
|
||||
<div class="proj-title">Rhythm Game</div>
|
||||
<div class="proj-desc">Hit keys to the beat. Song selection, real-time notes, and keyboard controls. My fingers hurt.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/geode-mod.html">
|
||||
<div class="proj-top"><span class="proj-tag">WIP</span><span class="proj-meta">MIT</span></div>
|
||||
<div class="proj-title">FEDL Geode Mod</div>
|
||||
<div class="proj-desc">Geometry Dash mod that shows the demon list in-game. Roulette mode, banned mod detection, and more.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/decompilations.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">MIT</span></div>
|
||||
<div class="proj-title">Decompilations</div>
|
||||
<div class="proj-desc">Native PC ports and decompilations of games. Progress tracking for projects that probably shouldn't exist.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/issue-tracker.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">Node.js</span></div>
|
||||
<div class="proj-title">Issue Tracker</div>
|
||||
<div class="proj-desc">Built my own issue tracker because why not. Node.js backend, no frameworks, just raw determination.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="projects/moon-gaming.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">1,542 commits</span></div>
|
||||
<div class="proj-title">Moon Gaming</div>
|
||||
<div class="proj-desc">Game hub with 1,500+ commits. Browse, play, and explore a growing library of browser games I made at 2am.</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ HIGHLIGHTS — COBALT ============ -->
|
||||
<section class="band cobalt section" id="highlights">
|
||||
<span class="sec-label flat">Level 04 // Scoreboard</span>
|
||||
<h2 class="sec-title or-white">Scoreboard</h2>
|
||||
<p class="sec-sub or-white">what my projects have achieved</p>
|
||||
|
||||
<div class="hl-block">
|
||||
<h3>FEDL</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">3,839</div><div class="hl-label">Verified runs</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">341</div><div class="hl-label">Ranked levels</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">250+</div><div class="hl-label">Active players</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">3,800+</div><div class="hl-label">YouTube videos</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hl-block">
|
||||
<h3>OmniEmu 2.0</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">20+</div><div class="hl-label">Emulators</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">3</div><div class="hl-label">Platforms</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">v0.3.2</div><div class="hl-label">Latest</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">TS</div><div class="hl-label">Built with</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hl-block">
|
||||
<h3>Other projects</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">3</div><div class="hl-label">Nova interfaces</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">5</div><div class="hl-label">Noto AI features</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">1,542</div><div class="hl-label">Moon Gaming commits</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">MIT</div><div class="hl-label">Licensed</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ GITHUB STATS — YELLOW ============ -->
|
||||
<section class="band yellow section" id="stats">
|
||||
<span class="sec-label">Level 05 // GitHub</span>
|
||||
<h2 class="sec-title">Stats</h2>
|
||||
<p class="sec-sub">the raw numbers, straight from the API</p>
|
||||
<div class="gh-stats">
|
||||
<img src="https://github-readme-stats.josselinonduty.fr/api?username=mileswolfallen2&show_icons=true&theme=noctis_minimus" alt="GitHub stats" loading="lazy">
|
||||
<img src="https://github-readme-stats.josselinonduty.fr/api/top-langs/?username=mileswolfallen2&layout=compact&theme=noctis_minimus" alt="GitHub languages" loading="lazy">
|
||||
<img src="https://streak-stats.demolab.com/?user=mileswolfallen2&theme=noctis_minimus" alt="GitHub streak" loading="lazy">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BLOG — COBALT ============ -->
|
||||
<section class="band cobalt section" id="blog">
|
||||
<span class="sec-label flat">Level 06 // Devlog</span>
|
||||
<h2 class="sec-title or-white">Devlog</h2>
|
||||
<p class="sec-sub or-white">things I've learned, broken, and fixed</p>
|
||||
<div class="blog-grid">
|
||||
<a class="blog-card" href="blog/diskette-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">Focusing on Diskette Labs</div>
|
||||
<div class="blog-excerpt">I'm co-founder of Diskette Labs, a product design collective started by Owen. It's where my focus is now — but I'm still open to collaborations.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Design</span><span class="blog-tag">Collective</span></div>
|
||||
</a>
|
||||
<a class="blog-card" href="blog/gelectron-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">Why I Built Gelectron</div>
|
||||
<div class="blog-excerpt">Electron bundles Chromium with every app. 150-300MB per app, hundreds of MBs of RAM. Native web views fix that.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Rust</span><span class="blog-tag">Native</span></div>
|
||||
</a>
|
||||
<a class="blog-card" 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" 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" 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" 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>
|
||||
|
||||
<!-- ============ CONTACT — ORANGE ============ -->
|
||||
<section class="band orange section" id="contact">
|
||||
<span class="sec-label flat">Level 07 // Reach out</span>
|
||||
<h2 class="sec-title or-white">Get In Touch</h2>
|
||||
<p class="sec-sub or-white">unless you're a bot, then please go away</p>
|
||||
<div class="contact-card">
|
||||
<span class="sticker" style="background:var(--yellow);color:var(--cobalt);"><b>TEXT<br>US</b><small>or email</small></span>
|
||||
<div class="contact-list">
|
||||
<a class="contact-row" href="mailto:[email protected]"><span class="k">email</span><span>[email protected]</span></a>
|
||||
<a class="contact-row" href="https://github.com/mileswolfallen2" target="_blank"><span class="k">github</span><span>mileswolfallen2</span></a>
|
||||
<a class="contact-row" href="https://replit.com/@MilesAllen1" target="_blank"><span class="k">replit</span><span>@MilesAllen1</span></a>
|
||||
<a class="contact-row" href="https://codepen.io/mileswolfallen2-the-typescripter" target="_blank"><span class="k">codepen</span><span>mileswolfallen2</span></a>
|
||||
<a class="contact-row" href="https://me.fedl.site" target="_blank"><span class="k">website</span><span>me.fedl.site</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ FOOTER — BLACK ============ -->
|
||||
<footer>
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="uses.html">Uses</a>
|
||||
<a href="now.html">Now</a>
|
||||
<a href="colophon.html">Colophon</a>
|
||||
<a href="experiments.html">Internet History</a>
|
||||
<a href="https://server.fedl.site/git/miles" target="_blank" rel="noopener">Backup</a>
|
||||
<a href="redesign/hub.html">View Redesigns</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code<br>Designed in the style of a loud printed broadside.</p>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+28
-3
@@ -266,7 +266,7 @@ html.js .card.featured:hover { transform: translate(-3px,-3px) !important; }
|
||||
<nav class="nav">
|
||||
<a class="nav-back" href="../index.html">← Back to site</a>
|
||||
<span class="nav-title">Redesign Lab</span>
|
||||
<span class="nav-count">19 designs</span>
|
||||
<span class="nav-count">20 designs</span>
|
||||
</nav>
|
||||
|
||||
<!-- hero -->
|
||||
@@ -278,8 +278,8 @@ html.js .card.featured:hover { transform: translate(-3px,-3px) !important; }
|
||||
</h1>
|
||||
<p class="hero-sub">
|
||||
Experimental redesigns of milesallen.site — exploring different design languages,
|
||||
animation approaches, and visual directions. Nineteen experiments so far,
|
||||
newest first: a 1976 punk zine, a Daily Bugle newspaper, retro intros, web cartoons, whimsical, loud/quiet, space stations, handhelds, and terminals.
|
||||
animation approaches, and visual directions. Twenty experiments so far,
|
||||
newest first: a loud SICK AGENCY printed broadside, a 1976 punk zine, a Daily Bugle newspaper, retro intros, web cartoons, whimsical, loud/quiet, space stations, handhelds, and terminals.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -288,6 +288,31 @@ html.js .card.featured:hover { transform: translate(-3px,-3px) !important; }
|
||||
<div class="grid-label">History — all experiments, newest first</div>
|
||||
<div class="cards" id="history">
|
||||
|
||||
<!-- attempt6 — SICK AGENCY -->
|
||||
<a class="card featured" href="../attempt6.html">
|
||||
<div class="card-preview">
|
||||
<iframe src="../attempt6.html" title="SICK AGENCY printed broadside redesign preview" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true"></iframe>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<div class="card-num">19</div>
|
||||
<div class="card-name">SICK BROADSIDE</div>
|
||||
<p class="card-desc">
|
||||
The portfolio as a loud printed broadside on a brick wall — a maximalist zine spread in the SICK AGENCY system: three saturated full-bleed color bands (voltage yellow, radioactive orange, electric cobalt) stacked with zero gap and hard color cuts. Anton/Morganite condensed display at up to 229px with 0.70 line-height (type as a solid printed block), Thunder 300 subheads, Sentient body with negative tracking, circular sticker badges as CAT/CTA anchors (USUALLY ONLINE, TEXT US), pill-shaped buttons as the only softness, sharp 0px corners everywhere else, hairline black rules, and no shadows, gradients, or neutrals — only #000 and #fff.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="card-tags">
|
||||
<span class="ctag ctag-y">SICK AGENCY</span>
|
||||
<span class="ctag ctag-p">Cobalt action</span>
|
||||
<span class="ctag ctag-c">Voltage yellow</span>
|
||||
<span class="ctag ctag-g">Full-bleed bands</span>
|
||||
</div>
|
||||
<span class="card-arrow">↗</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- punk-zine -->
|
||||
<a class="card featured" href="punk-zine.html">
|
||||
<div class="card-preview">
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Miles Wolf Allen — independent developer, 3D artist, emulator builder, and game maker.">
|
||||
<title>Miles Wolf Allen — Creative Technology</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--ink: #161616;
|
||||
--paper: #f2ead8;
|
||||
--yellow: #ffc700;
|
||||
--orange: #ff4e27;
|
||||
--pink: #f3a7bc;
|
||||
--muted: #6d6a61;
|
||||
--line: 1px solid var(--ink);
|
||||
--display: "Space Grotesk", Arial, sans-serif;
|
||||
--mono: "DM Mono", monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body { background: var(--paper); color: var(--ink); font-family: var(--display); overflow-x: hidden; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--orange); }
|
||||
.wrap { width: min(1320px, calc(100% - 48px)); margin: 0 auto; }
|
||||
|
||||
header { border-bottom: var(--line); }
|
||||
nav { min-height: 76px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.brand { font-size: 19px; font-weight: 700; letter-spacing: -.06em; text-transform: uppercase; }
|
||||
nav ul { display: flex; gap: 28px; list-style: none; padding: 0; font: 12px var(--mono); text-transform: uppercase; }
|
||||
.menu-mark { display: none; font: 12px var(--mono); text-transform: uppercase; }
|
||||
|
||||
.hero { min-height: 690px; display: grid; grid-template-columns: 1.35fr .65fr; border-bottom: var(--line); }
|
||||
.hero-copy { padding: 92px 7vw 76px 0; border-right: var(--line); }
|
||||
.kicker, .label { color: var(--orange); font: 11px var(--mono); letter-spacing: .08em; text-transform: uppercase; }
|
||||
.hero h1 { max-width: 900px; margin-top: 24px; font-size: clamp(70px, 12vw, 174px); line-height: .82; letter-spacing: -.095em; text-transform: uppercase; }
|
||||
.hero h1 em { color: var(--orange); font-style: normal; }
|
||||
.lead { max-width: 480px; margin-top: 38px; font-size: 19px; line-height: 1.35; }
|
||||
.cta { display: inline-flex; align-items: center; gap: 22px; margin-top: 42px; padding: 15px 18px; background: var(--yellow); border: var(--line); font: 12px var(--mono); text-transform: uppercase; transition: transform .2s, background .2s; }
|
||||
.cta:hover { color: var(--ink); background: var(--orange); transform: translate(5px, -5px); }
|
||||
|
||||
.hero-side { position: relative; display: flex; flex-direction: column; justify-content: space-between; padding: 26px; background: var(--yellow); overflow: hidden; }
|
||||
.hero-side .index { font: 11px var(--mono); }
|
||||
.sun { width: min(260px, 70%); aspect-ratio: 1; margin: auto; border: var(--line); border-radius: 50%; background: var(--orange); display: grid; place-items: center; font-size: clamp(45px, 8vw, 100px); line-height: 1; transform: rotate(12deg); box-shadow: 12px 12px 0 var(--ink); }
|
||||
.side-note { font: 12px/1.4 var(--mono); text-transform: uppercase; }
|
||||
|
||||
.ticker { overflow: hidden; border-bottom: var(--line); background: var(--ink); color: var(--paper); white-space: nowrap; }
|
||||
.ticker-track { display: inline-block; padding: 16px 0; animation: slide 22s linear infinite; font-size: 15px; text-transform: uppercase; }
|
||||
@keyframes slide { to { transform: translateX(-50%); } }
|
||||
|
||||
section { padding: 100px 0; border-bottom: var(--line); }
|
||||
.section-head { display: grid; grid-template-columns: 1fr 2fr; gap: 24px; margin-bottom: 62px; }
|
||||
.section-title { max-width: 760px; font-size: clamp(43px, 7vw, 92px); line-height: .88; letter-spacing: -.075em; text-transform: uppercase; }
|
||||
.section-title span { color: var(--orange); }
|
||||
|
||||
.services { display: grid; grid-template-columns: repeat(3, 1fr); border-top: var(--line); border-left: var(--line); }
|
||||
.service { min-height: 330px; padding: 22px; border-right: var(--line); border-bottom: var(--line); display: flex; flex-direction: column; justify-content: space-between; }
|
||||
.service:nth-child(2) { background: var(--pink); }
|
||||
.service:nth-child(3) { background: var(--orange); }
|
||||
.number { font: 11px var(--mono); }
|
||||
.service h3 { font-size: clamp(30px, 4vw, 53px); line-height: .86; letter-spacing: -.07em; text-transform: uppercase; }
|
||||
.service p { max-width: 290px; font-size: 15px; line-height: 1.35; }
|
||||
|
||||
.work { background: var(--orange); }
|
||||
.work .label { color: var(--ink); }
|
||||
.work-grid { display: grid; grid-template-columns: repeat(2, 1fr); border-top: var(--line); border-left: var(--line); }
|
||||
.project { min-height: 300px; padding: 23px; border-right: var(--line); border-bottom: var(--line); display: flex; flex-direction: column; justify-content: flex-end; background: var(--paper); transition: background .2s, color .2s; }
|
||||
.project:hover { background: var(--yellow); color: var(--ink); }
|
||||
.project:nth-child(2) { background: var(--ink); color: var(--paper); }
|
||||
.project:nth-child(3) { background: var(--pink); }
|
||||
.project:nth-child(4) { background: var(--yellow); }
|
||||
.project h3 { font-size: clamp(42px, 7vw, 90px); line-height: .8; letter-spacing: -.09em; text-transform: uppercase; }
|
||||
.project p { margin-top: 18px; font: 11px var(--mono); text-transform: uppercase; }
|
||||
|
||||
.quote { max-width: 980px; margin: 0 auto; text-align: center; }
|
||||
blockquote { font-size: clamp(32px, 5.8vw, 76px); line-height: .93; letter-spacing: -.07em; }
|
||||
cite { display: block; margin-top: 30px; color: var(--muted); font: 11px var(--mono); font-style: normal; text-transform: uppercase; }
|
||||
|
||||
.contact { padding: 110px 0 80px; background: var(--ink); color: var(--paper); text-align: center; }
|
||||
.contact h2 { max-width: 1050px; margin: 0 auto; font-size: clamp(55px, 11vw, 150px); line-height: .8; letter-spacing: -.1em; text-transform: uppercase; }
|
||||
.contact h2 span { color: var(--yellow); }
|
||||
.contact .cta { color: var(--ink); margin-top: 54px; }
|
||||
.links { display: flex; justify-content: center; flex-wrap: wrap; gap: 26px; margin-top: 58px; font: 11px var(--mono); text-transform: uppercase; }
|
||||
footer { padding: 22px 0; background: var(--ink); color: var(--paper); border-top: 1px solid #555; font: 11px var(--mono); text-transform: uppercase; }
|
||||
footer .wrap { display: flex; justify-content: space-between; gap: 20px; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.wrap { width: min(100% - 30px, 1320px); }
|
||||
nav ul { display: none; }
|
||||
.menu-mark { display: block; }
|
||||
.hero { display: block; min-height: 0; }
|
||||
.hero-copy { padding: 74px 0 64px; border-right: 0; }
|
||||
.hero h1 { font-size: clamp(62px, 18vw, 110px); }
|
||||
.hero-side { min-height: 330px; margin: 0 -15px; padding: 20px 15px; }
|
||||
.sun { width: 180px; box-shadow: 8px 8px 0 var(--ink); }
|
||||
section { padding: 72px 0; }
|
||||
.section-head { display: block; margin-bottom: 40px; }
|
||||
.section-title { margin-top: 25px; }
|
||||
.services, .work-grid { grid-template-columns: 1fr; }
|
||||
.service { min-height: 270px; }
|
||||
.project { min-height: 230px; }
|
||||
footer .wrap { display: block; line-height: 2; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="wrap">
|
||||
<nav>
|
||||
<a class="brand" href="#top">Miles Wolf Allen</a>
|
||||
<span class="menu-mark">Scroll / Explore</span>
|
||||
<ul>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#work">Work</a></li>
|
||||
<li><a href="about-me.html">About</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
<div class="wrap">
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<p class="kicker">Independent developer / creative technologist</p>
|
||||
<h1>Build things that <em>refuse</em> to whisper.</h1>
|
||||
<p class="lead">I design weird tools, playful software, and 3D experiments that turn ideas into experiences people actually remember.</p>
|
||||
<a class="cta" href="#contact">Start a conversation <span>↗</span></a>
|
||||
</div>
|
||||
<div class="hero-side" aria-hidden="true">
|
||||
<span class="index">MWA / 001</span>
|
||||
<div class="sun">✳</div>
|
||||
<p class="side-note">Good ideas<br>move fast.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="ticker"><div class="ticker-track">Developer ✳ 3D Artist ✳ Emulator Builder ✳ Game Maker ✳ Toolsmith ✳ Developer ✳ 3D Artist ✳ Emulator Builder ✳ Game Maker ✳ Toolsmith ✳</div></div>
|
||||
|
||||
<div class="wrap">
|
||||
<section id="services">
|
||||
<div class="section-head">
|
||||
<p class="label">01 / What I do</p>
|
||||
<h2 class="section-title">A point of view, <span>not just a product.</span></h2>
|
||||
</div>
|
||||
<div class="services">
|
||||
<article class="service"><span class="number">01</span><h3>Prototype<br>Systems</h3><p>Explore, test, and ship weird ideas fast — from game mechanics to emulator tooling and browser experiments.</p></article>
|
||||
<article class="service"><span class="number">02</span><h3>Creative<br>Code</h3><p>Turn visuals, motion, and interaction into expressive interfaces with attention to feeling and clarity.</p></article>
|
||||
<article class="service"><span class="number">03</span><h3>3D + UI<br>Direction</h3><p>Shape side projects, concepts, and production-facing systems with a strong visual and technical identity.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="work" id="work">
|
||||
<div class="wrap">
|
||||
<div class="section-head"><p class="label">02 / Selected work</p><h2 class="section-title">Made to be <span>remembered.</span></h2></div>
|
||||
<div class="work-grid">
|
||||
<article class="project"><h3>FEDL</h3><p>Emulator engineering / Game tooling</p></article>
|
||||
<article class="project"><h3>OmniEmu</h3><p>Platform experimentation / Browser emulation</p></article>
|
||||
<article class="project"><h3>Gelectron</h3><p>Creative software / UI prototypes</p></article>
|
||||
<article class="project"><h3>Nova</h3><p>Game concept / Iterative design</p></article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="wrap"><section><div class="quote"><blockquote>“The work feels intentional, curious, and impossible to ignore — it turns complex ideas into something people instantly understand.”</blockquote><cite>— Personal note from building around the internet</cite></div></section></div>
|
||||
|
||||
<section class="contact" id="contact">
|
||||
<div class="wrap">
|
||||
<h2>Make your next idea <span>impossible</span> to ignore.</h2>
|
||||
<a class="cta" href="mailto:[email protected]">[email protected] <span>↗</span></a>
|
||||
<div class="links"><a href="projects/">Projects</a><a href="about-me.html">About</a><a href="blog/">Blog</a><a href="https://github.com/mileswolfallen2">GitHub</a></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer><div class="wrap"><span>© 2026 Miles Wolf Allen</span><span>Built with intent, not templates.</span></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
/* ============================================================
|
||||
REMAKES — SICK AGENCY broadside design system (shared)
|
||||
Loud printed broadside. Maximalist zine spread.
|
||||
Yellow / orange / cobalt full-bleed bands, no shadows,
|
||||
no gradients, no rounded corners except 999px pills.
|
||||
============================================================ */
|
||||
|
||||
:root{
|
||||
--yellow:#f5b700;
|
||||
--orange:#ff4e27;
|
||||
--cobalt:#0029ff;
|
||||
--sienna:#4d170c;
|
||||
--black:#000000;
|
||||
--white:#ffffff;
|
||||
--morganite:'Anton','Bebas Neue',Impact,sans-serif;
|
||||
--thunder:'Josefin Sans',ui-sans-serif,sans-serif;
|
||||
--sentient:'Spectral',Georgia,serif;
|
||||
--fine:Georgia,'Times New Roman',times,serif;
|
||||
}
|
||||
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;overflow-x:hidden;}
|
||||
body{
|
||||
background:var(--yellow);color:var(--black);
|
||||
font-family:var(--sentient);line-height:1.5;
|
||||
overflow-x:hidden;-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
img{display:block;max-width:100%;}
|
||||
|
||||
.band{width:100%;}
|
||||
.yellow{background:var(--yellow);color:var(--black);}
|
||||
.orange{background:var(--orange);color:var(--white);}
|
||||
.cobalt{background:var(--cobalt);color:var(--white);}
|
||||
.ink{background:var(--black);color:var(--white);}
|
||||
|
||||
/* Pill buttons — the only softness in the system */
|
||||
.btn{
|
||||
display:inline-block;font-family:var(--sentient);font-size:16px;
|
||||
padding:8px 26px;border-radius:999px;border:2px solid var(--cobalt);
|
||||
color:var(--cobalt);transition:background .2s,color .2s;
|
||||
}
|
||||
.btn:hover{background:var(--cobalt);color:var(--white);}
|
||||
.btn.primary{background:var(--cobalt);border-color:var(--cobalt);color:var(--white);}
|
||||
.btn.primary:hover{background:var(--black);border-color:var(--black);}
|
||||
.btn.on-dark{border-color:var(--white);color:var(--white);}
|
||||
.btn.on-dark:hover{background:var(--white);color:var(--black);}
|
||||
|
||||
/* Sticker badges — circular, contrasting, CTA punctuation */
|
||||
.sticker{
|
||||
position:absolute;display:grid;place-items:center;align-content:center;
|
||||
width:140px;height:140px;border-radius:50%;
|
||||
font-family:var(--fine);font-size:11px;line-height:1.2;text-align:center;
|
||||
letter-spacing:.05em;text-transform:uppercase;
|
||||
}
|
||||
.sticker b{font-size:20px;font-family:var(--morganite);font-weight:400;display:block;line-height:1;}
|
||||
.sticker small{display:block;margin-top:4px;font-size:9px;}
|
||||
|
||||
/* ================= ARTICLE (devlog / about) ================= */
|
||||
.article-nav{
|
||||
position:sticky;top:0;z-index:50;background:var(--yellow);color:var(--black);
|
||||
display:flex;align-items:center;justify-content:space-between;gap:16px;
|
||||
padding:14px 5vw;border-bottom:3px solid var(--black);flex-wrap:wrap;
|
||||
}
|
||||
.article-nav .brand{font-family:var(--morganite);font-weight:400;font-size:30px;line-height:1;display:flex;align-items:center;gap:10px;}
|
||||
.article-nav .brand .swatch{width:16px;height:16px;border-radius:50%;background:var(--cobalt);display:inline-block;}
|
||||
.article-nav .links{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
||||
.article-nav .links a{font-family:var(--sentient);font-size:13px;letter-spacing:-.01em;padding:6px 16px;border-radius:999px;border:2px solid var(--black);transition:background .2s,color .2s;}
|
||||
.article-nav .links a:hover{background:var(--black);color:var(--yellow);}
|
||||
.article-nav .links a.cta{background:var(--cobalt);border-color:var(--cobalt);color:var(--white);}
|
||||
.article-nav .links a.cta:hover{background:var(--black);border-color:var(--black);color:var(--yellow);}
|
||||
@media(max-width:640px){.article-nav .links{width:100%;order:3;}}
|
||||
|
||||
.article-wrap{max-width:880px;margin:0 auto;padding:0 5vw;}
|
||||
|
||||
.article-hero{padding:70px 0 50px;position:relative;}
|
||||
.article-hero .sticker{right:-20px;top:10px;}
|
||||
.article-eyebrow{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.18em;display:inline-flex;align-items:center;gap:12px;margin-bottom:26px;}
|
||||
.article-eyebrow::before{content:'';width:34px;height:2px;background:var(--black);display:inline-block;}
|
||||
.article-hero h1{
|
||||
font-family:var(--morganite);font-weight:400;
|
||||
font-size:clamp(66px,13vw,150px);line-height:.72;letter-spacing:.01em;
|
||||
text-transform:uppercase;margin-bottom:34px;
|
||||
}
|
||||
.article-tagline{font-size:16px;line-height:1.44;letter-spacing:-.015em;max-width:520px;}
|
||||
.article-tags{display:flex;gap:10px;flex-wrap:wrap;margin-top:30px;}
|
||||
.article-tag{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;border:2px solid var(--black);border-radius:999px;padding:4px 16px;}
|
||||
.article-date{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;opacity:.6;}
|
||||
|
||||
/* Body */
|
||||
.article-body{
|
||||
padding:44px clamp(24px,5vw,60px) 90px;
|
||||
background:var(--white);
|
||||
color:var(--black);
|
||||
border:3px solid var(--black);
|
||||
border-top:none;
|
||||
box-shadow:8px 8px 0 var(--black);
|
||||
max-width:1000px;
|
||||
margin:0 auto;
|
||||
}
|
||||
.hairline{height:3px;background:var(--black);width:100%;}
|
||||
.article-body h2{
|
||||
font-family:var(--morganite);font-weight:400;
|
||||
font-size:clamp(40px,7vw,64px);line-height:.75;text-transform:uppercase;
|
||||
margin:70px 0 24px;
|
||||
}
|
||||
.article-body h3{font-family:var(--morganite);font-weight:400;font-size:clamp(26px,4vw,38px);line-height:.8;text-transform:uppercase;margin:44px 0 16px;}
|
||||
.article-body p{font-size:17px;line-height:1.6;letter-spacing:-.012em;margin-bottom:22px;max-width:66ch;}
|
||||
.article-body strong{font-weight:700;}
|
||||
.article-body a{color:var(--cobalt);border-bottom:2px solid var(--cobalt);}
|
||||
.article-body a:hover{background:var(--cobalt);color:var(--white);}
|
||||
.article-body ul{margin:6px 0 26px;list-style:none;}
|
||||
.article-body ul li{
|
||||
position:relative;padding:14px 0 14px 34px;font-size:17px;line-height:1.5;letter-spacing:-.012em;
|
||||
border-bottom:2px solid var(--black);max-width:66ch;
|
||||
}
|
||||
.article-body ul li::before{content:'→';position:absolute;left:0;top:15px;font-size:18px;color:var(--cobalt);}
|
||||
.article-body blockquote{
|
||||
font-family:var(--thunder);font-weight:300;
|
||||
font-size:clamp(24px,4vw,36px);line-height:1.1;
|
||||
padding:30px 0;margin:50px 0;border-top:4px solid var(--cobalt);border-bottom:4px solid var(--cobalt);
|
||||
max-width:24ch;
|
||||
}
|
||||
.article-body blockquote::before{content:'“';display:block;font-family:var(--fine);font-size:44px;color:var(--cobalt);}
|
||||
.article-body code{background:rgba(0,0,0,.06);border:1px solid rgba(0,0,0,.2);padding:1px 7px;font-family:var(--fine);font-size:.92em;}
|
||||
|
||||
/* Footer band */
|
||||
.article-footer{background:var(--black);color:var(--white);padding:70px 5vw;text-align:center;}
|
||||
.article-footer .foot-brand{font-family:var(--morganite);font-weight:400;font-size:clamp(48px,10vw,110px);line-height:.7;color:var(--yellow);text-transform:uppercase;margin-bottom:28px;}
|
||||
.article-footer .foot-links{display:flex;gap:16px;flex-wrap:wrap;justify-content:center;margin-bottom:26px;}
|
||||
.article-footer .foot-links a{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;border-radius:999px;border:1px solid var(--white);padding:7px 18px;transition:background .2s,color .2s;}
|
||||
.article-footer .foot-links a:hover{background:var(--yellow);color:var(--black);border-color:var(--yellow);}
|
||||
.article-footer .foot-note{color:rgba(255,255,255,.55);font-size:13px;line-height:1.7;}
|
||||
.article-footer .foot-note a{color:var(--yellow);border-bottom:1px solid var(--yellow);}
|
||||
|
||||
/* Back to devlog pill */
|
||||
.back-row{display:flex;gap:16px;flex-wrap:wrap;margin-top:60px;}
|
||||
|
||||
/* ============================================================
|
||||
FUN — hover pops, ambient float, playful touches
|
||||
Shared: applies to both the index and every article page.
|
||||
============================================================ */
|
||||
|
||||
/* --- keyframes --- */
|
||||
@keyframes bounceDown{0%,100%{transform:translateY(0);}50%{transform:translateY(6px);}}
|
||||
@keyframes bob{0%,100%{transform:translateY(0) rotate(-1deg);}50%{transform:translateY(-10px) rotate(1deg);}}
|
||||
@keyframes wiggle{0%,100%{transform:rotate(-2deg);}25%{transform:rotate(3deg);}75%{transform:rotate(-3deg);}}
|
||||
@keyframes spinSlow{to{transform:rotate(360deg);}}
|
||||
@keyframes blinkText{0%,100%{opacity:1;}50%{opacity:.35;}}
|
||||
@keyframes jiggle{0%,100%{transform:rotate(0) translateY(0);}20%{transform:rotate(-8deg);}40%{transform:rotate(8deg);}60%{transform:rotate(-5deg);}80%{transform:rotate(5deg);}}
|
||||
|
||||
/* --- buttons: springy pop + press --- */
|
||||
.btn{
|
||||
transition:transform .18s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s,box-shadow .2s;
|
||||
will-change:transform;
|
||||
}
|
||||
.btn:hover{transform:translateY(-3px) rotate(-1deg);}
|
||||
.btn:active{transform:translateY(1px) scale(.94);}
|
||||
|
||||
/* --- sticker badge: gently bobs; heavier wiggle on hover --- */
|
||||
.sticker{animation:bob 5s ease-in-out infinite;transition:filter .2s;}
|
||||
.sticker:hover{animation:wiggle .5s ease-in-out infinite;}
|
||||
.sticker b{transition:transform .18s cubic-bezier(.34,1.56,.64,1);}
|
||||
.sticker:hover b{transform:scale(1.12);}
|
||||
|
||||
/* --- nav links: bounce in to say hi --- */
|
||||
.article-nav .brand .swatch{transition:transform .3s;}
|
||||
.article-nav .brand:hover .swatch{transform:scale(1.35) rotate(180deg);}
|
||||
.article-nav .links a{transition:transform .16s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s;}
|
||||
.article-nav .links a:hover{transform:translateY(-3px);}
|
||||
|
||||
/* --- footer links --- */
|
||||
.article-footer .foot-brand{transition:letter-spacing .3s;}
|
||||
.article-footer .foot-brand:hover{letter-spacing:.06em;color:var(--white);}
|
||||
.article-footer .foot-links a{transition:transform .16s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s;}
|
||||
.article-footer .foot-links a:hover{transform:translateY(-3px) rotate(-2deg);}
|
||||
|
||||
/* --- article hero title: cheeky hover shift --- */
|
||||
.article-hero h1{
|
||||
transition:transform .3s cubic-bezier(.34,1.56,.64,1),letter-spacing .3s;
|
||||
}
|
||||
.article-hero h1:hover{letter-spacing:.015em;}
|
||||
|
||||
/* --- article tags: wiggle --- */
|
||||
.article-tag{transition:background .2s,color .2s,transform .15s;}
|
||||
.article-tag:hover{background:var(--black);color:var(--yellow);transform:rotate(-6deg);}
|
||||
|
||||
/* --- body links --- */
|
||||
.article-body a{transition:background .2s,color .2s,padding .2s;}
|
||||
.article-body a:hover{padding:0 4px;}
|
||||
|
||||
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.001ms!important;}}
|
||||
@@ -0,0 +1,100 @@
|
||||
<!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 Command-Move | Miles Wolf Allen</title>
|
||||
<meta name="description" content="The story behind Command-Move — a macOS app that does what Finder should have done ten years ago: absolute path display, quick copy, and move-to-folder.">
|
||||
<link rel="canonical" href="https://milesallen.site/remake/command-move-devlog.html">
|
||||
<meta property="og:title" content="Why I Built Command-Move | Miles Wolf Allen">
|
||||
<meta property="og:description" content="A macOS app that does what Finder should have done ten years ago.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2026-02-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Why I Built Command-Move | Miles Wolf Allen",
|
||||
"description": "A macOS app that does what Finder should have done ten years ago.",
|
||||
"author": { "@type": "Person", "name": "Miles Wolf Allen", "url": "https://milesallen.site/" },
|
||||
"datePublished": "2026-02-15",
|
||||
"url": "https://milesallen.site/remake/command-move-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/remake/command-move-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
</head>
|
||||
<body class="yellow">
|
||||
|
||||
<nav class="article-nav">
|
||||
<a class="brand" href="index.html"><span class="swatch"></span>MILES</a>
|
||||
<div class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="index.html#projects">Projects</a>
|
||||
<a href="index.html#blog">Devlog</a>
|
||||
<a class="cta" href="index.html#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="article-wrap article-hero">
|
||||
<div class="article-eyebrow">// DEVLOG 003 · 2026</div>
|
||||
<h1>Why I Built Command-Move</h1>
|
||||
<p class="article-tagline">Finder has been around for 25 years and still won't show you an absolute path. I got tired of waiting.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Swift</span>
|
||||
<span class="article-tag">macOS</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="hairline"></div>
|
||||
|
||||
<article class="article-wrap article-body">
|
||||
|
||||
<h2>The Irritation</h2>
|
||||
<p>I spend most of my day in the terminal. Paths are second nature to me. But every so often I have to leave the warm embrace of the command line and deal with Finder — and every time, I hit the same wall. I want to copy the absolute path of a file, or I want to move a file into the folder that's sitting right there. And Finder just... won't let me. Not conveniently, anyway.</p>
|
||||
<p>Sure, there are hidden shortcuts. You can Command+Option+C to copy a path, if you remember that exact combo across a network share. There are third-party extensions. But all of them feel like bandaids on a wound that shouldn't exist.</p>
|
||||
|
||||
<h2>The Should-Have-Been-Obvious Features</h2>
|
||||
<p>I want two things, and I refuse to believe I'm the only one:</p>
|
||||
<ul>
|
||||
<li><strong>Absolute path display:</strong> Show me where I am. Don't make me guess the path to the file I'm looking at.</li>
|
||||
<li><strong>Quick copy:</strong> Copy a file's path in one click, so I can paste it anywhere — a terminal, a setting, a docs snippet.</li>
|
||||
<li><strong>Move-to-folder:</strong> Drag a file onto a folder and have it actually move there without a step-by-step dialog dance.</li>
|
||||
</ul>
|
||||
<p>These are things every power user wants. They're the reasons apps like Path Finder exist and sell for $39.99. But I didn't want to pay for a file manager — I wanted Finder fixed. And since Apple wasn't going to do it, I built the context-menu command myself.</p>
|
||||
|
||||
<h2>Building It in Swift</h2>
|
||||
<p>macOS gives you one genuinely great hook for this: the Finder Sync extension. It lets a small Swift app inject items into the Finder's context menu. From there it's a native Swift + AppKit app with a menu bar icon, a settings window, and the extension doing the real work.</p>
|
||||
<p>Swift is the right tool here. AppKit gives you first-class access to all the things that matter — the pasteboard, the file manager, URL handling. And because it's a native Mac app, it starts instantly and uses almost no memory. No Electron runtime. No bundled browser. Just a small, fast, focused utility.</p>
|
||||
|
||||
<blockquote>Every power-user feature Apple doesn't ship is a missing-party app built once, alone, and quietly.</blockquote>
|
||||
|
||||
<h2>The Result</h2>
|
||||
<p>Command-Move is a tiny app that lives in your menu bar. Click it (or use a keyboard shortcut) and you get a floating window showing the current absolute path of your frontmost Finder window. One button copies it to your clipboard. Drag a file onto the folder icon and it moves there instantly. Done.</p>
|
||||
<p>It's not flashy. It doesn't have a 47-tab settings panel. It does exactly three things and does them well — the way macOS utilities used to be.</p>
|
||||
|
||||
<div class="back-row">
|
||||
<a class="btn primary" href="index.html#blog">↩ Back to Devlog</a>
|
||||
<a class="btn" href="index.html">Home</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<footer class="article-footer">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>The Diskette Project | Miles Wolf Allen</title>
|
||||
<meta name="description" content="How The Diskette Project started and grew — part design experiment, part community bet, part creative exercise, and what 5,000+ people taught us about physical media, play, and internet lore.">
|
||||
<link rel="canonical" href="https://milesallen.site/remake/diskette-devlog.html">
|
||||
<meta property="og:title" content="The Diskette Project | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Part design experiment, part community bet, part creative exercise.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2026-08-11">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "The Diskette Project | Miles Wolf Allen",
|
||||
"description": "Part design experiment, part community bet, part creative exercise.",
|
||||
"author": { "@type": "Person", "name": "Miles Wolf Allen", "url": "https://milesallen.site/" },
|
||||
"datePublished": "2026-08-11",
|
||||
"url": "https://milesallen.site/remake/diskette-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/remake/diskette-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
</head>
|
||||
<body class="yellow">
|
||||
|
||||
<nav class="article-nav">
|
||||
<a class="brand" href="index.html"><span class="swatch"></span>MILES</a>
|
||||
<div class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="index.html#projects">Projects</a>
|
||||
<a href="index.html#blog">Devlog</a>
|
||||
<a class="cta" href="index.html#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="article-wrap article-hero">
|
||||
<div class="article-eyebrow">// DEVLOG 006 · 2026</div>
|
||||
<h1>The Diskette Project</h1>
|
||||
<p class="article-tagline">Part design experiment, part community bet, part creative exercise. It accidentally became a collective's most-loved project.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Design</span>
|
||||
<span class="article-tag">Collective</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="hairline"></div>
|
||||
|
||||
<article class="article-wrap article-body">
|
||||
|
||||
<h2>How It Started</h2>
|
||||
<p>The Diskette Project started with a question: what if we thought about software the way we think about the objects that used to hold it? A floppy disk was small, physical, and precious. It held something complete and self-contained. You could hand it to someone. It had a weight to it.</p>
|
||||
<p>We started printing saves — little releases, zines, and resources — as beautiful portable files that look and feel like a small object rather than another link in a feed. Each one gets its own retro-styled landing page, like a physical sleeve sliding out of a desk drawer. Something you'd want to keep.</p>
|
||||
|
||||
<h2>Design as a Frame</h2>
|
||||
<p>The name "Diskette" leans into the nostalgia, but the design language is its own thing. Lots of white, near-infinite blank space, thin rules, and a tiny serif for the fine print. It feels like the cover of a pamphlet from 1989 that somehow lives on the internet. Understated is the point — the files are the stars, not the chrome.</p>
|
||||
<p>There's a deliberate tension in the whole thing. On one hand it's resolutely nostalgic and physical. On the other it's a fully digital, link-shareable, instant-access thing. We never let one win over the other. The absurdity is the appeal.</p>
|
||||
|
||||
<h2>Community Happened Anyway</h2>
|
||||
<p>We never really planned for people to show up. The Diskette Project was meant to be mostly for us — an ongoing illustration exercise where a handful of us design fake products, absurd little files, and tiny print-flavored web pages. But then people started sharing them. And then more people. And then it became a full-blown community project.</p>
|
||||
<p>At our peak, over 5,000 people joined our community. Not bad for a thing that started as "wouldn't it be funny if..." Actually, the community did what good communities do — they made it better. They built their own diskettes, riffed on the design, and pushed the whole thing somewhere none of us could have gotten to alone. It's equal parts mine and everyone's.</p>
|
||||
|
||||
<blockquote>The best projects are the ones the internet insists on finishing for you.</blockquote>
|
||||
|
||||
<h2>It's Still Kind of Silly</h2>
|
||||
<p>Let's be honest — it's a project about digital floppy disks. It's absurd. And that's exactly why it works. The internet desperately wants to play. The second you hand people permission to be silly and make something beautiful while they do it, they'll show up in droves.</p>
|
||||
<p>The Diskette Project taught me that the best creative work usually starts as a joke, a bet, or a dare — never as a "strategic initiative." Give people something tactile-feeling and strange, and give them room to add to it. You'll be surprised what comes back.</p>
|
||||
|
||||
<h2>What's Next</h2>
|
||||
<p>We're not sure where Diskette goes from here. And honestly, that's part of the fun. It might keep growing. It might plateau. It might just exist as a strange little archive of playful internet objects. Any of those outcomes beats never having made it at all.</p>
|
||||
|
||||
<div class="back-row">
|
||||
<a class="btn primary" href="index.html#blog">↩ Back to Devlog</a>
|
||||
<a class="btn" href="index.html">Home</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<footer class="article-footer">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Fedl: A Discord Community for Gamers | Miles Wolf Allen</title>
|
||||
<meta name="description" content="How I started Fedl — a cozy online community rooted in Valentine's Day, built on a simple principle: zero tolerance for toxic behavior, total support for the people there to game.">
|
||||
<link rel="canonical" href="https://milesallen.site/remake/fedl-devlog.html">
|
||||
<meta property="og:title" content="Fedl: A Discord Community for Gamers | Miles Wolf Allen">
|
||||
<meta property="og:description" content="A cozy online community rooted in Valentine's Day, built on a simple principle: zero tolerance for toxic behavior.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2025-06-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Fedl: A Discord Community for Gamers | Miles Wolf Allen",
|
||||
"description": "A cozy online community rooted in Valentine's Day, built on a simple principle: zero tolerance for toxic behavior.",
|
||||
"author": { "@type": "Person", "name": "Miles Wolf Allen", "url": "https://milesallen.site/" },
|
||||
"datePublished": "2025-06-15",
|
||||
"url": "https://milesallen.site/remake/fedl-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/remake/fedl-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
</head>
|
||||
<body class="yellow">
|
||||
|
||||
<nav class="article-nav">
|
||||
<a class="brand" href="index.html"><span class="swatch"></span>MILES</a>
|
||||
<div class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="index.html#projects">Projects</a>
|
||||
<a href="index.html#blog">Devlog</a>
|
||||
<a class="cta" href="index.html#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="article-wrap article-hero">
|
||||
<div class="article-eyebrow">// DEVLOG 002 · 2025</div>
|
||||
<h1>Fedl</h1>
|
||||
<p class="article-tagline">A cozy online community rooted in Valentine's Day — and proof that a server can feel like a home.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Node.js</span>
|
||||
<span class="article-tag">Community</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="hairline"></div>
|
||||
|
||||
<article class="article-wrap article-body">
|
||||
|
||||
<h2>It Started on Valentine's Day</h2>
|
||||
<p>Fedl began as a Valentine's Day special. A small server, a silly name, a handful of friends who wanted a place to hang out and play games without the noise. It wasn't supposed to become anything. But it did — because it filled a real gap.</p>
|
||||
<p>Gaming communities are loud. They're full of memes and cliques and, often, genuinely toxic behavior. I wanted the exact opposite: a quiet, supportive space where people could show up, be themselves, and game. So I built it around a single, non-negotiable principle.</p>
|
||||
|
||||
<h2>The One Rule That Matters</h2>
|
||||
<p>The entire philosophy of Fedl comes down to one line: <strong>zero tolerance for toxic behavior, total support for the people there to game.</strong> That's it. No endless lists of what's allowed and what isn't. No gray areas to lawyer your way around. If you're here to bring other people down, you're out. If you're here to game and be kind, you're home.</p>
|
||||
<p>That rule is easy to state and genuinely hard to run. It means actually moderating, not just posting a rules channel that nobody reads. It means having a mod team that cares. The result is a community where people feel safe enough to be themselves — which is the whole point.</p>
|
||||
|
||||
<h2>The Powered-By-Next.js Detail</h2>
|
||||
<p>There's a fun little technical footnote to all this. I connected the site to Discord using a Next.js API route — a single API route that acts as a bridge between a beautiful, hand-built website and a fast, modern Discord community. It's a reminder that the community tech stack doesn't have to be ugly or bloated.</p>
|
||||
<p>Every time I build a small bot or a little integration, I'm reminded how far "cozy internet" has come. You can stand up something thoughtful and homey with a few lines of TypeScript and a clear vision for what you want the space to feel like.</p>
|
||||
|
||||
<blockquote>Toxicity isn't a personality trait; it's a choice. And we don't have to make room for it.</blockquote>
|
||||
|
||||
<h2>Why It Matters</h2>
|
||||
<p>The internet can feel huge and hostile. Fedl is my small argument that it doesn't have to be. You can curate the noise out, you can set the tone, and you can build a corner of the internet where people actually want to spend their evenings.</p>
|
||||
<p>Growing a community is slow work. You earn it one good conversation at a time, one game night, one person deciding to stick around because the room felt safe. That's the real devlog — not the code, but the thousands of small moments that turned a Valentine's Day joke into a home.</p>
|
||||
|
||||
<div class="back-row">
|
||||
<a class="btn primary" href="index.html#blog">↩ Back to Devlog</a>
|
||||
<a class="btn" href="index.html">Home</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<footer class="article-footer">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,123 @@
|
||||
<!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 Gelectron | Miles Wolf Allen</title>
|
||||
<meta name="description" content="Why I built Gelectron — a drop-in replacement for Electron using native web views instead of bundling Chromium. Memory usage, efficiency, and the problem with bundling a full browser.">
|
||||
<link rel="canonical" href="https://milesallen.site/remake/gelectron-devlog.html">
|
||||
<meta property="og:title" content="Why I Built Gelectron | Miles Wolf Allen">
|
||||
<meta property="og:description" content="Why I built Gelectron — a drop-in replacement for Electron using native web views instead of bundling Chromium.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2026-03-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Why I Built Gelectron | Miles Wolf Allen",
|
||||
"description": "Why I built Gelectron — a drop-in replacement for Electron using native web views instead of bundling Chromium.",
|
||||
"author": { "@type": "Person", "name": "Miles Wolf Allen", "url": "https://milesallen.site/" },
|
||||
"datePublished": "2026-03-15",
|
||||
"url": "https://milesallen.site/remake/gelectron-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/remake/gelectron-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
</head>
|
||||
<body class="yellow">
|
||||
|
||||
<nav class="article-nav">
|
||||
<a class="brand" href="index.html"><span class="swatch"></span>MILES</a>
|
||||
<div class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="index.html#projects">Projects</a>
|
||||
<a href="index.html#blog">Devlog</a>
|
||||
<a class="cta" href="index.html#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="article-wrap article-hero">
|
||||
<div class="article-eyebrow">// DEVLOG 005 · 2026</div>
|
||||
<h1>Why I Built Gelectron</h1>
|
||||
<p class="article-tagline">Electron bundles Chromium with every app. 150-300MB on disk, hundreds of MBs of RAM. Native web views fix that without changing your code.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Rust</span>
|
||||
<span class="article-tag">Native</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="hairline"></div>
|
||||
|
||||
<article class="article-wrap article-body">
|
||||
|
||||
<h2>The Electron Problem</h2>
|
||||
<p>Electron is everywhere. VS Code, Discord, Slack, Figma — some of the best desktop apps in the world are built with it. And for good reason: you write one web app, and it runs on macOS, Windows, and Linux. The developer experience is fantastic.</p>
|
||||
<p>But there's a cost. Every single Electron app bundles its own copy of Chromium. That's not a shared system library — it's a full copy of Chrome, embedded inside your app. A simple todo app? 150MB on disk. A chat app? 200MB. A complex tool like VS Code? Over 300MB.</p>
|
||||
<p>And it's not just disk space. Each Electron app spins up its own Chromium renderer process, its own GPU compositing pipeline, its own memory space. If you have three Electron apps open, you have three copies of Chromium running. On a laptop with 8GB of RAM, that adds up fast.</p>
|
||||
|
||||
<h2>The Memory Issue</h2>
|
||||
<p>This is what bothered me the most. Memory usage in Electron apps is genuinely high. A minimal "hello world" Electron app uses 80-120MB of RAM. That's more than some full desktop applications. The reason is Chromium — it's a full web browser, and browsers are memory hogs.</p>
|
||||
<p>Chromium keeps multiple processes running: the main process, renderer processes for each window, GPU processes, utility processes. Each one consumes memory. Even when your app is idle, Chromium is still managing tabs you don't have, networking you don't need, and features you'll never use.</p>
|
||||
<p>On my Mac, I routinely have 5-6 apps open at once. If three of them are Electron-based, that's easily 1-1.5GB of RAM just for those apps. On a machine where I'm also running Docker, VS Code, and a browser with 30 tabs... something has to give.</p>
|
||||
|
||||
<h2>The Native Web View Approach</h2>
|
||||
<p>There are alternatives to Electron. Tauri uses the system's webview, which solves the disk space problem but introduces consistency issues — Safari's webview on macOS is different from WebView2 on Windows, which is different from WebKitGTK on Linux. You lose predictability.</p>
|
||||
<p>I wanted something closer to Electron's developer experience. Same Node.js runtime. Same IPC model. Same APIs. But instead of bundling a full Chromium binary, Gelectron maps Electron's rendering layer to the OS-native web view. WKWebView on macOS/iOS, WebView2 on Windows, WebKitGTK on Linux. Each platform gets its own optimized renderer — no browser to bundle, no 150MB tax.</p>
|
||||
|
||||
<h2>Why Native Web Views Win</h2>
|
||||
<p>The efficiency gains come from not duplicating what the OS already provides:</p>
|
||||
<ul>
|
||||
<li><strong>Shared system libraries:</strong> The OS already has its web rendering engine loaded. No need to bundle another one.</li>
|
||||
<li><strong>No process-per-app overhead:</strong> Each Electron app spins up its own Chromium process tree. Native web views share the system renderer.</li>
|
||||
<li><strong>Smaller binary:</strong> No Chromium bundle means binaries measured in megabytes, not hundreds of megabytes.</li>
|
||||
<li><strong>Lower baseline memory:</strong> Without a full browser engine in-process, your app starts with a fraction of Electron's baseline RAM.</li>
|
||||
<li><strong>OS-level optimizations:</strong> Apple, Microsoft, and the GNOME team all optimize their web views for their own platforms. You get that for free.</li>
|
||||
</ul>
|
||||
<p>I'm not claiming Gelectron is faster than Electron today. It's early, experimental, and many APIs are stubs. But the foundation is fundamentally more efficient. The ceiling is higher. And that's what matters.</p>
|
||||
|
||||
<h2>How It Works</h2>
|
||||
<p>Gelectron is a drop-in replacement for Electron. You run <code>gelectron .</code> instead of <code>electron .</code>, and your app should just work. Under the hood:</p>
|
||||
<ul>
|
||||
<li>A JavaScript compatibility layer maps Electron's API surface (BrowserWindow, ipcMain, Menu, Tray, etc.) to Gelectron's internals</li>
|
||||
<li>A Rust N-API addon handles the heavy lifting: window management via winit, native menus via muda, system tray via tray-icon, file dialogs via rfd</li>
|
||||
<li>The platform's native web view handles all rendering — HTML, CSS, JavaScript execution</li>
|
||||
<li>Node.js runs the main process, just like Electron</li>
|
||||
</ul>
|
||||
<p>The goal is zero changes to existing Electron apps. Same <code>require('electron')</code> imports, same APIs, same code. Just no bundled browser.</p>
|
||||
|
||||
<blockquote>The best infrastructure is the one you don't notice. If your app framework is using 200MB of RAM, you notice it.</blockquote>
|
||||
|
||||
<h2>Where It's At</h2>
|
||||
<p>Let me be clear: Gelectron is very early. This is a proof of concept, not a production tool. Many Electron APIs are stubs. Some things break. Auto-updater doesn't exist yet. Multi-window support is limited.</p>
|
||||
<p>But the core works. You can create windows, handle IPC, build menus, show dialogs. The foundation is there. And every week, more APIs get filled in.</p>
|
||||
|
||||
<h2>Why It Matters</h2>
|
||||
<p>Desktop apps aren't going away. And the web platform keeps getting more powerful. The question is: how do we build cross-platform desktop apps without burning 300MB of disk and 200MB of RAM per app?</p>
|
||||
<p>Gelectron is my answer to that question. It's early, it's rough, and it might not go anywhere. But if it works — if we can get Electron's developer experience without bundling a full browser — that changes the economics of desktop app development.</p>
|
||||
<p>Smaller apps. Less memory. Same code. That's the dream.</p>
|
||||
|
||||
<div class="back-row">
|
||||
<a class="btn primary" href="index.html#blog">↩ Back to Devlog</a>
|
||||
<a class="btn" href="index.html">Home</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<footer class="article-footer">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,765 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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.">
|
||||
<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/remake/index.html">
|
||||
<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:type" content="website">
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
<style>
|
||||
/* ============================================================
|
||||
CINEMATIC INTRO — Iad-lab inspired. A colossal MILES wordmark
|
||||
splits apart from a full-bleed crop, crashes back together,
|
||||
flashes cobalt, then blows past the camera to reveal the site.
|
||||
============================================================ */
|
||||
.intro{
|
||||
position:fixed;inset:0;z-index:1000;overflow:hidden;
|
||||
background:#000;
|
||||
}
|
||||
|
||||
/* flying word stage — sits above the black intro, animates independently */
|
||||
.intro-stage{
|
||||
position:fixed;inset:0;z-index:1002;overflow:hidden;pointer-events:none;
|
||||
display:grid;place-items:center;
|
||||
}
|
||||
|
||||
/* dark loader phase */
|
||||
.intro-tag{
|
||||
position:absolute;bottom:34px;left:50%;transform:translateX(-50%);
|
||||
font-family:var(--sentient);font-size:12px;letter-spacing:.28em;
|
||||
text-transform:uppercase;color:#888;white-space:nowrap;
|
||||
}
|
||||
.intro-tag b{color:var(--yellow);font-weight:700;}
|
||||
.intro-bar{
|
||||
position:absolute;bottom:22px;left:50%;transform:translateX(-50%);
|
||||
width:min(320px,70vw);height:2px;background:#222;
|
||||
}
|
||||
.intro-bar .fill{height:100%;width:0;background:var(--orange);}
|
||||
|
||||
/* the slider wordmark (flies in, then lands on the hero) */
|
||||
.intro-word{
|
||||
position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
|
||||
font-family:var(--morganite);font-weight:400;text-transform:uppercase;
|
||||
font-size:clamp(90px,22vw,340px);line-height:.72;letter-spacing:.02em;
|
||||
white-space:nowrap;opacity:0;
|
||||
background:linear-gradient(90deg,var(--orange),var(--yellow));
|
||||
-webkit-background-clip:text;background-clip:text;color:transparent;
|
||||
will-change:transform;
|
||||
}
|
||||
/* main page (revealed after intro) */
|
||||
.site{opacity:0;}
|
||||
|
||||
/* ================= NAV ================= */
|
||||
.nav{
|
||||
position:sticky;top:0;z-index:50;background:var(--yellow);color:var(--black);
|
||||
display:flex;align-items:center;justify-content:space-between;gap:16px;
|
||||
padding:14px 5vw;border-bottom:3px solid var(--black);flex-wrap:wrap;
|
||||
}
|
||||
.nav-brand{font-family:var(--morganite);font-weight:400;font-size:34px;line-height:1;letter-spacing:.02em;display:flex;align-items:center;gap:10px;}
|
||||
.nav-brand .swatch{width:18px;height:18px;border-radius:50%;background:var(--cobalt);display:inline-block;}
|
||||
.nav-links{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
|
||||
.nav-links a{font-family:var(--sentient);font-size:13px;letter-spacing:-.01em;padding:7px 16px;border-radius:999px;border:2px solid var(--black);transition:background .2s,color .2s;}
|
||||
.nav-links a:hover{background:var(--black);color:var(--yellow);}
|
||||
.nav-links a.cta{background:var(--cobalt);border-color:var(--cobalt);color:var(--white);}
|
||||
.nav-links a.cta:hover{background:var(--black);border-color:var(--black);color:var(--yellow);}
|
||||
@media(max-width:640px){.nav-links{width:100%;order:3;}}
|
||||
|
||||
/* ================= HERO ================= */
|
||||
.hero{position:relative;overflow:hidden;padding:70px 5vw 90px;}
|
||||
.hero .sticker{right:6vw;top:30px;}
|
||||
.hero-title{font-family:var(--morganite);font-weight:400;font-size:clamp(96px,22vw,229px);line-height:.7;letter-spacing:.01em;text-transform:uppercase;margin-top:24px;}
|
||||
.hero-title .kick{display:block;font-family:var(--thunder);font-weight:300;font-size:clamp(28px,4.5vw,46px);line-height:1;letter-spacing:.02em;margin-bottom:20px;text-transform:none;}
|
||||
.hero-title .loud{display:block;}
|
||||
.hero-sub{margin-top:70px;max-width:480px;font-size:16px;line-height:1.44;letter-spacing:-.015em;}
|
||||
.hero-sub em{font-weight:600;}
|
||||
.hero-status{display:inline-flex;align-items:center;gap:10px;margin-top:24px;font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;border:1px solid var(--black);border-radius:999px;padding:6px 16px;}
|
||||
.hero-status i{width:9px;height:9px;border-radius:50%;background:var(--cobalt);display:inline-block;animation:pulse 1.6s ease-in-out infinite;}
|
||||
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.25;}}
|
||||
.hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:34px;}
|
||||
|
||||
.hook{text-align:center;padding:9vw 6vw;position:relative;}
|
||||
.hook-text{font-family:var(--thunder);font-weight:300;font-size:clamp(30px,5vw,56px);line-height:1.1;max-width:1100px;margin:0 auto;}
|
||||
.hook-text em{font-family:var(--fine);font-weight:400;}
|
||||
|
||||
/* ================= MARQUEE ================= */
|
||||
.marquee{overflow:hidden;white-space:nowrap;border-top:3px solid var(--black);border-bottom:3px solid var(--black);padding:16px 0;}
|
||||
.marquee .track{display:inline-flex;animation:scrollLeft 22s linear infinite;}
|
||||
.marquee span{font-family:var(--morganite);font-weight:400;font-size:32px;letter-spacing:.02em;margin:0 30px;text-transform:uppercase;flex-shrink:0;}
|
||||
@keyframes scrollLeft{0%{transform:translateX(0);}100%{transform:translateX(-50%);}}
|
||||
|
||||
/* ================= STATS ================= */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);}
|
||||
.stat{border-right:1px solid rgba(255,255,255,.55);border-bottom:1px solid rgba(255,255,255,.55);padding:60px 5vw;text-align:center;}
|
||||
.stat:last-child{border-right:none;}
|
||||
.stat-num{font-family:var(--morganite);font-weight:400;font-size:clamp(64px,10vw,110px);line-height:.7;color:var(--white);}
|
||||
.stat-label{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--white);margin-top:16px;}
|
||||
@media(max-width:760px){.stats{grid-template-columns:1fr 1fr;}.stat:nth-child(2){border-right:none;}.stat:nth-child(1),.stat:nth-child(2){border-bottom:1px solid rgba(255,255,255,.55);}}
|
||||
|
||||
/* ================= SECTIONS ================= */
|
||||
.section{position:relative;padding:110px 5vw 120px;}
|
||||
.sec-label{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.16em;border:1px solid currentColor;border-radius:999px;display:inline-block;padding:5px 16px;margin-bottom:40px;}
|
||||
.sec-title{font-family:var(--morganite);font-weight:400;font-size:clamp(100px,18vw,229px);line-height:.66;letter-spacing:.01em;text-transform:uppercase;max-width:92vw;margin-bottom:48px;}
|
||||
.sec-sub{font-size:12px;font-family:var(--fine);text-transform:uppercase;letter-spacing:.14em;max-width:520px;line-height:1.6;margin-bottom:70px;}
|
||||
.or-white{color:var(--white);}
|
||||
.sec-label.flat{border-color:var(--white);}
|
||||
|
||||
/* ================= ABOUT ================= */
|
||||
.about-grid{display:grid;grid-template-columns:auto 1fr;gap:80px;align-items:start;}
|
||||
.avatar-frame{border:3px solid var(--black);outline:3px solid var(--white);transform:rotate(-3deg);width:240px;background:var(--white);box-shadow:6px 6px 0 var(--black);}
|
||||
.avatar-frame img{width:240px;filter:contrast(1.1);display:block;}
|
||||
.avatar-name{font-family:var(--morganite);font-weight:400;font-size:30px;text-transform:uppercase;margin-top:16px;}
|
||||
.avatar-tag{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:rgba(0,0,0,.55);margin-top:2px;}
|
||||
.about-body{border:3px solid var(--black);background:var(--white);padding:34px;box-shadow:6px 6px 0 var(--black);}
|
||||
.about-body p{font-size:16px;line-height:1.55;letter-spacing:-.015em;max-width:560px;margin-bottom:18px;color:rgba(0,0,0,.9);}
|
||||
.badge-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:26px;}
|
||||
.badge{padding:5px 18px;border-radius:999px;border:2px solid var(--black);font-family:var(--fine);font-size:13px;letter-spacing:-.01em;background:var(--cobalt);color:var(--white);transform:rotate(-2deg);}
|
||||
.badge:nth-child(2){background:var(--orange);color:var(--white);transform:rotate(1deg);}
|
||||
.badge:nth-child(3){background:var(--white);color:var(--black);transform:rotate(-1deg);}
|
||||
.about-cta{display:flex;gap:12px;flex-wrap:wrap;margin-top:30px;align-items:center;}
|
||||
.hair-rule{height:2px;background:var(--black);margin:60px 0 40px;width:100%;}
|
||||
.page-links{display:flex;gap:24px;flex-wrap:wrap;}
|
||||
.page-links a{font-family:var(--fine);font-size:13px;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--black);}
|
||||
.page-links a:hover{color:var(--cobalt);border-color:var(--cobalt);}
|
||||
@media(max-width:820px){.about-grid{grid-template-columns:1fr;gap:40px;}.avatar-frame,.avatar-frame img{width:200px;}}
|
||||
|
||||
/* ================= SKILLS ================= */
|
||||
.skill-group-label{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.16em;margin-bottom:24px;color:var(--white);}
|
||||
.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));margin-bottom:60px;}
|
||||
.skill{border:2px solid var(--white);border-right:none;border-bottom:none;padding:26px 14px;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;min-height:130px;}
|
||||
.skill:nth-child(4n){border-right:2px solid var(--white);}
|
||||
.skill .si{width:30px;height:30px;filter:invert(1);}
|
||||
.skill span{font-family:var(--fine);font-size:13px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.skill.alt1{background:var(--black);color:var(--yellow);}
|
||||
.skill.alt2{background:var(--yellow);color:var(--black);}
|
||||
.skill.alt3{background:var(--white);color:var(--black);}
|
||||
.skill.alt1 .si,.skill.alt2 .si{filter:none;}
|
||||
.skill.alt2 .si{filter:none;}
|
||||
.skill.alt3 .si{filter:invert(0);}
|
||||
.skill.learn{border-style:dashed;opacity:.75;}
|
||||
|
||||
/* ================= PROJECTS ================= */
|
||||
.project-grid{display:grid;grid-template-columns:1fr 1fr;gap:30px;}
|
||||
.proj-card{border:3px solid var(--black);background:var(--white);display:block;padding:34px;transform:rotate(-1deg);transition:transform .25s,background .25s,color .25s;box-shadow:6px 6px 0 var(--black);}
|
||||
.proj-card:nth-child(odd){transform:rotate(1deg);}
|
||||
.proj-card:hover{transform:rotate(-1deg) scale(1.01);}
|
||||
.proj-top{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap;}
|
||||
.proj-tag{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.1em;background:var(--yellow);color:var(--black);border-radius:999px;padding:4px 14px;border:2px solid var(--black);}
|
||||
.proj-tag.on-orange{background:var(--orange);color:var(--white);}
|
||||
.proj-meta{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:rgba(0,0,0,.72);}
|
||||
.proj-title{font-family:var(--morganite);font-weight:400;font-size:clamp(36px,4vw,56px);line-height:.8;text-transform:uppercase;margin-bottom:16px;}
|
||||
.proj-desc{font-size:15px;line-height:1.5;letter-spacing:-.01em;max-width:44ch;color:rgba(0,0,0,.88);}
|
||||
@media(max-width:800px){.project-grid{grid-template-columns:1fr;}}
|
||||
|
||||
/* ================= HIGHLIGHTS ================= */
|
||||
.hl-block{margin-bottom:80px;}
|
||||
.hl-block h3{font-family:var(--morganite);font-weight:400;font-size:clamp(40px,6vw,64px);text-transform:uppercase;margin-bottom:28px;color:var(--yellow);}
|
||||
.hl-row{display:grid;grid-template-columns:repeat(4,1fr);border-top:2px solid var(--yellow);}
|
||||
.hl-cell{border-right:2px solid var(--yellow);border-bottom:2px solid var(--yellow);padding:40px 30px;}
|
||||
.hl-cell:last-child{border-right:none;}
|
||||
.hl-num{font-family:var(--morganite);font-weight:400;font-size:clamp(52px,7vw,84px);line-height:.7;color:var(--white);}
|
||||
.hl-label{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--white);margin-top:16px;}
|
||||
@media(max-width:760px){.hl-row{grid-template-columns:1fr 1fr;}.hl-cell:nth-child(2){border-right:none;}}
|
||||
|
||||
/* ================= GITHUB ================= */
|
||||
.gh-stats{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start;}
|
||||
.gh-stats img{border:3px solid var(--black);background:var(--white);height:165px;width:auto;box-shadow:5px 5px 0 var(--black);}
|
||||
|
||||
/* ================= BLOG ================= */
|
||||
.blog-grid{display:grid;grid-template-columns:1fr 1fr;gap:30px;}
|
||||
.blog-card{border:3px solid var(--white);display:block;padding:30px;transform:rotate(-1deg);transition:transform .25s,border-color .25s;box-shadow:6px 6px 0 var(--black);}
|
||||
.blog-card:nth-child(even){transform:rotate(1deg);}
|
||||
.blog-card:hover{transform:rotate(0);border-color:var(--yellow);}
|
||||
.blog-date{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:var(--yellow);margin-bottom:16px;}
|
||||
.blog-title{font-family:var(--morganite);font-weight:400;font-size:clamp(30px,3vw,40px);text-transform:uppercase;line-height:.85;margin-bottom:14px;}
|
||||
.blog-excerpt{font-size:15px;line-height:1.5;letter-spacing:-.01em;color:rgba(255,255,255,.92);margin-bottom:18px;max-width:46ch;}
|
||||
.blog-tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px;}
|
||||
.blog-tag{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--yellow);color:var(--yellow);border-radius:999px;padding:4px 14px;}
|
||||
@media(max-width:760px){.blog-grid{grid-template-columns:1fr;}}
|
||||
|
||||
/* ================= CONTACT ================= */
|
||||
.contact-card{border:3px solid var(--black);background:var(--white);padding:44px;transform:rotate(-1deg);max-width:640px;position:relative;box-shadow:6px 6px 0 var(--black);}
|
||||
.contact-card .sticker{right:-30px;top:-40px;background:var(--yellow);color:var(--cobalt);transform:rotate(10deg);}
|
||||
.contact-list{display:flex;flex-direction:column;}
|
||||
.contact-row{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:20px 6px;border-bottom:2px solid var(--black);font-size:16px;transition:padding-left .2s,color .2s;flex-wrap:wrap;}
|
||||
.contact-row:last-child{border-bottom:none;}
|
||||
.contact-row span.k{font-family:var(--fine);font-size:11px;text-transform:uppercase;letter-spacing:.14em;opacity:.75;}
|
||||
.contact-row:hover{padding-left:18px;color:var(--cobalt);}
|
||||
|
||||
/* ================= FOOTER ================= */
|
||||
.site-foot{background:var(--black);color:var(--white);padding:70px 5vw 80px;text-align:center;}
|
||||
.site-foot .foot-brand{font-family:var(--morganite);font-weight:400;font-size:clamp(48px,10vw,110px);line-height:.7;color:var(--yellow);text-transform:uppercase;margin-bottom:30px;}
|
||||
.site-foot .foot-links{display:flex;gap:18px;flex-wrap:wrap;justify-content:center;margin-bottom:30px;}
|
||||
.site-foot .foot-links a{font-family:var(--fine);font-size:12px;text-transform:uppercase;letter-spacing:.1em;border-radius:999px;border:1px solid var(--white);padding:7px 18px;transition:background .2s,color .2s;}
|
||||
.site-foot .foot-links a:hover{background:var(--yellow);color:var(--black);border-color:var(--yellow);}
|
||||
.site-foot .foot-note{color:rgba(255,255,255,.55);font-size:13px;line-height:1.7;}
|
||||
.site-foot .foot-note a{color:var(--yellow);border-bottom:1px solid var(--yellow);}
|
||||
|
||||
/* ============================================================
|
||||
FUN — index hover pops + ambient life
|
||||
============================================================ */
|
||||
|
||||
/* nav links: bounce to say hi */
|
||||
.nav-brand .swatch{transition:transform .3s;}
|
||||
.nav-brand:hover .swatch{transform:scale(1.4) rotate(180deg);}
|
||||
.nav-links a{transition:transform .16s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s;}
|
||||
.nav-links a:hover{transform:translateY(-3px);}
|
||||
.nav-links a.cta:hover{transform:translateY(-3px) scale(1.05);}
|
||||
|
||||
/* hero: wordmark gently breathes, kick wiggles */
|
||||
.hero{overflow:hidden;}
|
||||
.hero-title .loud{transition:transform .4s cubic-bezier(.34,1.56,.64,1);}
|
||||
.hero-title:hover .loud{transform:translateX(8px) rotate(-1deg);}
|
||||
.hero-title .kick{display:inline-block;transition:transform .2s;}
|
||||
.hero-title:hover .kick{animation:jiggle .6s ease-in-out;}
|
||||
|
||||
/* hook: happy sway */
|
||||
.hook-text{transition:transform .3s;}
|
||||
.hook-text:hover{transform:scale(1.02) rotate(-.5deg);}
|
||||
|
||||
/* section titles: loud on hover */
|
||||
.sec-title{transition:transform .3s cubic-bezier(.34,1.56,.64,1),letter-spacing .3s;}
|
||||
.sec-title:hover{letter-spacing:.02em;}
|
||||
.section:hover .sec-title{transform:translateX(6px);}
|
||||
.sec-label{transition:transform .2s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s;}
|
||||
.sec-label:hover{transform:rotate(-4deg) scale(1.05);background:var(--cobalt);color:var(--white);}
|
||||
.sec-label.flat:hover{background:var(--orange);color:var(--white);}
|
||||
|
||||
/* stats: number pops + label slides on hover */
|
||||
.stat{transition:background .25s;}
|
||||
.stat:hover{background:rgba(255,255,255,.1);}
|
||||
.stat-num{transition:transform .25s cubic-bezier(.34,1.56,.64,1);display:inline-block;}
|
||||
.stat:hover .stat-num{transform:scale(1.15) rotate(-3deg);}
|
||||
.stat-label{transition:letter-spacing .25s,background .2s;}
|
||||
.stat:hover .stat-label{letter-spacing:.22em;background:var(--yellow);color:var(--black);padding:2px 8px;border-radius:999px;}
|
||||
|
||||
/* skill cells: wobbly wave on hover */
|
||||
.skill{transition:transform .2s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s,opacity .2s;will-change:transform;}
|
||||
.skill:hover{transform:translateY(-6px) rotate(-2deg) scale(1.05);z-index:2;}
|
||||
.skill.learn:hover{transform:translateY(-6px) rotate(2deg) scale(1.05);opacity:1;}
|
||||
.skill-group-label{transition:letter-spacing .3s;}
|
||||
.skill-group-label::after{content:'→';display:inline-block;margin-left:8px;opacity:0;transform:translateX(-6px);transition:opacity .2s,transform .2s;}
|
||||
.skill-group-label:hover{letter-spacing:.22em;}
|
||||
|
||||
/* project cards: lift, straighten, stamp arrow */
|
||||
.proj-card{transition:transform .25s cubic-bezier(.34,1.56,.64,1),background .25s,color .25s,box-shadow .25s;}
|
||||
.proj-card:hover{transform:rotate(0) scale(1.03) translateY(-6px);background:var(--cobalt);color:var(--white);box-shadow:8px 8px 0 var(--black);}
|
||||
.proj-card:hover .proj-desc,.proj-card:hover .proj-meta{color:rgba(255,255,255,.92);}
|
||||
.proj-title{transition:letter-spacing .25s,transform .25s;}
|
||||
.proj-card:hover .proj-title{letter-spacing:.02em;}
|
||||
.proj-tag{transition:transform .2s,background .2s,color .2s;}
|
||||
.proj-card:hover .proj-tag{transform:rotate(-6deg) scale(1.1);}
|
||||
.proj-card:hover .proj-tag.on-orange{background:var(--orange);}
|
||||
|
||||
/* blog cards: rise + straighten */
|
||||
.blog-card{transition:transform .25s cubic-bezier(.34,1.56,.64,1),border-color .25s,background .25s,box-shadow .25s;box-shadow:6px 6px 0 var(--black);}
|
||||
.blog-card:hover{transform:rotate(0) scale(1.03) translateY(-6px);background:var(--black);border-color:var(--yellow);}
|
||||
.blog-tag{transition:background .2s,color .2s,transform .15s;}
|
||||
.blog-card:hover .blog-tag{background:var(--yellow);color:var(--black);transform:rotate(-5deg);}
|
||||
.blog-title{transition:letter-spacing .25s;}
|
||||
.blog-card:hover .blog-title{letter-spacing:.02em;}
|
||||
|
||||
/* contact rows already slide; polish */
|
||||
.contact-row .val{transition:transform .2s;}
|
||||
.contact-row:hover .val{transform:translateX(6px);}
|
||||
|
||||
/* badges: dotted wiggle on hover */
|
||||
.badge{transition:transform .2s cubic-bezier(.34,1.56,.64,1),background .2s,color .2s;}
|
||||
.badge:hover{transform:rotate(-4deg) scale(1.12)!important;}
|
||||
|
||||
/* avatar: straightens to say hi */
|
||||
.avatar-frame{transition:transform .3s cubic-bezier(.34,1.56,.64,1);}
|
||||
.avatar-frame:hover{transform:rotate(0) scale(1.02);}
|
||||
|
||||
/* back-to-top / footer brand */
|
||||
.site-foot .foot-brand{transition:letter-spacing .3s;}
|
||||
.site-foot .foot-brand:hover{letter-spacing:.05em;color:var(--white);}
|
||||
|
||||
/* marquee spans: brighten on hover */
|
||||
.marquee span{transition:opacity .2s;}
|
||||
.marquee:hover span{opacity:1;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============ CINEMATIC INTRO ============ -->
|
||||
<div class="intro" id="intro" aria-hidden="true">
|
||||
<div class="intro-tag" id="introTag">SICK AGENCY · <b>MILES WOLF ALLEN</b></div>
|
||||
<div class="intro-bar" id="introBar"><div class="fill" id="introFill"></div></div>
|
||||
</div>
|
||||
<div class="intro-stage" id="introStage" aria-hidden="true">
|
||||
<div class="intro-word" id="introWord">MILES</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ SITE ============ -->
|
||||
<div class="site" id="site">
|
||||
|
||||
<nav class="nav">
|
||||
<a class="nav-brand" href="#top"><span class="swatch"></span>MILES</a>
|
||||
<div class="nav-links">
|
||||
<a href="#about">About</a>
|
||||
<a href="#skills">Skills</a>
|
||||
<a href="#projects">Projects</a>
|
||||
<a href="#blog">Devlog</a>
|
||||
<a class="cta" href="#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- ============ HERO — YELLOW ============ -->
|
||||
<section class="band yellow hero">
|
||||
<span class="sticker" style="background:var(--cobalt);color:var(--white);"><b>USUALLY</b>ONLINE <small>@mileswolfallen2</small></span>
|
||||
<div class="hero-title">
|
||||
<span class="kick">Hi, I'm —</span>
|
||||
<span class="loud">MILES<br>WOLF<br>ALLEN</span>
|
||||
</div>
|
||||
<p class="hero-sub">I make games, tools, and websites. I'm still in school and building a full Electron replacement — <em>send help</em>. I also work at Diskette Labs.</p>
|
||||
<div class="hero-status"><i></i> usually online</div>
|
||||
<div class="hero-actions">
|
||||
<a class="btn primary" href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
|
||||
<a class="btn" href="https://replit.com/@MilesAllen1" target="_blank">Replit</a>
|
||||
<a class="btn" href="https://codepen.io/mileswolfallen2-the-typescripter" target="_blank">CodePen</a>
|
||||
<a class="btn" href="https://diskettelabs.com/" target="_blank">Diskette Labs</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ HOOK — ORANGE ============ -->
|
||||
<section class="band orange hook">
|
||||
<p class="hook-text">I believe pancakes are better than waffles, cereal is soup, and software should <em>feel good to use.</em></p>
|
||||
</section>
|
||||
|
||||
<!-- ============ MARQUEE — YELLOW ============ -->
|
||||
<div class="band yellow marquee">
|
||||
<div class="track">
|
||||
<span>* building emulators *</span><span>* send help *</span><span>* 160 repos and counting *</span><span>* still debugging *</span><span>* geometry dash addict *</span><span>* usually online *</span><span>* ↑ ↑ ↓ ↓ ← → ← → B A *</span>
|
||||
<span>* building emulators *</span><span>* send help *</span><span>* 160 repos and counting *</span><span>* still debugging *</span><span>* geometry dash addict *</span><span>* usually online *</span><span>* ↑ ↑ ↓ ↓ ← → ← → B A *</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ STATS — ORANGE ============ -->
|
||||
<section class="band orange stats">
|
||||
<div class="stat"><div class="stat-num">160</div><div class="stat-label">Repos online</div></div>
|
||||
<div class="stat"><div class="stat-num">91</div><div class="stat-label">Stars earned</div></div>
|
||||
<div class="stat"><div class="stat-num">13</div><div class="stat-label">Watching me</div></div>
|
||||
<div class="stat"><div class="stat-num">37</div><div class="stat-label">I watch</div></div>
|
||||
</section>
|
||||
|
||||
<!-- ============ ABOUT — YELLOW ============ -->
|
||||
<section class="band yellow section" id="about">
|
||||
<span class="sec-label">Level 01 // Identity</span>
|
||||
<h2 class="sec-title">Who's Miles?</h2>
|
||||
<p class="sec-sub">the human behind the commits</p>
|
||||
<div class="about-grid">
|
||||
<div class="avatar-block" style="text-align:center;">
|
||||
<div class="avatar-frame"><img src="https://avatars.githubusercontent.com/u/115095719?v=4" alt="Miles Wolf Allen" loading="lazy" width="240" height="240"></div>
|
||||
<div class="avatar-name">Miles Wolf Allen</div>
|
||||
<div class="avatar-tag">@mileswolfallen2</div>
|
||||
</div>
|
||||
<div class="about-body">
|
||||
<p>I build things on the internet. Some of them are games, some are tools, and some are just because I thought it would be fun. 160 repos, 91 stars, and a growing list of things I'm figuring out as I go.</p>
|
||||
<div class="badge-row">
|
||||
<span class="badge">Pull Shark x2</span>
|
||||
<span class="badge">YOLO</span>
|
||||
<span class="badge">Quickdraw</span>
|
||||
</div>
|
||||
<div class="about-cta">
|
||||
<a class="btn primary" href="https://diskettelabs.com/" target="_blank">Member of Diskette Labs</a>
|
||||
<a class="btn" href="../about-me.html">More About Me</a>
|
||||
</div>
|
||||
<div class="hair-rule"></div>
|
||||
<div class="page-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html" style="color:var(--cobalt);border-color:var(--cobalt);">Internet History</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ SKILLS — COBALT ============ -->
|
||||
<section class="band cobalt section" id="skills">
|
||||
<span class="sec-label flat">Level 02 // Loadout</span>
|
||||
<h2 class="sec-title or-white">My Loadout</h2>
|
||||
<p class="sec-sub or-white">what I actually know how to use</p>
|
||||
|
||||
<div class="skill-group-label">Ready to use</div>
|
||||
<div class="skill-grid">
|
||||
<div class="skill"><span>JavaScript</span></div>
|
||||
<div class="skill alt1"><span>Node.js</span></div>
|
||||
<div class="skill alt2"><span>HTML5</span></div>
|
||||
<div class="skill alt3"><span>CSS3</span></div>
|
||||
<div class="skill"><span>Python</span></div>
|
||||
<div class="skill alt1"><span>Git</span></div>
|
||||
<div class="skill alt2"><span>Blender</span></div>
|
||||
<div class="skill alt3"><span>Unity</span></div>
|
||||
<div class="skill"><span>Unreal</span></div>
|
||||
<div class="skill alt1"><span>Cloudflare</span></div>
|
||||
<div class="skill alt2"><span>Linux</span></div>
|
||||
<div class="skill alt3"><span>Netlify</span></div>
|
||||
<div class="skill"><span>Bash</span></div>
|
||||
<div class="skill alt1"><span>GitHub</span></div>
|
||||
<div class="skill alt2"><span>Electron</span></div>
|
||||
<div class="skill alt3"><span>Gelectron</span></div>
|
||||
</div>
|
||||
|
||||
<div class="skill-group-label">Currently unlocking</div>
|
||||
<div class="skill-grid">
|
||||
<div class="skill learn"><span>PHP</span></div>
|
||||
<div class="skill learn alt1"><span>.NET</span></div>
|
||||
<div class="skill learn alt2"><span>Nix</span></div>
|
||||
<div class="skill learn alt3"><span>Yarn</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ PROJECTS — YELLOW ============ -->
|
||||
<section class="band yellow section" id="projects">
|
||||
<span class="sec-label">Level 03 // Builds</span>
|
||||
<h2 class="sec-title">The Projects</h2>
|
||||
<p class="sec-sub">stuff I've made (and some I forked)</p>
|
||||
|
||||
<div class="project-grid">
|
||||
<a class="proj-card" href="../projects/omniemu.html">
|
||||
<div class="proj-top"><span class="proj-tag">Active</span><span class="proj-meta">TypeScript / v0.3.2</span></div>
|
||||
<div class="proj-title">OmniEmu 2.0</div>
|
||||
<div class="proj-desc">All your emulators in one place. Install 20+ emulators, scan your ROMs, and launch games without opening 15 different apps.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/gelectron.html">
|
||||
<div class="proj-top"><span class="proj-tag">Early</span><span class="proj-meta">Rust + Node.js</span></div>
|
||||
<div class="proj-title">Gelectron</div>
|
||||
<div class="proj-desc">Drop-in replacement for Electron using native web views (WKWebView / WebView2 / WebKitGTK) instead of bundling Chromium.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="https://vanilla.milesallen.site/" target="_blank">
|
||||
<div class="proj-top"><span class="proj-tag">Unreleased</span><span class="proj-meta">Vanilla JS</span></div>
|
||||
<div class="proj-title">Vanilla</div>
|
||||
<div class="proj-desc">A clean, minimal web experiment. Pure vanilla JS, no frameworks, no cruft.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/fedl.html">
|
||||
<div class="proj-top"><span class="proj-tag on-orange">Live</span><span class="proj-meta">2196 commits</span></div>
|
||||
<div class="proj-title">FEDL</div>
|
||||
<div class="proj-desc">The demon list for Geometry Dash. Ranked levels, run submissions, and a community of people way better at the game than me.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/command-move.html">
|
||||
<div class="proj-top"><span class="proj-tag">Swift</span><span class="proj-meta">macOS / MIT</span></div>
|
||||
<div class="proj-title">CommandMove</div>
|
||||
<div class="proj-desc">True cut-and-paste for Finder on macOS. Select files, Cmd+X, navigate, Cmd+V. What Apple forgot to build.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/noto.html">
|
||||
<div class="proj-top"><span class="proj-tag">Active</span><span class="proj-meta">Flutter / AI</span></div>
|
||||
<div class="proj-title">Noto</div>
|
||||
<div class="proj-desc">Note-taking app with AI built in. Built with Flutter, powered by Cortex AI, and actually good at organizing my chaos.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/nova.html">
|
||||
<div class="proj-top"><span class="proj-tag">Python</span><span class="proj-meta">Desktop / Web / CLI</span></div>
|
||||
<div class="proj-title">Nova AI</div>
|
||||
<div class="proj-desc">AI assistant that runs on your machine. Desktop GUI, web interface, and CLI. No cloud, no subscriptions, just vibes.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/music-app.html">
|
||||
<div class="proj-top"><span class="proj-tag">JS</span><span class="proj-meta">Apache-2.0</span></div>
|
||||
<div class="proj-title">Music App</div>
|
||||
<div class="proj-desc">Music player that doesn't interrupt you with ads. Inspired by YouTube Music but without the annoying parts.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/rhythm-game.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">MPL-2.0</span></div>
|
||||
<div class="proj-title">Rhythm Game</div>
|
||||
<div class="proj-desc">Hit keys to the beat. Song selection, real-time notes, and keyboard controls. My fingers hurt.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/geode-mod.html">
|
||||
<div class="proj-top"><span class="proj-tag">WIP</span><span class="proj-meta">MIT</span></div>
|
||||
<div class="proj-title">FEDL Geode Mod</div>
|
||||
<div class="proj-desc">Geometry Dash mod that shows the demon list in-game. Roulette mode, banned mod detection, and more.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/decompilations.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">MIT</span></div>
|
||||
<div class="proj-title">Decompilations</div>
|
||||
<div class="proj-desc">Native PC ports and decompilations of games. Progress tracking for projects that probably shouldn't exist.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/issue-tracker.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">Node.js</span></div>
|
||||
<div class="proj-title">Issue Tracker</div>
|
||||
<div class="proj-desc">Built my own issue tracker because why not. Node.js backend, no frameworks, just raw determination.</div>
|
||||
</a>
|
||||
<a class="proj-card" href="../projects/moon-gaming.html">
|
||||
<div class="proj-top"><span class="proj-tag">HTML</span><span class="proj-meta">1,542 commits</span></div>
|
||||
<div class="proj-title">Moon Gaming</div>
|
||||
<div class="proj-desc">Game hub with 1,500+ commits. Browse, play, and explore a growing library of browser games I made at 2am.</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ HIGHLIGHTS — COBALT ============ -->
|
||||
<section class="band cobalt section" id="highlights">
|
||||
<span class="sec-label flat">Level 04 // Scoreboard</span>
|
||||
<h2 class="sec-title or-white">Scoreboard</h2>
|
||||
<p class="sec-sub or-white">what my projects have achieved</p>
|
||||
|
||||
<div class="hl-block">
|
||||
<h3>FEDL</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">3,839</div><div class="hl-label">Verified runs</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">341</div><div class="hl-label">Ranked levels</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">250+</div><div class="hl-label">Active players</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">3,800+</div><div class="hl-label">YouTube videos</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hl-block">
|
||||
<h3>OmniEmu 2.0</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">20+</div><div class="hl-label">Emulators</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">3</div><div class="hl-label">Platforms</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">v0.3.2</div><div class="hl-label">Latest</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">TS</div><div class="hl-label">Built with</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hl-block">
|
||||
<h3>Other projects</h3>
|
||||
<div class="hl-row">
|
||||
<div class="hl-cell"><div class="hl-num">3</div><div class="hl-label">Nova interfaces</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">5</div><div class="hl-label">Noto AI features</div></div>
|
||||
<div class="hl-cell"><div class="hl-num">1,542</div><div class="hl-label">Moon Gaming commits</div></div>
|
||||
<div class="hl-cell"><div class="hl-num" style="font-size:clamp(34px,5vw,58px);">MIT</div><div class="hl-label">Licensed</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ GITHUB STATS — YELLOW ============ -->
|
||||
<section class="band yellow section" id="stats">
|
||||
<span class="sec-label">Level 05 // GitHub</span>
|
||||
<h2 class="sec-title">Stats</h2>
|
||||
<p class="sec-sub">the raw numbers, straight from the API</p>
|
||||
<div class="gh-stats">
|
||||
<img src="https://github-readme-stats.josselinonduty.fr/api?username=mileswolfallen2&show_icons=true&theme=noctis_minimus" alt="GitHub stats" loading="lazy">
|
||||
<img src="https://github-readme-stats.josselinonduty.fr/api/top-langs/?username=mileswolfallen2&layout=compact&theme=noctis_minimus" alt="GitHub languages" loading="lazy">
|
||||
<img src="https://streak-stats.demolab.com/?user=mileswolfallen2&theme=noctis_minimus" alt="GitHub streak" loading="lazy">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ BLOG — COBALT ============ -->
|
||||
<section class="band cobalt section" id="blog">
|
||||
<span class="sec-label flat">Level 06 // Devlog</span>
|
||||
<h2 class="sec-title or-white">Devlog</h2>
|
||||
<p class="sec-sub or-white">things I've learned, broken, and fixed</p>
|
||||
<div class="blog-grid">
|
||||
<a class="blog-card" href="diskette-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">Focusing on Diskette Labs</div>
|
||||
<div class="blog-excerpt">I'm co-founder of Diskette Labs, a product design collective started by Owen. It's where my focus is now — but I'm still open to collaborations.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Design</span><span class="blog-tag">Collective</span></div>
|
||||
</a>
|
||||
<a class="blog-card" href="gelectron-devlog.html">
|
||||
<div class="blog-date">2026</div>
|
||||
<div class="blog-title">Why I Built Gelectron</div>
|
||||
<div class="blog-excerpt">Electron bundles Chromium with every app. 150-300MB per app, hundreds of MBs of RAM. Native web views fix that.</div>
|
||||
<div class="blog-tags"><span class="blog-tag">Rust</span><span class="blog-tag">Native</span></div>
|
||||
</a>
|
||||
<a class="blog-card" href="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" href="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" href="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" href="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>
|
||||
|
||||
<!-- ============ CONTACT — ORANGE ============ -->
|
||||
<section class="band orange section" id="contact">
|
||||
<span class="sec-label flat">Level 07 // Reach out</span>
|
||||
<h2 class="sec-title or-white">Get In Touch</h2>
|
||||
<p class="sec-sub or-white">unless you're a bot, then please go away</p>
|
||||
<div class="contact-card">
|
||||
<span class="sticker" style="background:var(--yellow);color:var(--cobalt);"><b>TEXT<br>US</b><small>or email</small></span>
|
||||
<div class="contact-list">
|
||||
<a class="contact-row" href="mailto:[email protected]"><span class="k">email</span><span>[email protected]</span></a>
|
||||
<a class="contact-row" href="https://github.com/mileswolfallen2" target="_blank"><span class="k">github</span><span>mileswolfallen2</span></a>
|
||||
<a class="contact-row" href="https://replit.com/@MilesAllen1" target="_blank"><span class="k">replit</span><span>@MilesAllen1</span></a>
|
||||
<a class="contact-row" href="https://codepen.io/mileswolfallen2-the-typescripter" target="_blank"><span class="k">codepen</span><span>mileswolfallen2</span></a>
|
||||
<a class="contact-row" href="https://me.fedl.site" target="_blank"><span class="k">website</span><span>me.fedl.site</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ FOOTER — BLACK ============ -->
|
||||
<footer class="site-foot">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
<a href="https://server.fedl.site/git/miles" target="_blank" rel="noopener">Backup</a>
|
||||
<a href="../redesign/hub.html">View Redesigns</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code<br>Remake in the SICK AGENCY broadside style, with a loud MILES slash intro.</p>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
// Skip intro for reduced-motion / small screens to keep it snappy
|
||||
var REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var SMALL = window.innerWidth < 560;
|
||||
|
||||
var intro = document.getElementById('intro');
|
||||
var stage = document.getElementById('introStage');
|
||||
var introWord = document.getElementById('introWord');
|
||||
var tag = document.getElementById('introTag');
|
||||
var fill = document.getElementById('introFill');
|
||||
var site = document.getElementById('site');
|
||||
var heroLoud = document.querySelector('.hero-title .loud');
|
||||
|
||||
if (REDUCED || SMALL || !window.gsap) { finish(); return; }
|
||||
|
||||
var pct = 0;
|
||||
var started = false;
|
||||
var progressTimer = setInterval(function(){
|
||||
pct += Math.random() * 24;
|
||||
if (pct >= 100) { pct = 100; clearInterval(progressTimer); }
|
||||
if (fill) fill.style.width = pct + '%';
|
||||
}, 200);
|
||||
|
||||
function finish(){
|
||||
if (intro && intro.parentNode) intro.remove();
|
||||
if (stage && stage.parentNode) stage.remove();
|
||||
site.style.opacity = '1';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// Hide everything (except hero, which reveals during the merge), readied
|
||||
// behind the black intro so there's no flash.
|
||||
function preparePage(){
|
||||
gsap.set(site, { opacity: 1 });
|
||||
gsap.set('.site .band:not(.hero), .site nav, .site footer',
|
||||
{ opacity: 0, y: 60, scaleY: 0.92, transformOrigin: '50% 100%' });
|
||||
gsap.set('.proj-card,.skill,.stat,.hl-cell,.blog-card,.sec-label,.sec-title,.sec-sub,.badge,.avatar-frame,.sticker,.contact-row,.gh-stats img',
|
||||
{ opacity: 0, scale: 0.7, rotation: 4 });
|
||||
gsap.set('.hero .kick', { opacity: 0, x: -28 });
|
||||
gsap.set('.hero-sub, .hero-status, .hero-actions', { opacity: 0, y: 18 });
|
||||
gsap.set('.hero .loud', { opacity: 0, scale: 1.15, rotationX: 10, transformOrigin: '0% 50%' });
|
||||
gsap.set('.hero .sticker', { opacity: 0, scale: 0.5, rotation: -30 });
|
||||
}
|
||||
|
||||
// MILES flies to its spot on the page hero while the hero title pops in.
|
||||
function revealHero(){
|
||||
gsap.to('.hero .kick', { opacity: 1, x: 0, duration: 0.5, ease: 'power3.out', onComplete: function(){ gsap.set('.hero .kick', { clearProps: 'transform' }); } });
|
||||
gsap.to('.hero-sub, .hero-status, .hero-actions', { opacity: 1, y: 0, duration: 0.5, ease: 'power3.out', stagger: 0.08, onComplete: function(){ gsap.set('.hero-sub, .hero-status, .hero-actions', { clearProps: 'transform' }); } });
|
||||
if (heroLoud) {
|
||||
var wRect = introWord.getBoundingClientRect();
|
||||
var hRect = heroLoud.getBoundingClientRect();
|
||||
var scale = hRect.height / wRect.height;
|
||||
gsap.to('.hero .loud', { opacity: 1, scale: 1, rotationX: 0, duration: 0.7, ease: 'back.out(1.4)', onComplete: function(){ gsap.set('.hero .loud', { clearProps: 'transform' }); } });
|
||||
gsap.to(introWord, {
|
||||
x: (hRect.left + hRect.width/2) - (wRect.left + wRect.width/2),
|
||||
y: (hRect.top + hRect.height/2) - (wRect.top + wRect.height/2),
|
||||
scale: scale, opacity: 0, duration: 0.75, ease: 'power3.inOut'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reveal a section and its cards with a playful staggered pop.
|
||||
function revealBlock(el){
|
||||
gsap.to(el, { opacity: 1, y: 0, scaleY: 1, duration: 0.8, ease: 'back.out(1.4)', transformOrigin: '50% 100%',
|
||||
onComplete: function(){ gsap.set(el, { clearProps: 'transform' }); }
|
||||
});
|
||||
var things = el.querySelectorAll('.proj-card,.skill,.stat,.hl-cell,.blog-card,.sec-label,.sec-title,.sec-sub,.badge,.avatar-frame,.sticker,.contact-row,.gh-stats img');
|
||||
if (things.length) {
|
||||
gsap.to(things, { opacity: 1, scale: 1, rotation: 0, duration: 0.7, ease: 'elastic.out(1.1, 0.5)', stagger: 0.06, delay: 0.1,
|
||||
onComplete: function(){ gsap.set(things, { clearProps: 'transform' }); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Pop sections in as they scroll into view.
|
||||
function setupScrollReveal(){
|
||||
var BLOCKS = '.site .band:not(.hero), .site footer';
|
||||
if (typeof IntersectionObserver === 'undefined') {
|
||||
gsap.to(BLOCKS, { opacity: 1, y: 0, scaleY: 1 });
|
||||
gsap.to('.proj-card,.skill,.stat,.hl-cell,.blog-card,.sec-label,.sec-title,.sec-sub,.badge,.avatar-frame,.sticker,.contact-row,.gh-stats img', { opacity: 1, scale: 1, rotation: 0 });
|
||||
return;
|
||||
}
|
||||
var io = new IntersectionObserver(function(entries){
|
||||
entries.forEach(function(entry){
|
||||
if (!entry.isIntersecting) return;
|
||||
var el = entry.target;
|
||||
io.unobserve(el);
|
||||
revealBlock(el);
|
||||
});
|
||||
}, { rootMargin: '0px 0px -12% 0px', threshold: 0.05 });
|
||||
document.querySelectorAll(BLOCKS).forEach(function(el){ io.observe(el); });
|
||||
}
|
||||
|
||||
function startSequence() {
|
||||
if (started) return;
|
||||
started = true;
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
preparePage();
|
||||
|
||||
var tl = gsap.timeline({ onComplete: finish });
|
||||
|
||||
// ---- 1) black hold, tag + bar breathe in ----
|
||||
tl.set(intro, { backgroundColor: '#000' })
|
||||
.fromTo(tag, { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.45, ease: 'power2.out' }, 0.1)
|
||||
.fromTo(fill, { width: '0%' }, { width: '100%', duration: 1.3, ease: 'power2.inOut' }, 0.1);
|
||||
|
||||
// ---- 2) MILES SLIDES IN from the left, big and springy ----
|
||||
tl.fromTo(introWord,
|
||||
{ x: '-120vw', scale: 3.2, opacity: 0, skewX: -14 },
|
||||
{ x: 0, scale: 1, opacity: 1, skewX: 0, duration: 1.15, ease: 'back.out(1.3)' }, 0.5);
|
||||
|
||||
// hold the beat, then
|
||||
tl.to(tag, { opacity: 0, duration: 0.25 }, 2.0)
|
||||
.to(fill, { width: '100%', duration: 0.2 }, 2.0)
|
||||
|
||||
// ---- 3) MILES flies to its spot in the hero ----
|
||||
.add(function(){
|
||||
revealHero();
|
||||
gsap.fromTo('.nav', { opacity: 0, y: -40 }, { opacity: 1, y: 0, duration: 0.6, ease: 'back.out(1.4)', onComplete: function(){ gsap.set('.nav', { clearProps: 'transform' }); } });
|
||||
gsap.fromTo('.hero .sticker', { opacity: 0, scale: 0.5, rotation: -30 }, { opacity: 1, scale: 1, rotation: 0, duration: 0.7, ease: 'back.out(1.6)', onComplete: function(){ gsap.set('.hero .sticker', { clearProps: 'transform' }); } });
|
||||
}, 2.25)
|
||||
|
||||
// ---- 4) open the curtain ----
|
||||
.to(intro, { opacity: 0, duration: 0.4, ease: 'power2.inOut' }, 2.5)
|
||||
.to(stage, { opacity: 0, duration: 0.25 }, 2.4)
|
||||
.add(setupScrollReveal, 2.6);
|
||||
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
window.addEventListener('load', function(){ setTimeout(startSequence, 400); });
|
||||
setTimeout(startSequence, 3000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!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/remake/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:type" content="article">
|
||||
<meta property="article:published_time" content="2026-01-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "Why I Built OmniEmu | Miles Wolf Allen",
|
||||
"description": "The story behind OmniEmu 2.0 — why I built a cross-platform emulator manager.",
|
||||
"author": { "@type": "Person", "name": "Miles Wolf Allen", "url": "https://milesallen.site/" },
|
||||
"datePublished": "2026-01-15",
|
||||
"url": "https://milesallen.site/remake/omniemu-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/remake/omniemu-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<meta name="theme-color" content="#f5b700">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Josefin+Sans:wght@300;400&family=Spectral:ital,wght@0,400;0,600;1,400&family=Anton&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="broadside.css">
|
||||
</head>
|
||||
<body class="yellow">
|
||||
|
||||
<nav class="article-nav">
|
||||
<a class="brand" href="index.html"><span class="swatch"></span>MILES</a>
|
||||
<div class="links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="index.html#projects">Projects</a>
|
||||
<a href="index.html#blog">Devlog</a>
|
||||
<a class="cta" href="index.html#contact">Say hi</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="article-wrap article-hero">
|
||||
<div class="article-eyebrow">// DEVLOG 001 · 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>
|
||||
</header>
|
||||
|
||||
<div class="hairline"></div>
|
||||
|
||||
<article class="article-wrap 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>
|
||||
|
||||
<blockquote>The best tool is the one you build because nothing else does what you need.</blockquote>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="back-row">
|
||||
<a class="btn primary" href="index.html#blog">↩ Back to Devlog</a>
|
||||
<a class="btn" href="index.html">Home</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<footer class="article-footer">
|
||||
<div class="foot-brand">MILES</div>
|
||||
<div class="foot-links">
|
||||
<a href="../uses.html">Uses</a>
|
||||
<a href="../now.html">Now</a>
|
||||
<a href="../colophon.html">Colophon</a>
|
||||
<a href="../experiments.html">Internet History</a>
|
||||
</div>
|
||||
<p class="foot-note">Built by Miles · 2026 · Powered by too much coffee and the Konami code</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"profile": {
|
||||
"name": "miles wolf allen",
|
||||
"handle": "@mileswa · software engineer",
|
||||
"meta": "he/him · MN, USA",
|
||||
"bio": "Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust.",
|
||||
"avatar": "https://avatars.githubusercontent.com/u/115095719?v=4",
|
||||
"website": "https://milesallen.site"
|
||||
},
|
||||
"timezone": {
|
||||
"id": "America/Chicago",
|
||||
"label": "Central Time (MN, USA)"
|
||||
},
|
||||
"now": [
|
||||
["Learning", "Rust"],
|
||||
["Building", "Vanilla music app"],
|
||||
["Playing", "Geometry Dash"]
|
||||
],
|
||||
"posts": [
|
||||
{
|
||||
"title": "Shipped Vanilla v2.0",
|
||||
"body": "Rewrote the audio engine and added a custom queue. Huge QoL update.",
|
||||
"date": "2026-08-24",
|
||||
"link": "https://github.com/mileswolfallen2"
|
||||
},
|
||||
{
|
||||
"title": "Porting a classic to desktop",
|
||||
"body": "Working on a native PC port of a retro game. Almost playable.",
|
||||
"date": "2026-08-15",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"title": "Learning Rust",
|
||||
"body": "Picked up Rust for a systems project. Rotating on lifetimes right now.",
|
||||
"date": "2026-07-30",
|
||||
"link": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+831
@@ -0,0 +1,831 @@
|
||||
{
|
||||
"name": "miles-share-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "miles-share-server",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"express": "^4.22.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.6",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
|
||||
"integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "~1.2.0",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.15.1",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||
"integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "~1.20.5",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "~0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "~6.15.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~2.0.2",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.3",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
||||
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "~0.19.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"side-channel-list": "^1.0.1",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "miles-share-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Node server for miles' share page — editable now status, latest posts, timezone, dark mode.",
|
||||
"main": "server.js",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.22.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,127 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// NOTE: set ADMIN_TOKEN to a secret in your environment before running in production.
|
||||
// Example: ADMIN_TOKEN="s3cret" node server.js
|
||||
const ADMIN_TOKEN = process.env.ADMIN_TOKEN || 'change-me';
|
||||
|
||||
const DATA_DIR = path.join(__dirname, 'data');
|
||||
const DATA_FILE = path.join(DATA_DIR, 'data.json');
|
||||
|
||||
let DB = loadData();
|
||||
|
||||
function loadData() {
|
||||
try {
|
||||
const raw = fs.readFileSync(DATA_FILE, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
const defaults = {
|
||||
profile: {},
|
||||
timezone: { id: 'America/Chicago', label: 'Central Time (MN, USA)' },
|
||||
now: [],
|
||||
posts: []
|
||||
};
|
||||
saveData(defaults);
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
|
||||
function saveData(db) {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(db, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
function mustAuth(req) {
|
||||
const header = (req.headers.authorization || '').replace(/^Bearer\s+/i, '');
|
||||
return header && safeEqual(header, ADMIN_TOKEN);
|
||||
}
|
||||
|
||||
function safeEqual(a, b) {
|
||||
const ha = crypto.createHash('sha256').update(String(a)).digest();
|
||||
const hb = crypto.createHash('sha256').update(String(b)).digest();
|
||||
return ha.length === hb.length && crypto.timingSafeEqual(ha, hb);
|
||||
}
|
||||
|
||||
app.use(express.json({ limit: '256kb' }));
|
||||
app.use('/static', express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.get('/api/data', (req, res) => {
|
||||
res.json(DB);
|
||||
});
|
||||
|
||||
app.post('/api/data', (req, res) => {
|
||||
if (!mustAuth(req)) return res.status(401).json({ error: 'unauthorized' });
|
||||
const body = req.body || {};
|
||||
const next = {};
|
||||
|
||||
if (body.profile) next.profile = sanitizeProfile(body.profile);
|
||||
if (body.timezone) next.timezone = sanitizeTimezone(body.timezone);
|
||||
if (body.now !== undefined) next.now = sanitizeNow(body.now);
|
||||
if (body.posts !== undefined) next.posts = sanitizePosts(body.posts);
|
||||
|
||||
if (Object.keys(next).length === 0) return res.status(400).json({ error: 'nothing to update' });
|
||||
|
||||
DB = { ...DB, ...next };
|
||||
saveData(DB);
|
||||
res.json({ ok: true, data: DB });
|
||||
});
|
||||
|
||||
function sanitizeProfile(p) {
|
||||
return {
|
||||
name: str(p.name, ''),
|
||||
handle: str(p.handle, ''),
|
||||
meta: str(p.meta, ''),
|
||||
bio: str(p.bio, ''),
|
||||
avatar: str(p.avatar, ''),
|
||||
website: str(p.website, '')
|
||||
};
|
||||
}
|
||||
function sanitizeTimezone(t) {
|
||||
return { id: str(t.id, 'America/Chicago'), label: str(t.label, '') };
|
||||
}
|
||||
function sanitizeNow(n) {
|
||||
if (!Array.isArray(n)) return [];
|
||||
return n
|
||||
.filter((x) => Array.isArray(x) && x.length >= 2)
|
||||
.map((x) => [str(x[0], ''),
|
||||
str(x[1], '')])
|
||||
.filter((x) => x[0] || x[1]);
|
||||
}
|
||||
function sanitizePosts(ps) {
|
||||
if (!Array.isArray(ps)) return [];
|
||||
return ps
|
||||
.filter((x) => x && typeof x === 'object')
|
||||
.map((x) => ({
|
||||
title: str(x.title, ''),
|
||||
body: str(x.body, ''),
|
||||
date: str(x.date, ''),
|
||||
link: str(x.link, '')
|
||||
}))
|
||||
.filter((x) => x.title);
|
||||
}
|
||||
function str(v, dflt) {
|
||||
return typeof v === 'string' ? v.slice(0, 2000) : dflt;
|
||||
}
|
||||
|
||||
app.get('/admin', (req, res) => {
|
||||
const html = fs.readFileSync(path.join(__dirname, 'views', 'admin.html'), 'utf8');
|
||||
res.set('Content-Type', 'text/html');
|
||||
res.send(html.replace('{{DATA}}', JSON.stringify(DB)));
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const html = fs.readFileSync(path.join(__dirname, 'views', 'share.html'), 'utf8');
|
||||
res.set('Content-Type', 'text/html');
|
||||
res.send(html.replace('{{DATA}}', JSON.stringify(DB)));
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`share server running on http://localhost:${PORT}`);
|
||||
console.log(`admin page: http://localhost:${PORT}/admin`);
|
||||
console.log(`api: http://localhost:${PORT}/api/data`);
|
||||
});
|
||||
@@ -0,0 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit — share page</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>window.__DATA__ = {{DATA}};</script>
|
||||
<style>
|
||||
:root{--page:#0f1116;--card:#1a1d24;--card2:#22262f;--border:#2a2f3a;--text:#e8eaee;--muted:#9aa3b2;--accent:#1d9bf0;--danger:#ef4444;--ok:#22c55e;}
|
||||
*{box-sizing:border-box;}
|
||||
body{margin:0;padding:40px 16px 80px;background:var(--page);color:var(--text);font-family:'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;line-height:1.5;}
|
||||
.wrap{max-width:680px;margin:0 auto;}
|
||||
h1{font-size:24px;margin:0 0 4px;}
|
||||
.sub{color:var(--muted);font-size:13px;margin-bottom:28px;}
|
||||
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:20px;margin-bottom:20px;}
|
||||
.card h2{font-size:14px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:0 0 16px;}
|
||||
label{display:block;font-size:12px;font-weight:600;color:var(--muted);margin:12px 0 4px;}
|
||||
input[type=text],textarea,select{width:100%;padding:9px 11px;background:var(--card2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-family:inherit;font-size:14px;}
|
||||
textarea{resize:vertical;min-height:64px;}
|
||||
.hint{font-size:11px;color:var(--muted);margin:4px 0 0;}
|
||||
.row-item{display:grid;grid-template-columns:1fr 1fr auto;gap:8px;margin-bottom:8px;align-items:center;}
|
||||
.row-item input{width:100%;}
|
||||
.del{width:34px;height:34px;border-radius:8px;border:1px solid var(--border);background:var(--card2);color:var(--danger);cursor:pointer;font-size:16px;line-height:1;}
|
||||
.add{width:100%;padding:9px;border-radius:8px;border:1px dashed var(--border);background:transparent;color:var(--accent);cursor:pointer;font-size:13px;font-weight:600;}
|
||||
.post-item{border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:10px;background:var(--card2);}
|
||||
.post-item .p-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;}
|
||||
.post-item .p-head b{font-size:13px;color:var(--muted);}
|
||||
.bar{display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;}
|
||||
.bar .token{display:flex;gap:8px;flex:1;min-width:200px;}
|
||||
.bar input{flex:1;}
|
||||
.btn{font-family:inherit;font-size:13px;font-weight:700;padding:10px 18px;border-radius:10px;cursor:pointer;border:1px solid var(--border);background:var(--card2);color:var(--text);}
|
||||
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;}
|
||||
.status{font-size:13px;font-weight:600;}
|
||||
.status.ok{color:var(--ok);} .status.err{color:var(--danger);}
|
||||
.top-link{color:var(--accent);font-size:13px;text-decoration:none;margin-bottom:20px;display:inline-block;}
|
||||
.top-link:hover{text-decoration:underline;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="top-link" href="/">← View page</a>
|
||||
<h1>Edit share page</h1>
|
||||
<p class="sub">Changes save to the server's data file. Keep this link private.</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Profile</h2>
|
||||
<label>Name</label><input type="text" id="f-name">
|
||||
<label>Handle</label><input type="text" id="f-handle">
|
||||
<label>Meta (pronouns · location)</label><input type="text" id="f-meta">
|
||||
<label>Bio</label><textarea id="f-bio"></textarea>
|
||||
<label>Avatar URL</label><input type="text" id="f-avatar">
|
||||
<label>Website</label><input type="text" id="f-website">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Now (current status)</h2>
|
||||
<div id="now-list"></div>
|
||||
<button class="add" id="add-now" type="button">+ Add a "now" line</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Timezone</h2>
|
||||
<label>IANA timezone id</label><input type="text" id="f-tzid">
|
||||
<p class="hint">e.g. America/Chicago, America/Los_Angeles, Europe/London</p>
|
||||
<label>Label shown</label><input type="text" id="f-tzlabel">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Latest posts</h2>
|
||||
<div id="post-list"></div>
|
||||
<button class="add" id="add-post" type="button">+ Add a post</button>
|
||||
</div>
|
||||
|
||||
<div class="bar">
|
||||
<div class="token">
|
||||
<input type="password" id="f-token" placeholder="Admin token">
|
||||
<button class="btn primary" id="save" type="button">Save</button>
|
||||
</div>
|
||||
<span class="status" id="status"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var data = JSON.parse(JSON.stringify(window.__DATA__ || {}));
|
||||
data.profile = data.profile || {};
|
||||
data.timezone = data.timezone || {};
|
||||
data.now = data.now || [];
|
||||
data.posts = data.posts || [];
|
||||
|
||||
var nowList = document.getElementById('now-list');
|
||||
var postList = document.getElementById('post-list');
|
||||
|
||||
function oneNow(item) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'row-item';
|
||||
div.innerHTML = '<input type="text" placeholder="Label (Learning/Building/Playing)">' +
|
||||
'<input type="text" placeholder="Value">' +
|
||||
'<button class="del" type="button" aria-label="Remove">×</button>';
|
||||
div.children[0].value = item[0] || '';
|
||||
div.children[1].value = item[1] || '';
|
||||
div.children[2].addEventListener('click', function () { div.remove(); });
|
||||
return div;
|
||||
}
|
||||
|
||||
function renderNow() {
|
||||
nowList.innerHTML = '';
|
||||
(data.now.length ? data.now : [['', '']]).forEach(function (item) {
|
||||
nowList.appendChild(oneNow(item));
|
||||
});
|
||||
}
|
||||
document.getElementById('add-now').addEventListener('click', function () {
|
||||
nowList.appendChild(oneNow(['', '']));
|
||||
});
|
||||
|
||||
function onePost(p) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'post-item';
|
||||
div.innerHTML =
|
||||
'<div class="p-head"><b>Post</b><button class="del" type="button" aria-label="Remove post">×</button></div>' +
|
||||
'<label>Title</label><input type="text" class="p-title">' +
|
||||
'<label>Body</label><textarea class="p-body"></textarea>' +
|
||||
'<label>Date (YYYY-MM-DD)</label><input type="text" class="p-date" placeholder="2026-08-24">' +
|
||||
'<label>Link (optional)</label><input type="text" class="p-link" placeholder="https://...">';
|
||||
div.querySelector('.p-title').value = p.title || '';
|
||||
div.querySelector('.p-body').value = p.body || '';
|
||||
div.querySelector('.p-date').value = p.date || '';
|
||||
div.querySelector('.p-link').value = p.link || '';
|
||||
div.querySelector('.del').addEventListener('click', function () { div.remove(); });
|
||||
return div;
|
||||
}
|
||||
function renderPosts() {
|
||||
postList.innerHTML = '';
|
||||
(data.posts.length ? data.posts : [{}]).forEach(function (p) {
|
||||
postList.appendChild(onePost(p));
|
||||
});
|
||||
}
|
||||
document.getElementById('add-post').addEventListener('click', function () {
|
||||
postList.appendChild(onePost({}));
|
||||
});
|
||||
|
||||
function fill() {
|
||||
var p = data.profile;
|
||||
document.getElementById('f-name').value = p.name || '';
|
||||
document.getElementById('f-handle').value = p.handle || '';
|
||||
document.getElementById('f-meta').value = p.meta || '';
|
||||
document.getElementById('f-bio').value = p.bio || '';
|
||||
document.getElementById('f-avatar').value = p.avatar || '';
|
||||
document.getElementById('f-website').value = p.website || '';
|
||||
document.getElementById('f-tzid').value = data.timezone.id || 'America/Chicago';
|
||||
document.getElementById('f-tzlabel').value = data.timezone.label || '';
|
||||
renderNow();
|
||||
renderPosts();
|
||||
}
|
||||
|
||||
function collect() {
|
||||
var now = [];
|
||||
nowList.querySelectorAll('.row-item').forEach(function (r) {
|
||||
var k = r.children[0].value.trim();
|
||||
var v = r.children[1].value.trim();
|
||||
if (v) now.push([k, v]);
|
||||
});
|
||||
var posts = [];
|
||||
postList.querySelectorAll('.post-item').forEach(function (d) {
|
||||
var title = d.querySelector('.p-title').value.trim();
|
||||
if (title) posts.push({
|
||||
title: title,
|
||||
body: d.querySelector('.p-body').value.trim(),
|
||||
date: d.querySelector('.p-date').value.trim(),
|
||||
link: d.querySelector('.p-link').value.trim()
|
||||
});
|
||||
});
|
||||
return {
|
||||
profile: {
|
||||
name: document.getElementById('f-name').value.trim(),
|
||||
handle: document.getElementById('f-handle').value.trim(),
|
||||
meta: document.getElementById('f-meta').value.trim(),
|
||||
bio: document.getElementById('f-bio').value.trim(),
|
||||
avatar: document.getElementById('f-avatar').value.trim(),
|
||||
website: document.getElementById('f-website').value.trim()
|
||||
},
|
||||
timezone: {
|
||||
id: document.getElementById('f-tzid').value.trim(),
|
||||
label: document.getElementById('f-tzlabel').value.trim()
|
||||
},
|
||||
now: now,
|
||||
posts: posts
|
||||
};
|
||||
}
|
||||
|
||||
var statusEl = document.getElementById('status');
|
||||
document.getElementById('save').addEventListener('click', function () {
|
||||
var token = document.getElementById('f-token').value.trim();
|
||||
if (!token) { setStatus('Enter the admin token', 'err'); return; }
|
||||
var body = collect();
|
||||
setStatus('Saving…', '');
|
||||
fetch('/api/data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify(body)
|
||||
}).then(function (r) {
|
||||
if (r.status === 401) { setStatus('Wrong token', 'err'); throw new Error('unauthorized'); }
|
||||
if (!r.ok) { setStatus('Save failed (' + r.status + ')', 'err'); throw new Error('bad'); }
|
||||
return r.json();
|
||||
}).then(function (r) {
|
||||
data = r.data;
|
||||
setStatus('Saved ✓', 'ok');
|
||||
}).catch(function (e) {
|
||||
if (e && e.message === 'unauthorized') return;
|
||||
if (e && e.message !== 'bad') setStatus('Network error', 'err');
|
||||
});
|
||||
});
|
||||
|
||||
function setStatus(txt, cls) {
|
||||
statusEl.textContent = txt;
|
||||
statusEl.className = 'status ' + cls;
|
||||
}
|
||||
fill();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,320 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>miles wolf allen</title>
|
||||
<meta name="description" content="miles wolf allen — Software engineer. Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust.">
|
||||
<meta name="author" content="Miles Wolf Allen">
|
||||
<link rel="canonical" href="https://milesallen.site/share.html">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script>window.__DATA__ = {{DATA}};</script>
|
||||
<style>
|
||||
:root{
|
||||
--page:#111318;
|
||||
--card:#1a1d24;
|
||||
--card2:#20242d;
|
||||
--border:#2a2f3a;
|
||||
--text:#e8eaee;
|
||||
--muted:#9aa3b2;
|
||||
--accent:#1d9bf0;
|
||||
--chip:#2a3140;
|
||||
--danger:#ef4444;
|
||||
--ok:#22c55e;
|
||||
color-scheme:dark;
|
||||
}
|
||||
html.light{
|
||||
--page:#eef1f5;--card:#ffffff;--card2:#f7f9fb;--border:#e3e8ee;
|
||||
--text:#14161a;--muted:#647182;--accent:#1d9bf0;--chip:#e9eef5;
|
||||
--danger:#dc2626;--ok:#16a34a;
|
||||
color-scheme:light;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
body{
|
||||
background:var(--page);color:var(--text);
|
||||
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
-webkit-font-smoothing:antialiased;line-height:1.5;
|
||||
display:flex;flex-direction:column;align-items:center;
|
||||
min-height:100vh;padding:40px 20px 64px;overflow-x:hidden;
|
||||
}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
.card{width:100%;max-width:600px;background:var(--card);border:1px solid var(--border);border-radius:16px;overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.2),0 10px 30px rgba(0,0,0,.3);}
|
||||
.banner{height:160px;overflow:hidden;background:#14161b;}
|
||||
.banner img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;}
|
||||
.profile{padding:0 24px 12px;}
|
||||
.avatar{display:block;width:120px;height:120px;border-radius:50%;border:4px solid var(--card);background:#1f232b;box-shadow:0 2px 10px rgba(0,0,0,.4);object-fit:cover;margin-top:-56px;}
|
||||
.name{font-size:22px;font-weight:800;letter-spacing:-.01em;margin-top:14px;line-height:1.2;}
|
||||
.handle{font-size:14px;color:var(--muted);margin-top:3px;}
|
||||
.meta{font-size:13px;color:var(--muted);margin-top:8px;}
|
||||
.meta b{color:var(--text);font-weight:600;}
|
||||
.bio{font-size:15px;margin-top:14px;}
|
||||
.bio a{color:var(--accent);}
|
||||
.bio a:hover{text-decoration:underline;}
|
||||
|
||||
.socials{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:18px;}
|
||||
.soc{display:flex;align-items:center;justify-content:center;height:44px;border:1px solid var(--border);border-radius:12px;color:var(--muted);transition:all .15s;}
|
||||
.soc svg{width:22px;height:22px;fill:currentColor;}
|
||||
.soc:hover{color:var(--accent);border-color:var(--accent);background:var(--card2);transform:translateY(-2px);}
|
||||
|
||||
.section{border-top:1px solid var(--border);padding:20px 24px;}
|
||||
.sec-title{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);margin-bottom:12px;}
|
||||
|
||||
.now-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
|
||||
.now-item{display:flex;gap:10px;align-items:flex-start;background:var(--card2);border:1px solid var(--border);border-radius:12px;padding:12px 14px;}
|
||||
.now-item .nk{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--accent);}
|
||||
.now-item .nv{font-size:14px;font-weight:600;margin-top:3px;}
|
||||
|
||||
.row{display:flex;align-items:center;gap:12px;padding:13px 10px;border-radius:10px;border-bottom:1px solid var(--border);transition:background .15s;}
|
||||
.section .row:last-child{border-bottom:none;}
|
||||
.row:hover{background:var(--card2);}
|
||||
.row .ico{width:36px;height:36px;border-radius:10px;flex-shrink:0;background:var(--chip);display:flex;align-items:center;justify-content:center;}
|
||||
.row .ico svg{width:19px;height:19px;fill:var(--muted);}
|
||||
.row .r-body{flex:1;min-width:0;}
|
||||
.row .r-title{font-size:14px;font-weight:600;}
|
||||
.row .r-right{font-size:13px;color:var(--muted);flex-shrink:0;}
|
||||
|
||||
.post{display:block;padding:14px 10px;border-radius:10px;border-bottom:1px solid var(--border);transition:background .15s;}
|
||||
.post:hover{background:var(--card2);}
|
||||
.post .p-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px;}
|
||||
.post .p-title{font-size:14px;font-weight:700;}
|
||||
.post .p-date{font-size:12px;color:var(--muted);flex-shrink:0;}
|
||||
.post .p-body{font-size:13px;color:var(--muted);margin-top:4px;}
|
||||
|
||||
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
||||
.cell{border:1px solid var(--border);border-radius:12px;padding:16px;transition:border-color .15s,background .15s;}
|
||||
.cell:hover{border-color:var(--accent);background:var(--card2);}
|
||||
.cell .ck{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);}
|
||||
.cell .cv{font-size:14px;font-weight:600;margin-top:6px;word-break:break-all;}
|
||||
.cell a.cv{color:var(--accent);}
|
||||
|
||||
.tz{display:flex;align-items:center;justify-content:space-between;gap:12px;}
|
||||
.tz .tk{font-size:11px;letter-spacing:.04em;color:var(--muted);}
|
||||
.tz .tt{font-size:20px;font-weight:800;font-variant-numeric:tabular-nums;}
|
||||
.tz .ts{font-size:12px;color:var(--muted);}
|
||||
|
||||
.share-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;}
|
||||
.btn{font-family:inherit;font-size:13px;font-weight:700;padding:8px 16px;border-radius:999px;cursor:pointer;border:1px solid var(--border);background:var(--card);color:var(--text);transition:background .15s,border-color .15s;}
|
||||
.btn:hover{background:var(--card2);}
|
||||
.btn-solid{background:var(--accent);border-color:var(--accent);color:#fff;}
|
||||
.btn-solid:hover{background:#1a89d8;}
|
||||
|
||||
.no-grav{margin-top:22px;font-size:13px;color:var(--muted);text-align:center;}
|
||||
.no-grav a{color:var(--accent);font-weight:600;}
|
||||
.no-grav a:hover{text-decoration:underline;}
|
||||
|
||||
.theme-btn{position:fixed;top:18px;right:18px;z-index:50;width:40px;height:40px;border-radius:50%;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;}
|
||||
.theme-btn:hover{background:var(--card2);}
|
||||
|
||||
@media(max-width:520px){
|
||||
body{padding:0 0 40px;}
|
||||
.banner{height:120px;}
|
||||
.profile{padding:0 16px 10px;}
|
||||
.section{padding:18px 16px;}
|
||||
.avatar{width:92px;height:92px;margin-top:-44px;}
|
||||
.contact-grid{grid-template-columns:1fr;}
|
||||
.now-grid{grid-template-columns:1fr 1fr;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button class="theme-btn" id="themeBtn" aria-label="Toggle theme">☾</button>
|
||||
|
||||
<div class="card">
|
||||
<div class="banner">
|
||||
<img src="/static/back.jpg" alt="" aria-hidden="true">
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<img class="avatar" id="avatar" src="" alt="miles wolf allen" width="120" height="120">
|
||||
<div class="name" id="name"></div>
|
||||
<div class="handle" id="handle"></div>
|
||||
<div class="meta"><span id="meta"></span> · <b id="tzLabel"></b></div>
|
||||
<p class="bio" id="bio"></p>
|
||||
|
||||
<div class="socials">
|
||||
<a class="soc" id="socBluesky" href="#" target="_blank" rel="noopener" aria-label="Bluesky">
|
||||
<svg viewBox="0 0 24 24"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>
|
||||
</a>
|
||||
<a class="soc" id="socGithub" href="#" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||
</a>
|
||||
<a class="soc" id="socX" href="#" target="_blank" rel="noopener" aria-label="X">
|
||||
<svg viewBox="0 0 24 24"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>
|
||||
</a>
|
||||
<a class="soc" id="socDiscord" href="#" target="_blank" rel="noopener" aria-label="Discord">
|
||||
<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" id="nowSection">
|
||||
<div class="sec-title">Now</div>
|
||||
<div class="now-grid" id="nowGrid"></div>
|
||||
</div>
|
||||
|
||||
<div class="section" id="postsSection">
|
||||
<div class="sec-title">Latest posts</div>
|
||||
<div id="postsList"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="sec-title">Contact</div>
|
||||
<div class="contact-grid">
|
||||
<a class="cell" href="mailto:[email protected]">
|
||||
<div class="ck">Email</div>
|
||||
<div class="cv">[email protected]</div>
|
||||
</a>
|
||||
<a class="cell" href="tel:+17633121150">
|
||||
<div class="ck">Phone</div>
|
||||
<div class="cv">763-312-1150</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="section tz" style="border-top:none;padding:14px 0 0;">
|
||||
<div class="tk" id="tzName">MN, USA</div>
|
||||
<div>
|
||||
<div class="tt" id="tzClock">--:--</div>
|
||||
<div class="ts" id="tzDate"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="share-row">
|
||||
<a class="btn" id="shareEmailBtn" href="#">Share via email</a>
|
||||
<button class="btn btn-solid" id="copyBtn" type="button">Copy link</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-grav">made by <a id="webLink" href="#">miles</a></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var data = window.__DATA__ || {};
|
||||
var themeBtn = document.getElementById('themeBtn');
|
||||
|
||||
// Dark mode by default, but respect an explicit saved preference.
|
||||
var saved = null;
|
||||
try { saved = localStorage.getItem('miles-theme'); } catch (e) {}
|
||||
var theme = saved || 'dark';
|
||||
applyTheme(theme);
|
||||
|
||||
function applyTheme(t) {
|
||||
document.documentElement.classList.toggle('light', t === 'light');
|
||||
themeBtn.textContent = t === 'light' ? '☀' : '☾';
|
||||
}
|
||||
themeBtn.addEventListener('click', function () {
|
||||
var cur = document.documentElement.classList.contains('light') ? 'light' : 'dark';
|
||||
var next = cur === 'light' ? 'dark' : 'light';
|
||||
applyTheme(next);
|
||||
try { localStorage.setItem('miles-theme', next); } catch (e) {}
|
||||
});
|
||||
|
||||
function render(db) {
|
||||
var p = db.profile || {};
|
||||
var nameEl = document.getElementById('name');
|
||||
nameEl.textContent = p.name || 'miles wolf allen';
|
||||
document.title = (p.name || 'miles wolf allen') + ' — profile';
|
||||
document.getElementById('avatar').src = p.avatar || 'https://avatars.githubusercontent.com/u/115095719?v=4';
|
||||
document.getElementById('handle').textContent = p.handle || '@mileswa · software engineer';
|
||||
document.getElementById('bio').textContent = p.bio || '';
|
||||
var web = p.website || 'https://milesallen.site';
|
||||
document.getElementById('webLink').href = web;
|
||||
document.querySelector('.bio a') && document.querySelector('.bio a').setAttribute('href', web);
|
||||
|
||||
var tz = db.timezone || {};
|
||||
document.getElementById('tzName').textContent = tz.label || 'MN, USA';
|
||||
var tzId = tz.id || 'America/Chicago';
|
||||
var now = document.getElementById('nowGrid');
|
||||
now.innerHTML = '';
|
||||
(db.now || []).forEach(function (item) {
|
||||
if (!item || !item[1]) return;
|
||||
var el = document.createElement('div');
|
||||
el.className = 'now-item';
|
||||
el.innerHTML = '<div><div class="nk">' + esc(item[0] || 'Now') + '</div><div class="nv">' + esc(item[1]) + '</div></div>';
|
||||
now.appendChild(el);
|
||||
});
|
||||
var ns = document.getElementById('nowSection');
|
||||
if ((db.now || []).length === 0) ns.style.display = 'none';
|
||||
|
||||
var posts = db.posts || [];
|
||||
var list = document.getElementById('postsList');
|
||||
list.innerHTML = '';
|
||||
posts.slice(0, 6).forEach(function (post) {
|
||||
var a = document.createElement('a');
|
||||
a.className = 'post';
|
||||
if (post.link) { a.href = post.link; a.target = '_blank'; a.rel = 'noopener'; }
|
||||
var d = post.date ? esc(fmtDate(post.date)) : '';
|
||||
a.innerHTML = '<div class="p-top"><span class="p-title">' + esc(post.title) + '</span><span class="p-date">' + d + '</span></div><div class="p-body">' + esc(post.body || '') + '</div>';
|
||||
list.appendChild(a);
|
||||
});
|
||||
var ps = document.getElementById('postsSection');
|
||||
if (posts.length === 0) ps.style.display = 'none';
|
||||
|
||||
// Share email link
|
||||
document.getElementById('shareEmailBtn').href =
|
||||
'mailto:?subject=' + encodeURIComponent((p.name || '').trim()) +
|
||||
'&body=' + encodeURIComponent('Check out ' + (p.name || 'miles wolf allen').trim() + ': ' + location.href);
|
||||
|
||||
// clock
|
||||
clock(tzId);
|
||||
setInterval(function () { clock(tzId); }, 1000);
|
||||
}
|
||||
|
||||
function clock(tzId) {
|
||||
var d = new Date();
|
||||
try {
|
||||
var tf = new Intl.DateTimeFormat('en-US', { timeZone: tzId, hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true });
|
||||
var df = new Intl.DateTimeFormat('en-US', { timeZone: tzId, weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
|
||||
document.getElementById('tzClock').textContent = tf.format(d);
|
||||
document.getElementById('tzDate').textContent = df.format(d);
|
||||
} catch (e) {
|
||||
document.getElementById('tzClock').textContent = '--:--:--';
|
||||
}
|
||||
}
|
||||
|
||||
function fmtDate(s) {
|
||||
try {
|
||||
var d = new Date(s + (s.indexOf('T') === -1 ? 'T12:00:00' : ''));
|
||||
if (isNaN(d)) return s;
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
} catch (e) { return s; }
|
||||
}
|
||||
function esc(s) {
|
||||
return String(s).replace(/[&<>"']/g, function (c) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
fetch('/api/data').then(function (r) { return r.json(); }).then(render).catch(function () { render(data); });
|
||||
}
|
||||
|
||||
render(data);
|
||||
refresh();
|
||||
|
||||
var copyBtn = document.getElementById('copyBtn');
|
||||
copyBtn.addEventListener('click', function () {
|
||||
copyText(location.href, function () { flash(copyBtn, 'Copied!'); });
|
||||
});
|
||||
function copyText(txt, done) {
|
||||
var ok = function () { done(); };
|
||||
var fail = function () {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
||||
document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand('copy'); ok(); } catch (e) { done(); }
|
||||
document.body.removeChild(ta);
|
||||
};
|
||||
if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(txt).then(ok, fail);
|
||||
else fail();
|
||||
}
|
||||
function flash(btn, txt) {
|
||||
var old = btn.textContent; btn.textContent = txt; btn.disabled = true;
|
||||
setTimeout(function () { btn.textContent = old; btn.disabled = false; }, 1400);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>miles wolf allen</title>
|
||||
<meta name="description" content="miles wolf allen — Software engineer. Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust.">
|
||||
<meta name="author" content="Miles Wolf Allen">
|
||||
<link rel="canonical" href="https://milesallen.site/share.html">
|
||||
<meta property="og:type" content="profile">
|
||||
<meta property="og:title" content="miles wolf allen">
|
||||
<meta property="og:description" content="Software engineer. Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust.">
|
||||
<meta property="og:url" content="https://milesallen.site/share.html">
|
||||
<meta property="og:image" content="https://avatars.githubusercontent.com/u/115095719?v=4">
|
||||
<meta property="og:site_name" content="miles wolf allen">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="miles wolf allen">
|
||||
<meta name="twitter:description" content="Software engineer. Developer and 3D artist interested in coding, games, and web technology.">
|
||||
<meta name="twitter:image" content="https://avatars.githubusercontent.com/u/115095719?v=4">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Miles Wolf Allen",
|
||||
"url": "https://milesallen.site/",
|
||||
"image": "https://avatars.githubusercontent.com/u/115095719?v=4",
|
||||
"email": "mailto:[email protected]",
|
||||
"telephone": "+1-763-312-1150",
|
||||
"jobTitle": "Software Engineer",
|
||||
"knowsAbout": ["Software Development", "Game Development", "Web Technology", "Rust"],
|
||||
"sameAs": [
|
||||
"https://bsky.app/profile/wolfreaper90.bsky.social",
|
||||
"https://github.com/mileswolfallen2",
|
||||
"https://open.spotify.com/user/31bodx3k4uok7wcmm64hovz57udu",
|
||||
"https://www.youtube.com/channel/UCeUJbFoCDwUUg6uZi-G-zhQ",
|
||||
"https://www.twitch.tv/mileswa1q2"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--page:#eef1f5;
|
||||
--card:#ffffff;
|
||||
--border:#e3e8ee;
|
||||
--text:#14161a;
|
||||
--muted:#647182;
|
||||
--accent:#1d9bf0;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{
|
||||
background:var(--page);color:var(--text);
|
||||
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
line-height:1.5;
|
||||
display:flex;flex-direction:column;align-items:center;
|
||||
min-height:100vh;padding:40px 20px 64px;
|
||||
overflow-x:hidden;position:relative;
|
||||
}
|
||||
body::before{
|
||||
content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
|
||||
}
|
||||
#bgCanvas{position:fixed;inset:0;z-index:-2;pointer-events:none;display:block;}
|
||||
body::after{
|
||||
content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(rgba(20,22,26,.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(20,22,26,.045) 1px, transparent 1px);
|
||||
background-size:44px 44px;
|
||||
-webkit-mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
|
||||
.card{
|
||||
width:100%;max-width:600px;
|
||||
background:var(--card);
|
||||
border:1px solid var(--border);
|
||||
border-radius:16px;
|
||||
overflow:hidden;
|
||||
box-shadow:0 1px 2px rgba(16,24,40,.05),0 10px 30px rgba(16,24,40,.08);
|
||||
}
|
||||
|
||||
.banner{height:160px;overflow:hidden;background:#dbe1e8;}
|
||||
.banner img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;}
|
||||
|
||||
.profile{padding:0 24px 24px;}
|
||||
.avatar{
|
||||
display:block;width:120px;height:120px;border-radius:50%;
|
||||
border:4px solid var(--card);
|
||||
background:#e5e9ee;
|
||||
box-shadow:0 2px 10px rgba(16,24,40,.2);
|
||||
object-fit:cover;margin-top:-56px;
|
||||
}
|
||||
.name{font-size:22px;font-weight:800;letter-spacing:-.01em;margin-top:14px;line-height:1.2;}
|
||||
.handle{font-size:14px;color:var(--muted);margin-top:3px;}
|
||||
.meta{font-size:13px;color:var(--muted);margin-top:8px;}
|
||||
.meta b{color:var(--text);font-weight:600;}
|
||||
.bio{font-size:15px;margin-top:16px;}
|
||||
.bio a{color:var(--accent);}
|
||||
.bio a:hover{text-decoration:underline;}
|
||||
|
||||
.socials{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:20px;}
|
||||
.soc{
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
height:44px;border:1px solid var(--border);border-radius:12px;
|
||||
color:var(--muted);transition:all .15s;
|
||||
}
|
||||
.soc svg{width:22px;height:22px;fill:currentColor;}
|
||||
.soc:hover{color:var(--accent);border-color:var(--accent);background:#f6fafe;transform:translateY(-2px);}
|
||||
|
||||
.section{border-top:1px solid var(--border);padding:20px 24px;}
|
||||
.sec-title{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);margin-bottom:4px;}
|
||||
.sec-hint{font-size:14px;color:var(--muted);margin-bottom:14px;}
|
||||
|
||||
.row{
|
||||
display:flex;align-items:center;gap:12px;
|
||||
padding:13px 10px;border-radius:10px;
|
||||
border-bottom:1px solid var(--border);
|
||||
transition:background .15s;
|
||||
}
|
||||
.section .row:last-child{border-bottom:none;}
|
||||
.row:hover{background:#f7f9fb;}
|
||||
.row .ico{
|
||||
width:36px;height:36px;border-radius:10px;flex-shrink:0;
|
||||
background:#eef3f8;display:flex;align-items:center;justify-content:center;
|
||||
}
|
||||
.row .ico svg{width:19px;height:19px;fill:#4b5563;}
|
||||
.row .r-body{flex:1;min-width:0;}
|
||||
.row .r-title{font-size:14px;font-weight:600;}
|
||||
.row .r-right{font-size:13px;color:var(--muted);flex-shrink:0;}
|
||||
|
||||
.contact-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
|
||||
.cell{
|
||||
border:1px solid var(--border);border-radius:12px;padding:16px;
|
||||
transition:border-color .15s,background .15s;
|
||||
}
|
||||
.cell:hover{border-color:var(--accent);background:#f7fbfe;}
|
||||
.cell .ck{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);}
|
||||
.cell .cv{font-size:14px;font-weight:600;margin-top:6px;word-break:break-all;}
|
||||
.cell a.cv{color:var(--accent);}
|
||||
|
||||
.share-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:6px;}
|
||||
.btn{
|
||||
font-family:inherit;font-size:13px;font-weight:700;
|
||||
padding:8px 16px;border-radius:999px;cursor:pointer;
|
||||
border:1px solid var(--border);background:var(--card);color:var(--text);
|
||||
transition:background .15s,border-color .15s;
|
||||
}
|
||||
.btn:hover{background:#f4f6f9;}
|
||||
.btn-solid{background:var(--accent);border-color:var(--accent);color:#fff;}
|
||||
.btn-solid:hover{background:#1a89d8;}
|
||||
|
||||
.no-grav{margin-top:26px;font-size:13px;color:var(--muted);text-align:center;}
|
||||
.no-grav a{color:var(--accent);font-weight:600;}
|
||||
.no-grav a:hover{text-decoration:underline;}
|
||||
.sublink{margin-top:22px;text-align:center;}
|
||||
.sublink a{display:inline-block;font-size:13px;font-weight:700;color:var(--accent);border:1px solid var(--border);background:var(--card);padding:9px 18px;border-radius:999px;transition:all .15s;}
|
||||
.sublink a:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(29,155,240,.15);}
|
||||
|
||||
@media(max-width:520px){
|
||||
body{padding:0 0 40px;}
|
||||
.banner{height:120px;}
|
||||
.profile{padding:0 16px 20px;}
|
||||
.section{padding:18px 16px;}
|
||||
.avatar{width:92px;height:92px;margin-top:-44px;}
|
||||
.socials{grid-template-columns:repeat(4,1fr);}
|
||||
.contact-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ---- Load entrance (only active while .anim is applied) ---- */
|
||||
.anim [data-anim]{opacity:0;}
|
||||
.anim .avatar{will-change:transform,opacity;}
|
||||
.anim .card{will-change:transform,opacity;}
|
||||
.soc{transition:transform .18s ease,color .15s,border-color .15s,background .15s;}
|
||||
.soc:hover{transform:translateY(-3px) scale(1.04);}
|
||||
.btn-solid{transform-origin:center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas" aria-hidden="true"></canvas>
|
||||
|
||||
<div class="card">
|
||||
<div class="banner" data-anim="banner">
|
||||
<img src="back.jpg" alt="" aria-hidden="true">
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<img class="avatar" data-anim="avatar" src="https://avatars.githubusercontent.com/u/115095719?v=4" alt="miles wolf allen" width="120" height="120">
|
||||
<div class="name" data-anim="txt">miles wolf allen</div>
|
||||
<div class="handle" data-anim="txt">@wolf_reaper90 · software engineer</div>
|
||||
<div class="meta" data-anim="txt">he/him · <b>MN, USA</b> · <span id="tzWatch">local time --:--:--</span></div>
|
||||
|
||||
<p class="bio" data-anim="bio">Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust. <a href="https://milesallen.site">↗ milesallen.site</a></p>
|
||||
|
||||
<div class="socials">
|
||||
<a class="soc" data-anim="soc" href="https://bsky.app/profile/wolfreaper90.bsky.social" target="_blank" rel="noopener" aria-label="Bluesky">
|
||||
<svg viewBox="0 0 24 24"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>
|
||||
</a>
|
||||
<a class="soc" data-anim="soc" href="https://github.com/mileswolfallen2" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||
</a>
|
||||
<a class="soc" data-anim="soc" href="https://www.twitch.tv/mileswa1q2" target="_blank" rel="noopener" aria-label="Twitch">
|
||||
<svg viewBox="0 0 24 24"><path d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0 1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"/></svg>
|
||||
</a>
|
||||
<a class="soc" data-anim="soc" href="https://discord.com/users/1058108777112543402" target="_blank" rel="noopener" aria-label="Discord">
|
||||
<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" data-anim="section">
|
||||
<div class="sec-title">Profiles</div>
|
||||
<a class="row" href="https://bsky.app/profile/wolfreaper90.bsky.social" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">Bluesky</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://github.com/mileswolfallen2" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">GitHub</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://www.npmjs.com/~wolf_reaper91" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">npm</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://x.com/Mileswa1q2" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">X</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://www.threads.net/@mileswa1q2" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">Threads</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://open.spotify.com/user/31bodx3k4uok7wcmm64hovz57udu" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">Spotify</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://www.youtube.com/channel/UCeUJbFoCDwUUg6uZi-G-zhQ" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">YouTube</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://www.twitch.tv/mileswa1q2" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0 1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">Twitch</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
<a class="row" href="https://discord.com/users/1058108777112543402" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z"/></svg></span>
|
||||
<span class="r-body"><span class="r-title">Discord</span></span>
|
||||
<span class="r-right">↗</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="section" data-anim="section">
|
||||
<div class="sec-title">Contact</div>
|
||||
<div class="contact-grid">
|
||||
<a class="cell" href="mailto:[email protected]">
|
||||
<div class="ck">Email</div>
|
||||
<div class="cv">[email protected]</div>
|
||||
</a>
|
||||
<a class="cell" href="tel:+17633121150">
|
||||
<div class="ck">Phone</div>
|
||||
<div class="cv">763-312-1150</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="share-row">
|
||||
<a class="btn" href="mailto:?subject=miles%20wolf%20allen&body=Check%20out%20miles%20wolf%20allen%3A%20https%3A%2F%2Fmilesallen.site%2Fshare.html">Share via email</a>
|
||||
<button class="btn btn-solid" id="copyBtn" type="button">Copy link</button>
|
||||
<button class="btn" id="vcBtn" type="button">Save contact</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sublink" data-anim="fade"><a href="top-shows.html">⬉ my top shows</a> · <a href="dr-stone.html">Dr. Stone breakdown</a> · <a href="bsg.html">Battlestar Galactica breakdown</a></div>
|
||||
|
||||
<div class="no-grav" data-anim="fade">made by <a href="index.html">miles</a>. not ai </div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
|
||||
// ---- Ambient aurora + particle background ----
|
||||
(function bg(){
|
||||
var canvas = document.getElementById('bgCanvas');
|
||||
if(!canvas) return;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
var orbs = [];
|
||||
var parts = [];
|
||||
var COLORS = [
|
||||
[29,155,240], // blue
|
||||
[255,61,154], // pink
|
||||
[34,211,153], // mint
|
||||
[168,85,247] // purple
|
||||
];
|
||||
|
||||
function resize(){
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
seed();
|
||||
}
|
||||
function seed(){
|
||||
orbs = [];
|
||||
parts = [];
|
||||
var w = canvas.width, h = canvas.height;
|
||||
var oCount = Math.min(7, Math.round(w / 200) + 3);
|
||||
for(var i=0;i<oCount;i++){
|
||||
orbs.push({
|
||||
x: Math.random()*w,
|
||||
y: Math.random()*h,
|
||||
r: 110 + Math.random()*220,
|
||||
c: COLORS[i % COLORS.length],
|
||||
sp: 0.10 + Math.random()*0.22,
|
||||
a: 0.14 + Math.random()*0.12,
|
||||
ox: Math.random()*1000,
|
||||
oy: Math.random()*1000
|
||||
});
|
||||
}
|
||||
var pCount = Math.min(80, Math.round(w*h/18000));
|
||||
for(var j=0;j<pCount;j++){
|
||||
parts.push({
|
||||
x: Math.random()*w,
|
||||
y: Math.random()*h,
|
||||
r: 0.6 + Math.random()*1.6,
|
||||
sp: 0.05 + Math.random()*0.25,
|
||||
ph: Math.random()*Math.PI*2,
|
||||
oy: Math.random()*100
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var t = 0;
|
||||
function frame(){
|
||||
requestAnimationFrame(frame);
|
||||
if(reduced) return;
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
t += 0.008;
|
||||
|
||||
// aurora orbs with slow, fluid drift (composited soft light)
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
for(var i=0;i<orbs.length;i++){
|
||||
var o = orbs[i];
|
||||
var nx = o.x + Math.sin(t*o.sp + o.ox) * (o.r*0.9);
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.oy) * (o.r*0.7) * 0.8;
|
||||
var g = ctx.createRadialGradient(nx,ny,0,nx,ny,o.r);
|
||||
g.addColorStop(0, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+','+o.a+')');
|
||||
g.addColorStop(1, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+',0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath();
|
||||
ctx.arc(nx,ny,o.r,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// drifting particle specks
|
||||
ctx.fillStyle = 'rgba(40,50,70,0.35)';
|
||||
for(var p=0;p<parts.length;p++){
|
||||
var pt = parts[p];
|
||||
var px = pt.x + Math.sin(t*pt.sp + pt.ph) * 40;
|
||||
var py = pt.y + Math.cos(t*pt.sp*0.7 + pt.ph*1.7 + pt.oy) * 26;
|
||||
var tw = 0.4 + 0.6*Math.abs(Math.sin(t*0.6 + pt.ph));
|
||||
ctx.globalAlpha = tw*0.6;
|
||||
ctx.beginPath();
|
||||
ctx.arc(px%canvas.width, py%canvas.height, pt.r, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
frame();
|
||||
})();
|
||||
|
||||
var copyBtn = document.getElementById('copyBtn');
|
||||
if(copyBtn) copyBtn.addEventListener('click', function(){
|
||||
copyText('https://milesallen.site/share.html', copyBtn, function(){ flash(copyBtn, 'Copied!'); });
|
||||
});
|
||||
|
||||
var vcBtn = document.getElementById('vcBtn');
|
||||
if(vcBtn) vcBtn.addEventListener('click', function(){
|
||||
var vcard = [
|
||||
'BEGIN:VCARD','VERSION:3.0','N:Allen;Miles Wolf;;;','FN:Miles Wolf Allen',
|
||||
'ORG:Diskette Labs','TITLE:Software Engineer','TEL;TYPE=CELL:+1-763-312-1150',
|
||||
'EMAIL;TYPE=INTERNET:[email protected]','URL:https://milesallen.site',
|
||||
'END:VCARD'
|
||||
].join('\r\n');
|
||||
var blob = new Blob([vcard], { type: 'text/vcard' });
|
||||
var a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = 'miles-wolf-allen.vcf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
setTimeout(function(){ URL.revokeObjectURL(a.href); }, 400);
|
||||
flash(vcBtn, 'Saved!');
|
||||
});
|
||||
|
||||
function tzClock(){
|
||||
var d = new Date(), tf;
|
||||
try {
|
||||
tf = new Intl.DateTimeFormat('en-US', { timeZone:'America/Chicago', hour:'2-digit', minute:'2-digit', hour12:true });
|
||||
} catch(e){ return; }
|
||||
var w = document.getElementById('tzWatch');
|
||||
if(w) w.textContent = 'local time ' + tf.format(d);
|
||||
}
|
||||
tzClock();
|
||||
setInterval(tzClock, 30000);
|
||||
|
||||
function copyText(txt, btn, done){
|
||||
var ok = function(){ if(done) done(); };
|
||||
var fail = function(){
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
||||
document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand('copy'); ok(); } catch(e){ if(done) done(); }
|
||||
document.body.removeChild(ta);
|
||||
};
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(txt).then(ok, fail);
|
||||
} else { fail(); }
|
||||
}
|
||||
function flash(btn, txt){
|
||||
var old = btn.textContent;
|
||||
btn.textContent = txt; btn.disabled = true;
|
||||
setTimeout(function(){ btn.textContent = old; btn.disabled = false; }, 1400);
|
||||
}
|
||||
|
||||
// ---- Choreographed load entrance (Anime.js) ----
|
||||
(function load(){
|
||||
var body = document.body;
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
body.classList.add('anim');
|
||||
var done = function(){ body.classList.remove('anim'); };
|
||||
if (!window.anime || reduced) { done(); return; }
|
||||
|
||||
// gentle continuous pulse on the copy button via Animate.css
|
||||
var copyBtn2 = document.getElementById('copyBtn');
|
||||
if (copyBtn2) copyBtn2.classList.add('animate__animated','animate__pulse','animate__infinite','animate__slower');
|
||||
|
||||
var tl = anime.timeline({ complete: done });
|
||||
|
||||
tl
|
||||
.add({
|
||||
targets: '[data-anim="banner"]',
|
||||
translateY:[-46,0], scaleY:[0,1], opacity:[0,1],
|
||||
transformOrigin:'50% 0%',
|
||||
duration:760, easing:'easeOutQuart'
|
||||
})
|
||||
.add({
|
||||
targets: '[data-anim="avatar"]',
|
||||
scale:[0.3,1.06,1], opacity:[0,1], rotate:[-12,3,0],
|
||||
duration:850, easing:'easeOutElastic(1,0.6)',
|
||||
offset:'-=520'
|
||||
})
|
||||
.add({
|
||||
targets: '[data-anim="txt"]',
|
||||
translateY:[28,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(70), easing:'easeOutExpo'
|
||||
}, '-=430')
|
||||
.add({
|
||||
targets: '[data-anim="bio"]',
|
||||
translateY:[18,0], opacity:[0,1],
|
||||
duration:470, easing:'easeOutExpo'
|
||||
}, '-=340')
|
||||
.add({
|
||||
targets: '[data-anim="soc"]',
|
||||
scale:[0.4,1.12,1], rotate:[-8,4,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(55), easing:'easeOutElastic(1,0.7)'
|
||||
}, '-=280')
|
||||
.add({
|
||||
targets: '[data-anim="section"]',
|
||||
translateX:[64,0], opacity:[0,1],
|
||||
duration:560, delay:anime.stagger(140), easing:'easeOutQuint'
|
||||
}, '-=160')
|
||||
.add({
|
||||
targets: '[data-anim="fade"]',
|
||||
opacity:[0,1], translateY:[10,0], duration:420, easing:'easeOutQuart'
|
||||
}, '-=220');
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+24
@@ -144,10 +144,34 @@
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/share.html</loc>
|
||||
<lastmod>2026-08-28</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/404.html</loc>
|
||||
<lastmod>2026-07-28</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/top-shows.html</loc>
|
||||
<lastmod>2026-08-30</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/dr-stone.html</loc>
|
||||
<lastmod>2026-09-06</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://milesallen.site/bsg.html</loc>
|
||||
<lastmod>2026-09-06</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
@@ -0,0 +1,527 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Miles Wolf Allen | Developer & 3D Artist</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--cream: #f5f0e8;
|
||||
--black: #111111;
|
||||
--yellow: #f6e548;
|
||||
--pink: #ff6ca8;
|
||||
--blue: #9fd9ff;
|
||||
--orange: #ff7147;
|
||||
--border: 2px solid var(--black);
|
||||
--shadow: 6px 6px 0 var(--black);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--cream);
|
||||
color: var(--black);
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: min(1180px, calc(100% - 40px));
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 0;
|
||||
border-bottom: var(--border);
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: flex;
|
||||
gap: 26px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 770px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr .8fr;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
position: relative;
|
||||
padding: 100px 0 130px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 800px;
|
||||
font-size: clamp(68px, 10vw, 150px);
|
||||
line-height: .82;
|
||||
letter-spacing: -9px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
max-width: 430px;
|
||||
margin-top: 38px;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
margin-top: 32px;
|
||||
padding: 16px 22px;
|
||||
border: var(--border);
|
||||
background: var(--yellow);
|
||||
box-shadow: var(--shadow);
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
transition: .2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translate(4px, 4px);
|
||||
box-shadow: 2px 2px 0 var(--black);
|
||||
}
|
||||
|
||||
.sticker {
|
||||
position: absolute;
|
||||
top: 130px;
|
||||
right: 4%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--pink);
|
||||
border: var(--border);
|
||||
transform: rotate(12deg);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-art {
|
||||
min-height: 450px;
|
||||
border: var(--border);
|
||||
background:
|
||||
linear-gradient(135deg, transparent 35%, var(--black) 36% 39%, transparent 40%),
|
||||
var(--blue);
|
||||
box-shadow: var(--shadow);
|
||||
position: relative;
|
||||
transform: rotate(3deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-art::before {
|
||||
content: "MAKE IT MATTER";
|
||||
position: absolute;
|
||||
top: 28%;
|
||||
left: -12%;
|
||||
width: 130%;
|
||||
padding: 18px;
|
||||
background: var(--orange);
|
||||
border-block: var(--border);
|
||||
font-size: clamp(28px, 4vw, 56px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -3px;
|
||||
transform: rotate(-14deg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hero-art::after {
|
||||
content: "✳";
|
||||
position: absolute;
|
||||
right: 15%;
|
||||
bottom: 12%;
|
||||
font-size: 130px;
|
||||
}
|
||||
|
||||
.marquee {
|
||||
border-block: var(--border);
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 14px 0;
|
||||
font-size: 22px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.marquee span {
|
||||
display: inline-block;
|
||||
animation: move 18s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-width: 880px;
|
||||
font-size: clamp(36px, 6vw, 78px);
|
||||
line-height: .95;
|
||||
letter-spacing: -4px;
|
||||
}
|
||||
|
||||
.services {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 370px;
|
||||
padding: 26px;
|
||||
border: var(--border);
|
||||
box-shadow: var(--shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card:nth-child(1) { background: var(--yellow); }
|
||||
.card:nth-child(2) { background: var(--pink); }
|
||||
.card:nth-child(3) { background: var(--blue); }
|
||||
|
||||
.card-number {
|
||||
font-family: "DM Mono", monospace;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: 34px;
|
||||
line-height: .95;
|
||||
letter-spacing: -2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card p {
|
||||
font-size: 17px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.work {
|
||||
background: var(--orange);
|
||||
border-block: var(--border);
|
||||
}
|
||||
|
||||
.work-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 22px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.project {
|
||||
min-height: 310px;
|
||||
padding: 25px;
|
||||
border: var(--border);
|
||||
background: var(--cream);
|
||||
box-shadow: var(--shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.project:nth-child(2) { background: var(--blue); }
|
||||
.project:nth-child(3) { background: var(--pink); }
|
||||
.project:nth-child(4) { background: var(--yellow); }
|
||||
|
||||
.project h3 {
|
||||
font-size: 42px;
|
||||
letter-spacing: -3px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.project p {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.testimonial {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: clamp(32px, 5vw, 65px);
|
||||
line-height: .98;
|
||||
letter-spacing: -3px;
|
||||
}
|
||||
|
||||
cite {
|
||||
display: block;
|
||||
margin-top: 28px;
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.contact {
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact h2 {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
font-size: clamp(56px, 10vw, 140px);
|
||||
line-height: .82;
|
||||
letter-spacing: -8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.contact .button {
|
||||
color: var(--black);
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 25px 0;
|
||||
background: var(--black);
|
||||
color: var(--cream);
|
||||
border-top: 1px solid #555;
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
nav ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: block;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
letter-spacing: -5px;
|
||||
}
|
||||
|
||||
.sticker {
|
||||
top: 34px;
|
||||
right: 0;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hero-art {
|
||||
margin: 70px 12px 0 0;
|
||||
min-height: 330px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.services,
|
||||
.work-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: block;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<a href="#" class="logo">LOUD®</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#work">Work</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Independent brand strategy & copywriting</p>
|
||||
<h1>Words that refuse to whisper.</h1>
|
||||
<p class="hero-copy">
|
||||
I help ambitious brands find their voice, sharpen their message,
|
||||
and turn casual attention into meaningful action.
|
||||
</p>
|
||||
<a href="#contact" class="button">Start a conversation ↗</a>
|
||||
</div>
|
||||
|
||||
<div class="hero-art" aria-hidden="true"></div>
|
||||
<div class="sticker">Good brands<br>have opinions</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="marquee">
|
||||
<span>
|
||||
Strategy ✳ Positioning ✳ Copywriting ✳ Messaging ✳ Strategy ✳ Positioning ✳ Copywriting ✳ Messaging ✳
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<section id="services">
|
||||
<p class="section-label">01 / What I do</p>
|
||||
|
||||
<p class="intro">
|
||||
Your brand does not need more content. It needs a clearer point of view.
|
||||
</p>
|
||||
|
||||
<div class="services">
|
||||
<article class="card">
|
||||
<span class="card-number">01</span>
|
||||
<h3>Message<br>Reset</h3>
|
||||
<p>
|
||||
Find the sharpest version of what you do, who it is for, and why
|
||||
anyone should care.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<span class="card-number">02</span>
|
||||
<h3>Conversion<br>Copy</h3>
|
||||
<p>
|
||||
Websites, landing pages, emails, product pages, and campaigns that
|
||||
make the next step obvious.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<span class="card-number">03</span>
|
||||
<h3>Fractional<br>Creative Lead</h3>
|
||||
<p>
|
||||
Senior-level thinking for teams that need direction, momentum, and
|
||||
someone willing to challenge the safe idea.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="work" id="work">
|
||||
<div class="container">
|
||||
<p class="section-label">02 / Selected work</p>
|
||||
|
||||
<div class="work-grid">
|
||||
<article class="project">
|
||||
<h3>Northstar</h3>
|
||||
<p>Brand positioning / Website copy</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>Good Goods</h3>
|
||||
<p>Messaging system / Product launch</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>Orbit</h3>
|
||||
<p>Campaign concept / Conversion copy</p>
|
||||
</article>
|
||||
|
||||
<article class="project">
|
||||
<h3>Fieldwork</h3>
|
||||
<p>Brand voice / Content direction</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="container">
|
||||
<section>
|
||||
<div class="testimonial">
|
||||
<blockquote>
|
||||
“They turned a complicated offer into a message our customers
|
||||
understood immediately.”
|
||||
</blockquote>
|
||||
<cite>— Jamie Lee, Founder at Northstar</cite>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="contact" id="contact">
|
||||
<div class="container">
|
||||
<h2>Make your brand impossible to ignore.</h2>
|
||||
<a href="mailto:[email protected]" class="button">
|
||||
[email protected] ↗
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<span>© 2026 mileswolf allen
|
||||
|
||||
</span>
|
||||
<span>Built with intent, not templates.</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
+584
@@ -0,0 +1,584 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>top shows · miles wolf allen</title>
|
||||
<meta name="description" content="Miles Wolf Allen's top shows: Jujutsu Kaisen, Dr. Stone, Battlestar Galactica, and Invincible — ranked with details.">
|
||||
<meta name="author" content="Miles Wolf Allen">
|
||||
<link rel="canonical" href="https://milesallen.site/top-shows.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="top shows · miles wolf allen">
|
||||
<meta property="og:description" content="My top 4 shows, ranked: their stats, what they're about, and why they made the list.">
|
||||
<meta property="og:url" content="https://milesallen.site/top-shows.html">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="top shows · miles wolf allen">
|
||||
<meta name="twitter:description" content="My top 4 shows, ranked: their stats, what they're about, and why they made the list.">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--page:#eef1f5;
|
||||
--card:#ffffff;
|
||||
--border:#e3e8ee;
|
||||
--text:#14161a;
|
||||
--muted:#647182;
|
||||
--accent:#1d9bf0;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{
|
||||
background:var(--page);color:var(--text);
|
||||
font-family:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
line-height:1.6;
|
||||
display:flex;flex-direction:column;align-items:center;
|
||||
min-height:100vh;padding:40px 20px 64px;
|
||||
overflow-x:hidden;position:relative;
|
||||
}
|
||||
body::after{
|
||||
content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
|
||||
background-image:
|
||||
linear-gradient(rgba(20,22,26,.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(20,22,26,.045) 1px, transparent 1px);
|
||||
background-size:44px 44px;
|
||||
-webkit-mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
mask-image:radial-gradient(820px 720px at 50% 12%, #000 30%, transparent 78%);
|
||||
}
|
||||
#bgCanvas{position:fixed;inset:0;z-index:-2;pointer-events:none;display:block;}
|
||||
a{color:inherit;text-decoration:none;}
|
||||
|
||||
.page-head{width:100%;max-width:680px;text-align:center;margin-bottom:26px;}
|
||||
.crumbs{font-size:12px;color:var(--muted);}
|
||||
.crumbs a{color:var(--accent);font-weight:600;}
|
||||
.crumbs a:hover{text-decoration:underline;}
|
||||
.page-title{font-size:30px;font-weight:900;letter-spacing:-.02em;margin-top:8px;}
|
||||
.page-sub{font-size:14px;color:var(--muted);margin-top:6px;}
|
||||
|
||||
.card{
|
||||
width:100%;max-width:680px;display:flex;flex-direction:column;gap:18px;
|
||||
}
|
||||
|
||||
.show{
|
||||
background:var(--card);border:1px solid var(--border);border-radius:18px;
|
||||
overflow:hidden;display:flex;gap:20px;padding:22px;
|
||||
box-shadow:0 1px 2px rgba(16,24,40,.05),0 14px 34px rgba(16,24,40,.10);
|
||||
}
|
||||
.poster{position:relative;flex-shrink:0;width:130px;align-self:flex-start;}
|
||||
.poster img{width:130px;height:auto;border-radius:12px;display:block;border:1px solid var(--border);
|
||||
box-shadow:0 6px 18px rgba(16,24,40,.18);}
|
||||
.score{
|
||||
position:absolute;right:-14px;bottom:-12px;width:56px;height:56px;border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;flex-direction:column;
|
||||
color:#fff;font-weight:900;font-size:17px;line-height:1;
|
||||
border:3px solid var(--card);
|
||||
}
|
||||
.score .of{font-size:9px;font-weight:700;letter-spacing:.04em;margin-top:2px;opacity:.95;}
|
||||
.score.s1{background:linear-gradient(135deg,#f6c343,#e8930a);box-shadow:0 4px 14px rgba(232,147,10,.5);}
|
||||
.score.s2{background:linear-gradient(135deg,#cfd6df,#8b96a5);box-shadow:0 4px 14px rgba(139,150,165,.45);}
|
||||
.score.s3{background:linear-gradient(135deg,#e2a06e,#b06a3c);box-shadow:0 4px 14px rgba(176,106,60,.45);}
|
||||
.score.s4{background:linear-gradient(135deg,#8b5cf6,#6d28d9);box-shadow:0 4px 14px rgba(109,40,217,.45);}
|
||||
.rank{
|
||||
width:56px;height:56px;border-radius:50%;flex-shrink:0;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-weight:900;font-size:24px;color:#fff;
|
||||
margin-top:4px;
|
||||
}
|
||||
.rank.r1{background:linear-gradient(135deg,#f6c343,#e8930a);box-shadow:0 4px 14px rgba(232,147,10,.4);}
|
||||
.rank.r2{background:linear-gradient(135deg,#cfd6df,#949fad);box-shadow:0 4px 14px rgba(148,159,173,.35);}
|
||||
.rank.r3{background:linear-gradient(135deg,#e2a06e,#b06a3c);box-shadow:0 4px 14px rgba(176,106,60,.35);}
|
||||
.rank.r4{background:linear-gradient(135deg,#8b5cf6,#6d28d9);box-shadow:0 4px 14px rgba(109,40,217,.35);}
|
||||
.show-body{flex:1;min-width:0;}
|
||||
.show-title{font-size:21px;font-weight:800;letter-spacing:-.01em;}
|
||||
.show-type{font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:2px;}
|
||||
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px;}
|
||||
.chip{font-size:11px;font-weight:600;color:var(--muted);border:1px solid var(--border);border-radius:999px;padding:4px 10px;background:#f7f9fb;}
|
||||
.synopsis{font-size:14px;color:#2a2f37;margin-top:13px;}
|
||||
.why{margin-top:13px;font-size:13.5px;background:#f4f8fc;border-left:3px solid var(--accent);border-radius:8px;padding:11px 14px;}
|
||||
.why b{color:var(--text);}
|
||||
|
||||
.show-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:14px;}
|
||||
.action-btn{
|
||||
font-family:inherit;font-size:12px;font-weight:700;
|
||||
color:var(--accent);border:1px solid var(--border);background:var(--card);
|
||||
padding:7px 15px;border-radius:999px;cursor:pointer;
|
||||
transition:all .15s;text-decoration:none;
|
||||
}
|
||||
.action-btn:hover{border-color:var(--accent);background:#f4f8fc;transform:translateY(-1px);}
|
||||
.action-btn[aria-expanded="true"]{background:var(--accent);color:#fff;border-color:var(--accent);}
|
||||
|
||||
.more{display:none;}
|
||||
|
||||
.modal{position:fixed;inset:0;z-index:1000;display:none;align-items:center;justify-content:center;padding:24px;}
|
||||
.modal.open{display:flex;}
|
||||
.modal-backdrop{position:absolute;inset:0;background:radial-gradient(circle at 30% 18%,rgba(29,155,240,.16),transparent 55%),rgba(17,20,26,.62);backdrop-filter:blur(4px);}
|
||||
.modal-card{
|
||||
position:relative;width:100%;max-width:680px;max-height:84vh;
|
||||
display:flex;flex-direction:column;
|
||||
background:var(--card);border:1px solid rgba(29,155,240,.22);border-radius:20px;
|
||||
box-shadow:0 32px 90px rgba(16,24,40,.5),0 8px 24px rgba(16,24,40,.3);
|
||||
overflow:hidden;animation:modalIn .28s cubic-bezier(.22,1,.36,1);
|
||||
}
|
||||
@keyframes modalIn{from{transform:translateY(24px) scale(.96);opacity:0;}to{transform:none;opacity:1;}}
|
||||
.modal-head{display:flex;flex-direction:column;gap:3px;padding:20px 56px 18px 26px;background:linear-gradient(180deg,#f6fafe,var(--card));border-bottom:1px solid var(--border);}
|
||||
.modal-eyebrow{font-size:10px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--accent);}
|
||||
.modal-title{font-size:19px;font-weight:800;letter-spacing:-.01em;color:var(--text);}
|
||||
.modal-head::after{content:'';display:block;width:44px;height:3px;border-radius:99px;background:var(--accent);margin-top:10px;}
|
||||
.modal-close{
|
||||
position:absolute;top:16px;right:16px;width:34px;height:34px;border-radius:50%;
|
||||
border:1px solid var(--border);background:var(--card);color:var(--muted);
|
||||
font-size:18px;line-height:1;cursor:pointer;z-index:2;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
transition:transform .2s ease,color .15s,border-color .15s,background .15s;
|
||||
}
|
||||
.modal-close:hover{color:var(--accent);border-color:var(--accent);background:#f6fafe;transform:rotate(90deg);}
|
||||
.modal-body{overflow-y:auto;padding:22px 28px 30px;min-height:0;}
|
||||
.modal-body::-webkit-scrollbar{width:10px;}
|
||||
.modal-body::-webkit-scrollbar-track{background:transparent;}
|
||||
.modal-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:99px;border:3px solid var(--card);}
|
||||
.modal-body::-webkit-scrollbar-thumb:hover{background:var(--muted);}
|
||||
.more-inner{font-size:13.5px;color:#2a2f37;line-height:1.7;}
|
||||
.more-inner h4{font-size:15px;margin:20px 0 8px;color:var(--text);font-weight:800;letter-spacing:-.01em;display:flex;align-items:center;gap:10px;}
|
||||
.more-inner h4:first-child{margin-top:2px;font-size:16px;}
|
||||
.more-inner h4::before{content:'';width:4px;height:16px;border-radius:99px;background:var(--accent);flex-shrink:0;}
|
||||
.more-inner p{margin:10px 0;color:#3a4150;}
|
||||
.more-inner ul{margin:10px 0 10px 4px;padding:0;list-style:none;}
|
||||
.more-inner li{margin:8px 0;padding-left:20px;position:relative;color:#3a4150;}
|
||||
.more-inner li::before{content:'';position:absolute;left:2px;top:9px;width:8px;height:8px;border-radius:2px;background:var(--accent);opacity:.35;}
|
||||
.more-inner strong{color:var(--text);font-weight:700;}
|
||||
.more-table{width:100%;border-collapse:separate;border-spacing:0;margin:12px 0;font-size:12.5px;border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
||||
.more-table th,.more-table td{border-bottom:1px solid var(--border);padding:9px 12px;text-align:left;vertical-align:top;}
|
||||
.more-table tr:last-child td{border-bottom:none;}
|
||||
.more-table th{background:#eaf1f8;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#4a5568;}
|
||||
.more-table td{border-right:1px solid var(--border);}
|
||||
.more-table td:last-child{border-right:none;}
|
||||
.more-inner p{margin:8px 0;}
|
||||
.more-inner ul{margin:8px 0 8px 18px;padding:0;}
|
||||
.more-inner li{margin:5px 0;}
|
||||
.more-table{width:100%;border-collapse:collapse;margin:10px 0;font-size:12px;}
|
||||
.more-table th,.more-table td{border:1px solid var(--border);padding:6px 9px;text-align:left;vertical-align:top;}
|
||||
.more-table th{background:#eaf1f8;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#4a5568;}
|
||||
|
||||
.back{width:100%;max-width:680px;text-align:center;margin-top:30px;}
|
||||
.back a{display:inline-block;font-size:14px;font-weight:700;color:var(--accent);border:1px solid var(--border);background:var(--card);padding:10px 20px;border-radius:999px;transition:all .15s;}
|
||||
.back a:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(29,155,240,.15);}
|
||||
|
||||
.no-grav{margin-top:34px;font-size:13px;color:var(--muted);text-align:center;}
|
||||
.no-grav a{color:var(--accent);font-weight:600;}
|
||||
.no-grav a:hover{text-decoration:underline;}
|
||||
|
||||
/* ---- Load entrance (only active while .anim is applied) ---- */
|
||||
.anim [data-anim]{opacity:0;}
|
||||
.anim .rank{will-change:transform,opacity;}
|
||||
.anim .show{will-change:transform,opacity;}
|
||||
|
||||
@media(max-width:560px){
|
||||
body{padding:32px 16px 48px;}
|
||||
.page-title{font-size:24px;}
|
||||
.show{padding:18px;gap:16px;flex-direction:row;align-items:flex-start;}
|
||||
.poster{width:96px;}
|
||||
.poster img{width:96px;}
|
||||
.score{right:-10px;bottom:-10px;width:48px;height:48px;font-size:14px;}
|
||||
.show-title{font-size:18px;}
|
||||
.rank{width:40px;height:40px;font-size:17px;}
|
||||
}
|
||||
@media(max-width:420px){
|
||||
.show{flex-direction:column;align-items:flex-start;}
|
||||
.poster{width:120px;}
|
||||
.poster img{width:120px;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas" aria-hidden="true"></canvas>
|
||||
|
||||
<div class="page-head">
|
||||
<div class="crumbs"><a href="share.html">↩ miles wolf allen</a></div>
|
||||
<div class="page-title" data-anim="txt">top shows</div>
|
||||
<div class="page-sub" data-anim="txt">my current top 4 shows, ranked & scored</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="show" data-anim="show">
|
||||
<div class="poster" data-anim="poster">
|
||||
<img src="https://image.tmdb.org/t/p/w400/fHpKWq9ayzSk8nSwqRuaAUemRKh.jpg" alt="Jujutsu Kaisen poster" loading="lazy">
|
||||
<span class="score s1"><span>10</span><span class="of">/10</span></span>
|
||||
</div>
|
||||
<div class="rank r1" data-anim="rank">1</div>
|
||||
<div class="show-body">
|
||||
<div class="show-title">Jujutsu Kaisen</div>
|
||||
<div class="show-type">anime · action · dark fantasy</div>
|
||||
<div class="chips">
|
||||
<span class="chip">Studio MAPPA</span>
|
||||
<span class="chip">2020–present</span>
|
||||
<span class="chip">action</span>
|
||||
<span class="chip">supernatural</span>
|
||||
</div>
|
||||
<p class="synopsis">After swallowing the rotting finger of the ancient curse Sukuna, ordinary high schooler Yuji Itadori becomes the vessel for one of the most dangerous spirits in existence. Thrown into the hidden world of jujutsu sorcerers, he joins Tokyo Jujutsu High to exorcise curses — and train until he can destroy Sukuna from within.</p>
|
||||
<p class="why"><b>Why it's #1:</b> unmatched fight choreography, gorgeous animation, a genuinely intense atmosphere.do i need to say more?</p>
|
||||
<div class="show-actions">
|
||||
<button class="action-btn" data-target="more-jjk" data-title="Jujutsu Kaisen" aria-expanded="false">show more info</button>
|
||||
</div>
|
||||
<div class="more" id="more-jjk">
|
||||
<div class="more-inner">
|
||||
<h4>Why Jujutsu Kaisen Earns the #1 Spot: A Deep Dive</h4>
|
||||
<p>Jujutsu Kaisen (JJK) has taken the global anime landscape by storm, establishing itself as the definitive modern peak of modern <em>shonen</em> and dark fantasy action. While the shonen genre is densely populated with tropes and familiar structures, JJK sets itself apart through a total dedication to technical combat mastery, cinematic visual direction, and a uniquely unforgiving atmosphere. Below is an in-depth breakdown of why it secures the undisputed #1 spot.</p>
|
||||
<h4>1. Unmatched Fight Choreography</h4>
|
||||
<p>Action in Jujutsu Kaisen is never just a vehicle to show off flashy power-ups; it is built on intricate, tactical, and grounded combat design. Every encounter functions almost like a high-speed chess match executed with brutal martial arts precision.</p>
|
||||
<ul>
|
||||
<li><strong>Tactical Depth & Hand-to-Hand Precision:</strong> The series frequently highlights close-quarters martial arts—such as Taekwondo, boxing, and classical martial arts—blended seamlessly with supernatural mechanics like Black Flash timing, Cursed Technique Reversal, and domain clashes. Combatants must continually assess distance, momentum, and body mechanics.</li>
|
||||
<li><strong>Hard Magic System Mechanics:</strong> The power system relies heavily on strict rules, such as Binding Vows (sacrificing one advantage to gain another) and revealing one's technique to boost its efficiency. This prevents victory from feeling unearned or powered purely by emotional outbursts.</li>
|
||||
<li><strong>Masterful Tag-Team Synergy:</strong> Fights involving multiple characters—most notably Yuji Itadori pairing with Aoi Todo or Kento Nanami—are widely regarded as gold standards in action direction. The fluid swaps, spatial manipulation, and rhythmic timing turn multi-person brawls into hypnotic displays of strategic teamwork.</li>
|
||||
</ul>
|
||||
<h4>2. Gorgeous Animation and Visual Direction</h4>
|
||||
<p>Brought to life by Studio MAPPA, Jujutsu Kaisen consistently pushes the boundaries of contemporary anime production, delivering feature-film quality visuals throughout its broadcast runs.</p>
|
||||
<table class="more-table">
|
||||
<thead><tr><th>Visual Aspect</th><th>Technical Execution</th><th>Impact on Viewer Experience</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Kinetic Camera Movement</td><td>Dynamic virtual camera tracking shot through 3D environments.</td><td>Creates an immersive sense of speed, weight, and visceral scale during combat.</td></tr>
|
||||
<tr><td>Expressive Sakuga</td><td>Shifting between sharp detail and loose, fluid keyframe animation.</td><td>Conveys raw impact, velocity, and emotional distortion during climax moments.</td></tr>
|
||||
<tr><td>VFX & Domain Expansions</td><td>Distinct visual palettes for Cursed Energy (e.g., Gojo's Limitless or Sukuna's Malevolent Shrine).</td><td>Provides breathtaking visual spectacle that transforms the arena into a living work of art.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>3. A Genuinely Intense, High-Stakes Atmosphere</h4>
|
||||
<p>Perhaps the most defining element of Jujutsu Kaisen is its willingness to abandon traditional shonen safety nets, creating a perpetual sense of danger where no character is truly safe.</p>
|
||||
<ul>
|
||||
<li><strong>Refusal of Plot Armor:</strong> Major protagonists experience severe physical trauma, permanent maiming, or sudden, tragic deaths. Victories are often costly and bittersweet, keeping audience anxiety high during every encounter.</li>
|
||||
<li><strong>Horror Aesthetics & Dark Fantasy:</strong> Drawing inspiration from classic horror and occult folklore, Cursed Spirits represent visceral human fears. The grotesque character designs, unsettling domain environments, and body-horror transformations contrast heavily with standard action anime tropes.</li>
|
||||
<li><strong>Unpredictable Narrative Pacing:</strong> Key story arcs, most notably the Shibuya Incident, completely dismantle the status quo without warning. The story moves at a blistering pace, refusing to rely on excessive filler or prolonged exposition.</li>
|
||||
</ul>
|
||||
<p><strong>Conclusion: Do I Need to Say More?</strong> When an anime combines revolutionary fight choreography, jaw-dropping visual artistry, and a truly unpredictable, high-stakes narrative atmosphere, it transcends standard entertainment. Jujutsu Kaisen doesn't just top the charts—it redefines what modern action anime can achieve. Do you really need to say more?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="show" data-anim="show">
|
||||
<div class="poster" data-anim="poster">
|
||||
<img src="https://image.tmdb.org/t/p/w400/xbZQ3fDl0y5mt0ARwfeyrgQ4JTw.jpg" alt="Dr. Stone poster" loading="lazy">
|
||||
<span class="score s2"><span>8.5</span><span class="of">/10</span></span>
|
||||
</div>
|
||||
<div class="rank r2" data-anim="rank">2</div>
|
||||
<div class="show-body">
|
||||
<div class="show-title">Dr. Stone</div>
|
||||
<div class="show-type">anime · adventure · sci-fi</div>
|
||||
<div class="chips">
|
||||
<span class="chip">Studio TMS / 8-PAN</span>
|
||||
<span class="chip">2019–present</span>
|
||||
<span class="chip">science</span>
|
||||
<span class="chip">comedy</span>
|
||||
</div>
|
||||
<p class="synopsis">In the blink of an eye, all of humanity is turned to stone — and stays that way for 3,700 years. When genius high schooler Senku Ishigami finally awakens, he finds civilization in ruins and sets out to rebuild it from scratch, one scientific breakthrough at a time: stone tools, gunpowder, electricity, and beyond.</p>
|
||||
<p class="why"><b>Why it's #2:</b> it makes real science feel like a superpower, with clever problem-solving and a fun cast that keeps the "step by step" progression endlessly satisfying. but if you do not like science, it might not be as engaging and boring to watch.</p>
|
||||
<div class="show-actions">
|
||||
<button class="action-btn" data-target="more-dst" data-title="Dr. Stone" aria-expanded="false">show more info</button>
|
||||
<a class="action-btn" href="dr-stone.html">full episode breakdown →</a>
|
||||
</div>
|
||||
<div class="more" id="more-dst">
|
||||
<div class="more-inner">
|
||||
<h4>The Dr. Stone Divide: Why a Science Fiction Phenomenon Polarizes Audiences</h4>
|
||||
<p>Dr. Stone stands out as one of the most unique Shonen properties of the modern era. By replacing magical combat and power-scaling with the scientific method and civil engineering, it gamified the history of human progress. However, while it has garnered a fiercely dedicated fanbase, it also leaves a specific segment of anime and manga consumers entirely cold. This report analyzes the core elements that drive the show's intense resonance with some, and the friction points that alienate others.</p>
|
||||
<h4>1. Why It Resonates: The Core Appeal</h4>
|
||||
<p><strong>The "Civ-Building" Hook.</strong> At its core, Dr. Stone taps into the exact psychological loop that makes sandbox and strategy games like Minecraft or Civilization so addictive. Viewers are treated to a rapid, highly satisfying progression from primitive stone tools to electricity, antibiotics, and telecommunications. The joy lies in seeing systemic progression—watching characters use baseline resources to climb the tech tree.</p>
|
||||
<p><strong>A Unique Shonen Protagonist.</strong> Senku Ishigami is a breath of fresh air in a genre dominated by hot-headed, physically powerful protagonists. Senku's "power" is his encyclopedic knowledge and unshakeable rationality. However, he subverts the "cold, calculating genius" trope by being deeply compassionate; his ultimate goal is to save every single human being without exception.</p>
|
||||
<p><strong>Infotainment and Edutainment.</strong> Dr. Stone effectively weaponizes education. Real-world chemistry, physics, and engineering are used to solve life-or-death problems. The "Roadmap" sequences—where Senku maps out the steps to build complex items like a smartphone or a tank—make learning incredibly engaging. It is a love letter to human ingenuity.</p>
|
||||
<p><strong>Unbridled Optimism.</strong> Post-apocalyptic settings are usually grim, desperate, and cynical. Dr. Stone is overwhelmingly optimistic. It argues that humanity is inherently good, that cooperation triumphs over division, and that science is a unifying force.</p>
|
||||
<h4>2. Why It Alienates: The Points of Friction</h4>
|
||||
<p><strong>The "Science" vs. "Realism" Problem.</strong> While the theories behind the science in the show are accurate, the execution heavily relies on narrative convenience. The speed at which resources are found, refined, and assembled by a handful of teenagers with primitive tools shatters the suspension of disbelief for many viewers (e.g., Senku accurately counting the seconds for 3,700 years while petrified).</p>
|
||||
<p><strong>Lack of True Stakes and Tension.</strong> The petrification beam, a central plot device, effectively acts as a magical healing potion. Because the "revival fluid" can cure mortal wounds and even resurrect the recently deceased (as long as they are petrified first), the threat of death is virtually eliminated. For viewers who need high stakes and dramatic tension to stay invested, the story quickly loses its edge.</p>
|
||||
<p><strong>Pacing and Character Bloat.</strong> As the kingdom expands, so does the cast. Early fan-favorite characters are increasingly sidelined as the story introduces highly specialized (and often one-note) characters to solve specific tech-tree problems. Furthermore, in the latter half of the story, the pacing accelerates wildly, skipping the meticulous building phases that made the early arcs so rewarding.</p>
|
||||
<p><strong>Polarizing Art Direction.</strong> Artist Boichi is a master at drawing landscapes, machinery, and expressive male characters. However, his designs for female characters are intensely polarizing. Their highly exaggerated proportions and unusually wide-set eyes often draw criticism and can be off-putting to new viewers, creating a barrier to entry.</p>
|
||||
<p><strong>Tonal Whiplash.</strong> The series frequently swings between life-or-death drama and exaggerated, goofy comedy featuring "chibi" character reactions. For those looking for a grounded survival story, this meme-heavy, slapstick humor can feel grating and childish.</p>
|
||||
<h4>Conclusion</h4>
|
||||
<p>Dr. Stone is a polarizing masterpiece. If a viewer approaches it as an optimistic, gamified celebration of human history where science is treated like magic, it is an incredibly rewarding experience. However, if a viewer approaches it expecting a gritty, realistic survival drama with intense character stakes, the series will inevitably feel absurd and emotionally hollow. Its greatest strengths—its rapid pacing and unshakeable optimism—are the exact same traits that drive its detractors away.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="show" data-anim="show">
|
||||
<div class="poster" data-anim="poster">
|
||||
<img src="https://image.tmdb.org/t/p/w400/99PJSbcO2LeM10uOGWeFihNp77j.jpg" alt="Battlestar Galactica poster" loading="lazy">
|
||||
<span class="score s3"><span>8</span><span class="of">/10</span></span>
|
||||
</div>
|
||||
<div class="rank r3" data-anim="rank">3</div>
|
||||
<div class="show-body">
|
||||
<div class="show-title">Battlestar Galactica</div>
|
||||
<div class="show-type">tv series · sci-fi · drama</div>
|
||||
<div class="chips">
|
||||
<span class="chip">Syfy</span>
|
||||
<span class="chip">2004–2009</span>
|
||||
<span class="chip">sci-fi</span>
|
||||
<span class="chip">drama</span>
|
||||
</div>
|
||||
<p class="synopsis">When the Cylons return and obliterate the twelve colonies, the aging warship Galactica leads the last remnants of humanity on a desperate journey toward the fabled thirteenth colony, Earth — hunted the whole way by machines that look indistinguishable from people.</p>
|
||||
<p class="why"><b>Why it's #3:</b> it's really good until about season three and four, where it starts to become kind of a slog. but it is still great.</p>
|
||||
<div class="show-actions">
|
||||
<button class="action-btn" data-target="more-bsg" data-title="Battlestar Galactica" aria-expanded="false">show more info</button>
|
||||
<a class="action-btn" href="bsg.html">full episode breakdown →</a>
|
||||
</div>
|
||||
<div class="more" id="more-bsg">
|
||||
<div class="more-inner">
|
||||
<h4>Why People Like Battlestar Galactica (#3): An Expanded Analysis</h4>
|
||||
<p>The 2004 reimagining of Battlestar Galactica (BSG), developed by Ronald D. Moore, stands as a landmark achievement in modern television. By blending military realism, intense political drama, and psychological survival, it fundamentally reshaped how science fiction could be told on television. While it secures an elite #3 spot among legendary sci-fi series, its legacy is defined by both breathtaking early brilliance and a noticeably slower late-series transition.</p>
|
||||
<h4>1. Peak Sci-Fi Television (Seasons 1 & 2)</h4>
|
||||
<p>The opening two seasons of Battlestar Galactica represent some of the tightest, most harrowing television ever produced, establishing a gold standard for serialized storytelling.</p>
|
||||
<ul>
|
||||
<li><strong>Paranoia and Suspense:</strong> The introduction of humanoid Cylons created an atmosphere of absolute paranoia. The horrifying reality that anyone—from a low-ranking deckhand to a high-ranking officer—could be a sleeping enemy agent maintained unbearable tension.</li>
|
||||
<li><strong>Gritty Documentary Realism:</strong> Utilizing hand-held camera work, muted color palettes, and Newtonian space physics, BSG discarded the bright, clean aesthetic of classic sci-fi. Resources like water, ammunition, and fuel were finite and tracked constantly.</li>
|
||||
<li><strong>Compelling Moral & Political Allegories:</strong> Airing during the mid-2000s, the series fearlessly tackled heavy real-world themes, including military vs. civilian governance, religious fanaticism, terrorism, civil liberty erosion, and military coups.</li>
|
||||
</ul>
|
||||
<h4>2. The Late-Series Shift: Becoming a Slog (Seasons 3 & 4)</h4>
|
||||
<p>Despite its explosive start, the series undergoes a profound thematic and structural shift during its second half that noticeably alters the viewing experience.</p>
|
||||
<table class="more-table">
|
||||
<thead><tr><th>Narrative Phase</th><th>Core Focus</th><th>Pacing & Audience Perception</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Seasons 1–2</td><td>Tactical military survival, political intrigue, and immediate crisis management.</td><td>Fast-paced, hyper-focused, and relentlessly intense.</td></tr>
|
||||
<tr><td>Seasons 3–4</td><td>Prophecy, ancient religious lore, existential mystery, and spiritual destiny.</td><td>Slower, more philosophical, and occasionally feels like a narrative slog.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul>
|
||||
<li><strong>Pacing De-acceleration:</strong> As the immediate threat of annihilation shifts toward finding Earth, the tight episode-to-episode momentum slows down considerably into long, contemplative character arcs.</li>
|
||||
<li><strong>Heavy Focus on Mysticism over Tactical Realism:</strong> The tactical and political grounding that drew in early viewers was increasingly replaced by prophecies, angelic visions, and religious destiny, which alienated fans who preferred hard military sci-fi.</li>
|
||||
<li><strong>Emotional Trauma & Fatigue:</strong> The characters endure unending grief, betrayal, and mental decay. By Seasons 3 and 4, this persistent despair makes binge-watching feel heavy and emotionally draining.</li>
|
||||
</ul>
|
||||
<h4>3. Why It Remains Great Despite the Slowdown</h4>
|
||||
<p>Even with its late-season pacing hurdles and polarized finale, Battlestar Galactica firmly retains its #3 standing due to its foundational strengths and lasting influence.</p>
|
||||
<ul>
|
||||
<li><strong>Flawed, Iconic Characters:</strong> Figures like Commander William Adama, President Laura Roslin, Gaius Baltar, and Kara "Starbuck" Thrace remain among the most deeply human, morally grey, and beautifully acted characters in screen history.</li>
|
||||
<li><strong>Bear McCreary's Legendary Score:</strong> The musical score—utilizing heavy percussion, orchestral movements, and global instrumental influences—elevates every emotional and action beat, providing a rich cultural backdrop to the human fleet.</li>
|
||||
<li><strong>Structural Legacy:</strong> BSG paved the way for prestige, serialized genre television (such as The Expanse, Game of Thrones, and Andor), proving that sci-fi could tackle prestige adult drama without holding back.</li>
|
||||
</ul>
|
||||
<h4>Conclusion</h4>
|
||||
<p>While Seasons 3 and 4 do become kind of a slog compared to the blistering speed of its beginning, Battlestar Galactica easily holds down its #3 spot. Its peak moments are virtually unmatched, cementing it as an undeniable classic of television history.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="show" data-anim="show">
|
||||
<div class="poster" data-anim="poster">
|
||||
<img src="https://image.tmdb.org/t/p/w400/4tblBrslcKSifMVZ3TmtT2ukMor.jpg" alt="Invincible poster" loading="lazy">
|
||||
<span class="score s4"><span>7.5</span><span class="of">/10</span></span>
|
||||
</div>
|
||||
<div class="rank r4" data-anim="rank">4</div>
|
||||
<div class="show-body">
|
||||
<div class="show-title">Invincible</div>
|
||||
<div class="show-type">animated series · superhero · drama</div>
|
||||
<div class="chips">
|
||||
<span class="chip">Amazon Prime Video</span>
|
||||
<span class="chip">2021–present</span>
|
||||
<span class="chip">superhero</span>
|
||||
<span class="chip">action-drama</span>
|
||||
</div>
|
||||
<p class="synopsis">Mark Grayson is just a regular teenager — until he inherits superpowers from his father, Omni-Man, Earth's greatest hero. As the rookie hero Invincible, he learns that being the son of a legend is nowhere near as simple as it looks when a dark family secret threatens everything he thought he knew.</p>
|
||||
<p class="why"><b>Why it's #4:</b> it's number four because of the way it toys around with superhero ideas in flipping them around, truly having complex characters that can actually change, it does the cliché of the evil superhero very well and really shows itself off even on the first episode alone.</p>
|
||||
<div class="show-actions">
|
||||
<button class="action-btn" data-target="more-inv" data-title="Invincible" aria-expanded="false">show more info</button>
|
||||
</div>
|
||||
<div class="more" id="more-inv">
|
||||
<div class="more-inner">
|
||||
<h4>Why Invincible Earns the #4 Spot</h4>
|
||||
<p>Among modern animated adaptations and superhero sagas, Invincible stands out as a triumph of storytelling. By combining classic comic book nostalgia with visceral realism, moral complexity, and subverted expectations, the series redefines what a superhero story can accomplish. Below is a detailed breakdown of why it comfortably claims the #4 spot.</p>
|
||||
<h4>1. Subverting and Flipping Superhero Tropes</h4>
|
||||
<p>On the surface, Invincible presents a familiar superhero world populated by archetypes reminiscent of iconic comic book leagues. However, it constantly subverts these foundational ideas:</p>
|
||||
<ul>
|
||||
<li><strong>Deconstructing Silver Age Nostalgia:</strong> It takes well-known comic book tropes—teen heroes juggling high school, secret identities, and cosmic threats—and grounds them in brutal reality.</li>
|
||||
<li><strong>Consequences to Collateral Damage:</strong> In traditional superhero stories, city-wide battles often reset by the next issue. In Invincible, destruction leaves permanent scars on the world, its infrastructure, and the civilian population.</li>
|
||||
<li><strong>Flipping Expectation:</strong> What starts as an optimistic coming-of-age story quickly dismantles the safety of the genre, forcing heroes to confront impossible systemic choices.</li>
|
||||
</ul>
|
||||
<h4>2. Complex Characters Who Actually Change</h4>
|
||||
<p>One of the strongest elements of Invincible is its refusal to keep characters trapped in a status-quo loop. Character growth is permanent, painful, and earned.</p>
|
||||
<table class="more-table">
|
||||
<thead><tr><th>Character</th><th>Core Conflict</th><th>Nature of Character Evolution</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Mark Grayson (Invincible)</td><td>Balancing human empathy with overwhelming alien lineage.</td><td>Evolves from an idealistic rookie into a battle-hardened hero who must make devastating compromises.</td></tr>
|
||||
<tr><td>Nolan Grayson (Omni-Man)</td><td>Duty to the Viltrum Empire vs. genuine love for his human family.</td><td>Shifts from an unyielding conqueror to a deeply conflicted figure tormented by his own humanity.</td></tr>
|
||||
<tr><td>Samantha Eve Wilkins (Atom Eve)</td><td>Choosing self-determination over heroic expectations and toxic family dynamics.</td><td>Transitions from a standard team hero to an independent force using her powers for humanitarian aid.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>3. Mastering the "Evil Superhero" Cliché</h4>
|
||||
<p>The "evil Superman" archetype has become a common narrative trope in modern media, but Invincible executes it far better than most by providing emotional weight and genuine complexity:</p>
|
||||
<ul>
|
||||
<li><strong>Grounded Ideological Conflict:</strong> Omni-Man isn't evil simply for the sake of destruction or vanity. His motivations stem from centuries of imperial indoctrination, making his actions cold, calculated, and ideologically driven.</li>
|
||||
<li><strong>Tragic Emotional Stakes:</strong> The tragedy of Nolan's betrayal hits deeply because his love for Mark and Debbie was real, creating a visceral friction between his mission and his emotions.</li>
|
||||
<li><strong>Long-Term Narrative Ripple Effects:</strong> The fallout of the "evil superhero" isn't resolved in a single fight; it reshapes geopolitical alliances, alien perceptions, and Mark's entire personal philosophy.</li>
|
||||
</ul>
|
||||
<h4>4. A Masterclass Opening Episode</h4>
|
||||
<p>Few shows establish their tone and promise as effectively as Invincible does in its very first episode:</p>
|
||||
<ul>
|
||||
<li><strong>The Ultimate Hook:</strong> After spending nearly an hour building up a bright, classic superhero landscape, the final minutes deliver a shockingly violent, narrative-defining twist that changes everything.</li>
|
||||
<li><strong>Immediate Escalation:</strong> By shattering the status quo in Episode 1, the show signals to the audience that no one is safe, actions have brutal consequences, and the stakes will only continue to rise.</li>
|
||||
<li><strong>Showing Off Its Full Potential:</strong> It proves immediately that it isn't just another cartoon superhero show, but a high-stakes, mature drama capable of pulling no punches.</li>
|
||||
</ul>
|
||||
<h4>Conclusion</h4>
|
||||
<p>By fearlessly playing with superhero conventions, delivering genuine character evolution, elevating the evil superhero trope, and hooking viewers from episode one, Invincible earns its place at #4 as an absolute modern classic.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="back" data-anim="fade">
|
||||
<a href="share.html">← back to my profile</a>
|
||||
</div>
|
||||
|
||||
<div class="no-grav" data-anim="fade">made by <a href="index.html">miles</a>. not ai</div>
|
||||
|
||||
<div class="modal" id="moreModal" aria-hidden="true">
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card" role="dialog" aria-modal="true" aria-label="More info">
|
||||
<div class="modal-head">
|
||||
<div class="modal-eyebrow">why i like it</div>
|
||||
<div class="modal-title" id="moreModalTitle">More info</div>
|
||||
</div>
|
||||
<button class="modal-close" data-close aria-label="Close">×</button>
|
||||
<div class="modal-body" id="moreModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
|
||||
// ---- Ambient aurora + particle background ----
|
||||
(function bg(){
|
||||
var canvas = document.getElementById('bgCanvas');
|
||||
if(!canvas) return;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var orbs = [];
|
||||
var parts = [];
|
||||
var COLORS = [
|
||||
[29,155,240], // blue
|
||||
[255,61,154], // pink
|
||||
[34,211,153], // mint
|
||||
[168,85,247] // purple
|
||||
];
|
||||
function resize(){
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
seed();
|
||||
}
|
||||
function seed(){
|
||||
orbs = []; parts = [];
|
||||
var w = canvas.width, h = canvas.height;
|
||||
var oCount = Math.min(7, Math.round(w / 200) + 3);
|
||||
for(var i=0;i<oCount;i++){
|
||||
orbs.push({
|
||||
x: Math.random()*w, y: Math.random()*h,
|
||||
r: 110 + Math.random()*220,
|
||||
c: COLORS[i % COLORS.length],
|
||||
sp: 0.10 + Math.random()*0.22,
|
||||
a: 0.14 + Math.random()*0.12,
|
||||
ox: Math.random()*1000, oy: Math.random()*1000
|
||||
});
|
||||
}
|
||||
var pCount = Math.min(80, Math.round(w*h/18000));
|
||||
for(var j=0;j<pCount;j++){
|
||||
parts.push({
|
||||
x: Math.random()*w, y: Math.random()*h,
|
||||
r: 0.6 + Math.random()*1.6,
|
||||
sp: 0.05 + Math.random()*0.25,
|
||||
ph: Math.random()*Math.PI*2, oy: Math.random()*100
|
||||
});
|
||||
}
|
||||
}
|
||||
var t = 0;
|
||||
function frame(){
|
||||
requestAnimationFrame(frame);
|
||||
if(reduced) return;
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
t += 0.008;
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
for(var i=0;i<orbs.length;i++){
|
||||
var o = orbs[i];
|
||||
var nx = o.x + Math.sin(t*o.sp + o.ox) * (o.r*0.9);
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.oy) * (o.r*0.7) * 0.8;
|
||||
var g = ctx.createRadialGradient(nx,ny,0,nx,ny,o.r);
|
||||
g.addColorStop(0, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+','+o.a+')');
|
||||
g.addColorStop(1, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+',0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(nx,ny,o.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.fillStyle = 'rgba(40,50,70,0.35)';
|
||||
for(var p=0;p<parts.length;p++){
|
||||
var pt = parts[p];
|
||||
var px = pt.x + Math.sin(t*pt.sp + pt.ph) * 40;
|
||||
var py = pt.y + Math.cos(t*pt.sp*0.7 + pt.ph*1.7 + pt.oy) * 26;
|
||||
var tw = 0.4 + 0.6*Math.abs(Math.sin(t*0.6 + pt.ph));
|
||||
ctx.globalAlpha = tw*0.6;
|
||||
ctx.beginPath(); ctx.arc(px%canvas.width, py%canvas.height, pt.r, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize(); frame();
|
||||
})();
|
||||
|
||||
// ---- Choreographed load entrance (Anime.js) ----
|
||||
(function load(){
|
||||
var body = document.body;
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
body.classList.add('anim');
|
||||
var done = function(){ body.classList.remove('anim'); };
|
||||
if (!window.anime || reduced) { done(); return; }
|
||||
var tl = anime.timeline({ complete: done });
|
||||
tl
|
||||
.add({ targets:'[data-anim="txt"]', translateY:[26,0], opacity:[0,1],
|
||||
duration:560, delay:anime.stagger(90), easing:'easeOutExpo' })
|
||||
.add({ targets:'[data-anim="rank"]', scale:[0.3,1.08,1], rotate:[-12,3,0], opacity:[0,1],
|
||||
duration:780, delay:anime.stagger(70), easing:'easeOutElastic(1,0.65)', offset:'-=260' })
|
||||
.add({ targets:'[data-anim="show"]', translateY:[40,0], opacity:[0,1],
|
||||
duration:600, delay:anime.stagger(120), easing:'easeOutQuint', offset:0 })
|
||||
.add({ targets:'[data-anim="poster"]', scale:[0.7,1], rotate:[-3,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(120), easing:'easeOutBack(1.6)', offset:'-=500' })
|
||||
.add({ targets:'[data-anim="fade"]', translateY:[10,0], opacity:[0,1],
|
||||
duration:420, easing:'easeOutQuart', offset:'-=120' });
|
||||
})();
|
||||
|
||||
document.querySelectorAll('.action-btn[data-target]').forEach(function(btn){
|
||||
btn.addEventListener('click', function(){
|
||||
var target = document.getElementById(btn.getAttribute('data-target'));
|
||||
if(!target) return;
|
||||
openModal(target, btn.getAttribute('data-title') || '');
|
||||
});
|
||||
});
|
||||
|
||||
var modal = document.getElementById('moreModal');
|
||||
var modalBody = document.getElementById('moreModalBody');
|
||||
var modalTitle = document.getElementById('moreModalTitle');
|
||||
function openModal(source, title){
|
||||
if(!modal || !modalBody) return;
|
||||
if(modalTitle) modalTitle.textContent = title || 'More info';
|
||||
modalBody.innerHTML = source.innerHTML;
|
||||
modal.classList.add('open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
modalBody.scrollTop = 0;
|
||||
}
|
||||
function closeModal(){
|
||||
if(!modal) return;
|
||||
modal.classList.remove('open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
modalBody.innerHTML = '';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
modal.querySelectorAll('[data-close]').forEach(function(el){
|
||||
el.addEventListener('click', closeModal);
|
||||
});
|
||||
document.addEventListener('keydown', function(e){
|
||||
if(e.key === 'Escape' && modal.classList.contains('open')) closeModal();
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user