mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
Compare commits
2
Commits
e3611c053c
...
eeeac7c4f7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeeac7c4f7 | ||
|
|
43ccb48009 |
+203
@@ -0,0 +1,203 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CommandMove: Cut & Paste on Mac | Miles Wolf Allen</title>
|
||||
<meta name="description" content="How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.">
|
||||
<link rel="canonical" href="https://milesallen.site/blog/command-move-devlog.html">
|
||||
<meta property="og:title" content="CommandMove: Cut & Paste on Mac | Miles Wolf Allen">
|
||||
<meta property="og:description" content="How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.">
|
||||
<meta property="og:image" content="https://milesallen.site/og.png">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="article:published_time" content="2026-02-15">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"headline": "CommandMove: Cut & Paste on Mac | Miles Wolf Allen",
|
||||
"description": "How I added true cut-and-paste to macOS Finder with CommandMove — global event taps, AppleScript, and Accessibility permissions.",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Miles Wolf Allen",
|
||||
"url": "https://milesallen.site/"
|
||||
},
|
||||
"datePublished": "2026-02-15",
|
||||
"url": "https://milesallen.site/blog/command-move-devlog.html",
|
||||
"mainEntityOfPage": "https://milesallen.site/blog/command-move-devlog.html"
|
||||
}
|
||||
</script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Space+Mono:wght@400;700&family=Inter:wght@400;500;600;800&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../projects/style.css">
|
||||
<style>
|
||||
.article-header{padding:100px 0 40px;}
|
||||
.article-eyebrow{font-family:var(--font-pixel);font-size:10px;color:var(--tape);margin-bottom:12px;text-transform:uppercase;letter-spacing:.1em;}
|
||||
.article-date{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-bottom:8px;}
|
||||
.article-header h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,44px);line-height:1.2;margin-bottom:16px;transform:rotate(-1deg);transform-origin:left center;text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);animation:glitch 5s ease-in-out infinite;}
|
||||
@keyframes glitch{0%,90%,100%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--tape);}92%{text-shadow:3px 3px 0 var(--red),-2px -2px 0 var(--accent);transform:rotate(-1deg) translateX(2px);}94%{text-shadow:3px 3px 0 var(--accent),-2px -2px 0 var(--red);transform:rotate(-1deg) translateX(-2px);}}
|
||||
.article-tagline{font-size:16px;color:var(--muted);max-width:600px;line-height:1.7;font-family:var(--font-mono);margin-bottom:24px;}
|
||||
.article-tags{display:flex;gap:8px;flex-wrap:wrap;}
|
||||
.article-tag{font-family:var(--font-pixel);font-size:8px;padding:6px 12px;background:var(--accent);color:#000;text-transform:uppercase;}
|
||||
.article-body{max-width:720px;}
|
||||
.article-body h2{font-family:var(--font-pixel);font-size:16px;color:var(--accent);margin:40px 0 16px;text-transform:uppercase;}
|
||||
.article-body h3{font-family:var(--font-mono);font-weight:700;font-size:16px;margin:32px 0 12px;color:var(--text);}
|
||||
.article-body p{font-size:14px;color:#ccc;line-height:1.9;font-family:var(--font-mono);margin-bottom:16px;}
|
||||
.article-body code{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);padding:2px 6px;font-size:12px;font-family:var(--font-mono);}
|
||||
.article-body blockquote{border-left:3px solid var(--accent);padding:12px 20px;margin:24px 0;background:rgba(255,255,255,0.02);font-style:italic;color:var(--muted);font-family:var(--font-mono);font-size:13px;}
|
||||
.article-body ul{margin:12px 0 16px 20px;font-family:var(--font-mono);font-size:13px;color:#ccc;line-height:1.8;}
|
||||
.article-body ul li{margin-bottom:6px;}
|
||||
.article-body ul li::marker{color:var(--accent);}
|
||||
.back-link{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:40px;text-decoration:none;transition:color .2s;}
|
||||
.back-link:hover{color:var(--accent);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot" id="cursorDot"></div>
|
||||
<div class="cursor-ring" id="cursorRing"></div>
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
|
||||
<div class="shortcut-modal" id="shortcutModal">
|
||||
<div class="shortcut-card">
|
||||
<h3>Keyboard Shortcuts</h3>
|
||||
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
|
||||
<div class="shortcut-row"><span>Go to GitHub</span><kbd>G</kbd></div>
|
||||
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
|
||||
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
|
||||
<div class="close-hint">Press Esc or ? to close</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flash" id="flash"></div>
|
||||
<div class="toast-stack" id="toastStack"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="../index.html">Home</a>
|
||||
<a href="fedl.html">FEDL</a>
|
||||
<a href="omniemu.html">OmniEmu</a>
|
||||
<a href="command-move.html">CommandMove</a>
|
||||
<a href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<nav>
|
||||
<a href="../index.html" class="nav-logo"><span class="logo-dot"></span> milesallen</a>
|
||||
<div class="nav-links">
|
||||
<a href="../index.html#about">About</a>
|
||||
<a href="../index.html#skills">Skills</a>
|
||||
<a href="../index.html#projects">Projects</a>
|
||||
<a href="../index.html#contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="article-header">
|
||||
<div class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<a href="../index.html#blog">Devlog</a>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||||
<span>CommandMove</span>
|
||||
</div>
|
||||
<div class="article-eyebrow">// DEVLOG 003</div>
|
||||
<div class="article-date">2026</div>
|
||||
<h1>CommandMove: Cut & Paste on Mac</h1>
|
||||
<p class="article-tagline">macOS doesn't have Cmd+X for files. I fixed that. Global event taps, AppleScript, and a lot of Accessibility permissions.</p>
|
||||
<div class="article-tags">
|
||||
<span class="article-tag">Swift</span>
|
||||
<span class="article-tag">macOS</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="reveal">
|
||||
<div class="article-body">
|
||||
<h2>The Frustration</h2>
|
||||
<p>If you've ever used a Mac and tried to move files in Finder, you know the pain. You select files, press Cmd+C, navigate to a new folder, press Cmd+V... and the files are <strong>copied</strong>, not moved. The originals stay where they were.</p>
|
||||
<p>On Windows, Cmd+X (or Ctrl+X) cuts files, and Cmd+V moves them. It's been that way since the 90s. macOS only supports copy-paste for files. There is no cut. I don't know why Apple decided this was a good idea, but here we are.</p>
|
||||
<p>Every time I had to move files on my Mac, I'd copy them, navigate to the destination, paste, go back to the original, select the originals, and delete them. Four steps instead of two. Multiply that by hundreds of file operations and you start to lose your mind.</p>
|
||||
|
||||
<h2>The Solution</h2>
|
||||
<p>CommandMove is a menu bar app that intercepts Cmd+X and Cmd+V in Finder and makes them do what they should have always done: cut and move files.</p>
|
||||
<p>Press Cmd+X on selected files in Finder, navigate to any folder, press Cmd+V, and the files move. Just like Windows. Just like it should be.</p>
|
||||
|
||||
<h2>How It Works</h2>
|
||||
<p>Under the hood, CommandMove uses three macOS technologies:</p>
|
||||
<ul>
|
||||
<li><strong>Global Event Tap:</strong> Intercepts Cmd+X and Cmd+V keystrokes while Finder is the frontmost app</li>
|
||||
<li><strong>AppleScript:</strong> Queries Finder for the selected items and current folder</li>
|
||||
<li><strong>FileManager:</strong> Performs the actual move operation using <code>FileManager.moveItem</code></li>
|
||||
</ul>
|
||||
<p>The event tap is the trickiest part. macOS is very protective about who can intercept keyboard events. You need Accessibility permissions, which means the user has to go into System Settings > Privacy & Security > Accessibility and manually enable CommandMove. macOS will prompt for this on first launch, but it's still a friction point.</p>
|
||||
|
||||
<h2>The Accessibility Hurdle</h2>
|
||||
<p>Building for macOS means respecting the security model. Global event taps are powerful but regulated for good reason — they can theoretically capture any keystroke. Apple's solution is the Accessibility permission system, which requires explicit user consent.</p>
|
||||
<p>The first time you launch CommandMove, it pops up a dialog explaining why it needs Accessibility access. Some users are wary of this, and I don't blame them. The app is open source, so anyone can verify what it does. But "please give me permission to read your keyboard" is always going to be a hard sell.</p>
|
||||
|
||||
<h2>Extra Features</h2>
|
||||
<p>Once I had the core cut-and-paste working, I kept adding things:</p>
|
||||
<ul>
|
||||
<li>Right-click "Cut Files" action in Finder via a bundled Quick Action</li>
|
||||
<li>Menu bar controls for cutting, pasting, clearing the clipboard, and toggling login launch</li>
|
||||
<li>Auto-launch at login so it's always running</li>
|
||||
<li>Conflict handling — if a file with the same name exists at the destination, it creates a numbered copy automatically</li>
|
||||
</ul>
|
||||
|
||||
<h2>Lessons Learned</h2>
|
||||
<ul>
|
||||
<li>Swift is a beautiful language once you get past the learning curve</li>
|
||||
<li>AppleScript is from 1993 and it shows, but it still works</li>
|
||||
<li>macOS Accessibility APIs are powerful but poorly documented</li>
|
||||
<li>The best utility apps solve one problem extremely well</li>
|
||||
<li>Open source builds trust, especially when you're asking for Accessibility permissions</li>
|
||||
</ul>
|
||||
|
||||
<blockquote>The simplest solutions are often the ones that should have existed from the start.</blockquote>
|
||||
|
||||
<a href="../index.html#blog" class="back-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Back to Devlog
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="footer-links" style="margin-bottom: 12px;">
|
||||
<a href="../uses.html" style="color:var(--muted);text-decoration:none;">Uses</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../now.html" style="color:var(--muted);text-decoration:none;">Now</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../colophon.html" style="color:var(--muted);text-decoration:none;">Colophon</a>
|
||||
<span style="color:var(--muted);margin:0 8px;">·</span>
|
||||
<a href="../experiments.html" style="color:var(--accent);text-decoration:none;">Internet History</a>
|
||||
</div>
|
||||
Built by Miles · 2026 · Powered by too much coffee and the Konami code
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
|
||||
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
|
||||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||||
</script>
|
||||
<script src="../projects/script.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/gsap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/[email protected]/ScrollTrigger.min.js"></script>
|
||||
<script>
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
const heroTl = gsap.timeline({ defaults: { ease: 'power3.out' } });
|
||||
heroTl
|
||||
.from('.breadcrumb', { x: -30, opacity: 0, duration: 0.6 })
|
||||
.from('.article-header h1', { y: 50, opacity: 0, duration: 0.8 }, '-=0.3')
|
||||
.from('.article-tagline', { y: 30, opacity: 0, duration: 0.7 }, '-=0.4')
|
||||
.from('.article-tag', { y: 20, opacity: 0, scale: 0.8, duration: 0.4, stagger: 0.06 }, '-=0.3');
|
||||
gsap.from('nav', { y: -20, opacity: 0, duration: 0.6, ease: 'power2.out' });
|
||||
document.querySelectorAll('.article-body h2').forEach(title => {
|
||||
gsap.from(title, { scrollTrigger: { trigger: title, start: 'top 85%' }, y: 30, opacity: 0, duration: 0.7, ease: 'power3.out' });
|
||||
});
|
||||
document.querySelectorAll('.article-body p, .article-body ul, .article-body blockquote').forEach(el => {
|
||||
gsap.from(el, { scrollTrigger: { trigger: el, start: 'top 88%' }, y: 20, opacity: 0, duration: 0.6, ease: 'power3.out' });
|
||||
});
|
||||
gsap.from('footer', { scrollTrigger: { trigger: 'footer', start: 'top 95%' }, opacity: 0, duration: 0.8 });
|
||||
window.addEventListener('load', () => ScrollTrigger.refresh());
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+122
-25
@@ -40,6 +40,8 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--page:#eef1f5;
|
||||
@@ -167,41 +169,49 @@
|
||||
.socials{grid-template-columns:repeat(4,1fr);}
|
||||
.contact-grid{grid-template-columns:1fr;}
|
||||
}
|
||||
|
||||
/* ---- Load entrance (only active while .anim is applied) ---- */
|
||||
.anim [data-anim]{opacity:0;}
|
||||
.anim .avatar{will-change:transform,opacity;}
|
||||
.anim .card{will-change:transform,opacity;}
|
||||
.soc{transition:transform .18s ease,color .15s,border-color .15s,background .15s;}
|
||||
.soc:hover{transform:translateY(-3px) scale(1.04);}
|
||||
.btn-solid{transform-origin:center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas" aria-hidden="true"></canvas>
|
||||
|
||||
<div class="card">
|
||||
<div class="banner">
|
||||
<div class="banner" data-anim="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>
|
||||
<img class="avatar" data-anim="avatar" src="https://avatars.githubusercontent.com/u/115095719?v=4" alt="miles wolf allen" width="120" height="120">
|
||||
<div class="name" data-anim="txt">miles wolf allen</div>
|
||||
<div class="handle" data-anim="txt">@wolf_reaper90 · software engineer</div>
|
||||
<div class="meta" data-anim="txt">he/him · <b>MN, USA</b> · <span id="tzWatch">local time --:--:--</span></div>
|
||||
|
||||
<p class="bio">Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust. <a href="https://milesallen.site">↗ milesallen.site</a></p>
|
||||
<p class="bio" data-anim="bio">Developer and 3D artist interested in coding, games, and web technology. Currently learning Rust. <a href="https://milesallen.site">↗ milesallen.site</a></p>
|
||||
|
||||
<div class="socials">
|
||||
<a class="soc" href="https://bsky.app/profile/wolfreaper90.bsky.social" target="_blank" rel="noopener" aria-label="Bluesky">
|
||||
<a class="soc" data-anim="soc" href="https://bsky.app/profile/wolfreaper90.bsky.social" target="_blank" rel="noopener" aria-label="Bluesky">
|
||||
<svg viewBox="0 0 24 24"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>
|
||||
</a>
|
||||
<a class="soc" href="https://github.com/mileswolfallen2" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<a class="soc" data-anim="soc" href="https://github.com/mileswolfallen2" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||
</a>
|
||||
<a class="soc" href="https://discord.com/users/1058108777112543402" target="_blank" rel="noopener" aria-label="Discord">
|
||||
<a class="soc" data-anim="soc" href="https://discord.com/users/1058108777112543402" target="_blank" rel="noopener" aria-label="Discord">
|
||||
<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z"/></svg>
|
||||
</a>
|
||||
<a class="soc" href="https://www.youtube.com/channel/UCeUJbFoCDwUUg6uZi-G-zhQ" target="_blank" rel="noopener" aria-label="YouTube">
|
||||
<a class="soc" data-anim="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="section" data-anim="section">
|
||||
<div class="sec-title">Profiles</div>
|
||||
<a class="row" href="https://bsky.app/profile/wolfreaper90.bsky.social" target="_blank" rel="noopener">
|
||||
<span class="ico"><svg viewBox="0 0 24 24"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg></span>
|
||||
@@ -245,7 +255,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section" data-anim="section">
|
||||
<div class="sec-title">Contact</div>
|
||||
<div class="contact-grid">
|
||||
<a class="cell" href="mailto:[email protected]">
|
||||
@@ -265,12 +275,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-grav">made by <a href="index.html">miles</a>. not ai </div>
|
||||
<div class="no-grav" data-anim="fade">made by <a href="index.html">miles</a>. not ai </div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
|
||||
// ---- Ambient drifting orbs background ----
|
||||
// ---- Ambient aurora + particle background ----
|
||||
(function bg(){
|
||||
var canvas = document.getElementById('bgCanvas');
|
||||
if(!canvas) return;
|
||||
@@ -278,6 +289,7 @@
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
var orbs = [];
|
||||
var parts = [];
|
||||
var COLORS = [
|
||||
[29,155,240], // blue
|
||||
[255,61,154], // pink
|
||||
@@ -292,16 +304,30 @@
|
||||
}
|
||||
function seed(){
|
||||
orbs = [];
|
||||
var count = Math.min(6, Math.round(canvas.width / 220) + 3);
|
||||
for(var i=0;i<count;i++){
|
||||
parts = [];
|
||||
var w = canvas.width, h = canvas.height;
|
||||
var oCount = Math.min(7, Math.round(w / 200) + 3);
|
||||
for(var i=0;i<oCount;i++){
|
||||
orbs.push({
|
||||
x: Math.random()*canvas.width,
|
||||
y: Math.random()*canvas.height,
|
||||
r: 80 + Math.random()*160,
|
||||
x: Math.random()*w,
|
||||
y: Math.random()*h,
|
||||
r: 110 + Math.random()*220,
|
||||
c: COLORS[i % COLORS.length],
|
||||
sp: 0.15 + Math.random()*0.25,
|
||||
a: 0.12 + Math.random()*0.10,
|
||||
seed: Math.random()*1000
|
||||
sp: 0.10 + Math.random()*0.22,
|
||||
a: 0.14 + Math.random()*0.12,
|
||||
ox: Math.random()*1000,
|
||||
oy: Math.random()*1000
|
||||
});
|
||||
}
|
||||
var pCount = Math.min(80, Math.round(w*h/18000));
|
||||
for(var j=0;j<pCount;j++){
|
||||
parts.push({
|
||||
x: Math.random()*w,
|
||||
y: Math.random()*h,
|
||||
r: 0.6 + Math.random()*1.6,
|
||||
sp: 0.05 + Math.random()*0.25,
|
||||
ph: Math.random()*Math.PI*2,
|
||||
oy: Math.random()*100
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -311,11 +337,14 @@
|
||||
requestAnimationFrame(frame);
|
||||
if(reduced) return;
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
t += 0.005;
|
||||
t += 0.008;
|
||||
|
||||
// aurora orbs with slow, fluid drift (composited soft light)
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
for(var i=0;i<orbs.length;i++){
|
||||
var o = orbs[i];
|
||||
var nx = o.x + Math.sin(t*o.sp + o.seed) * 90;
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.seed*1.3) * 70;
|
||||
var nx = o.x + Math.sin(t*o.sp + o.ox) * (o.r*0.9);
|
||||
var ny = o.y + Math.cos(t*o.sp*0.8 + o.oy) * (o.r*0.7) * 0.8;
|
||||
var g = ctx.createRadialGradient(nx,ny,0,nx,ny,o.r);
|
||||
g.addColorStop(0, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+','+o.a+')');
|
||||
g.addColorStop(1, 'rgba('+o.c[0]+','+o.c[1]+','+o.c[2]+',0)');
|
||||
@@ -324,6 +353,21 @@
|
||||
ctx.arc(nx,ny,o.r,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// drifting particle specks
|
||||
ctx.fillStyle = 'rgba(40,50,70,0.35)';
|
||||
for(var p=0;p<parts.length;p++){
|
||||
var pt = parts[p];
|
||||
var px = pt.x + Math.sin(t*pt.sp + pt.ph) * 40;
|
||||
var py = pt.y + Math.cos(t*pt.sp*0.7 + pt.ph*1.7 + pt.oy) * 26;
|
||||
var tw = 0.4 + 0.6*Math.abs(Math.sin(t*0.6 + pt.ph));
|
||||
ctx.globalAlpha = tw*0.6;
|
||||
ctx.beginPath();
|
||||
ctx.arc(px%canvas.width, py%canvas.height, pt.r, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
@@ -384,6 +428,59 @@
|
||||
btn.textContent = txt; btn.disabled = true;
|
||||
setTimeout(function(){ btn.textContent = old; btn.disabled = false; }, 1400);
|
||||
}
|
||||
|
||||
// ---- Choreographed load entrance (Anime.js) ----
|
||||
(function load(){
|
||||
var body = document.body;
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
body.classList.add('anim');
|
||||
var done = function(){ body.classList.remove('anim'); };
|
||||
if (!window.anime || reduced) { done(); return; }
|
||||
|
||||
// gentle continuous pulse on the copy button via Animate.css
|
||||
var copyBtn2 = document.getElementById('copyBtn');
|
||||
if (copyBtn2) copyBtn2.classList.add('animate__animated','animate__pulse','animate__infinite','animate__slower');
|
||||
|
||||
var tl = anime.timeline({ complete: done });
|
||||
|
||||
tl
|
||||
.add({
|
||||
targets: '[data-anim="banner"]',
|
||||
translateY:[-46,0], scaleY:[0,1], opacity:[0,1],
|
||||
transformOrigin:'50% 0%',
|
||||
duration:760, easing:'easeOutQuart'
|
||||
})
|
||||
.add({
|
||||
targets: '[data-anim="avatar"]',
|
||||
scale:[0.3,1.06,1], opacity:[0,1], rotate:[-12,3,0],
|
||||
duration:850, easing:'easeOutElastic(1,0.6)',
|
||||
offset:'-=520'
|
||||
})
|
||||
.add({
|
||||
targets: '[data-anim="txt"]',
|
||||
translateY:[28,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(70), easing:'easeOutExpo'
|
||||
}, '-=430')
|
||||
.add({
|
||||
targets: '[data-anim="bio"]',
|
||||
translateY:[18,0], opacity:[0,1],
|
||||
duration:470, easing:'easeOutExpo'
|
||||
}, '-=340')
|
||||
.add({
|
||||
targets: '[data-anim="soc"]',
|
||||
scale:[0.4,1.12,1], rotate:[-8,4,0], opacity:[0,1],
|
||||
duration:520, delay:anime.stagger(55), easing:'easeOutElastic(1,0.7)'
|
||||
}, '-=280')
|
||||
.add({
|
||||
targets: '[data-anim="section"]',
|
||||
translateX:[64,0], opacity:[0,1],
|
||||
duration:560, delay:anime.stagger(140), easing:'easeOutQuint'
|
||||
}, '-=160')
|
||||
.add({
|
||||
targets: '[data-anim="fade"]',
|
||||
opacity:[0,1], translateY:[10,0], duration:420, easing:'easeOutQuart'
|
||||
}, '-=220');
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user