mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
Compare commits
6
Commits
b053be846d
...
e3611c053c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3611c053c | ||
|
|
d27792b813 | ||
|
|
d8a9d74912 | ||
|
|
3d731ebd35 | ||
|
|
9be085d251 | ||
|
|
295d2c51a7 |
@@ -0,0 +1 @@
|
||||
share-server/node_modules
|
||||
+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>
|
||||
@@ -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>
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
<!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"
|
||||
]
|
||||
}
|
||||
</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">
|
||||
<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;}
|
||||
|
||||
@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;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas" aria-hidden="true"></canvas>
|
||||
|
||||
<div class="card">
|
||||
<div class="banner">
|
||||
<img src="back.jpg" alt="" aria-hidden="true">
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<img class="avatar" src="https://avatars.githubusercontent.com/u/115095719?v=4" alt="miles wolf allen" width="120" height="120">
|
||||
<div class="name">miles wolf allen</div>
|
||||
<div class="handle">@wolf_reaper90 · software engineer</div>
|
||||
<div class="meta">he/him · <b>MN, USA</b> · <span id="tzWatch">local time --:--:--</span></div>
|
||||
|
||||
<p class="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" 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" 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" 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>
|
||||
<a class="soc" href="https://www.youtube.com/channel/UCeUJbFoCDwUUg6uZi-G-zhQ" target="_blank" rel="noopener" aria-label="YouTube">
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="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://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">
|
||||
<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="no-grav">made by <a href="index.html">miles</a>. not ai </div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
|
||||
// ---- Ambient drifting orbs 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 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 = [];
|
||||
var count = Math.min(6, Math.round(canvas.width / 220) + 3);
|
||||
for(var i=0;i<count;i++){
|
||||
orbs.push({
|
||||
x: Math.random()*canvas.width,
|
||||
y: Math.random()*canvas.height,
|
||||
r: 80 + Math.random()*160,
|
||||
c: COLORS[i % COLORS.length],
|
||||
sp: 0.15 + Math.random()*0.25,
|
||||
a: 0.12 + Math.random()*0.10,
|
||||
seed: Math.random()*1000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var t = 0;
|
||||
function frame(){
|
||||
requestAnimationFrame(frame);
|
||||
if(reduced) return;
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
t += 0.005;
|
||||
for(var i=0;i<orbs.length;i++){
|
||||
var o = orbs[i];
|
||||
var nx = o.x + Math.sin(t*o.sp + o.seed) * 90;
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.seed*1.3) * 70;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -144,6 +144,12 @@
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user