This commit is contained in:
2026-07-30 21:30:00 -05:00
parent 12acce05b7
commit 8ed399346f
3 changed files with 865 additions and 0 deletions
+5
View File
@@ -708,6 +708,11 @@
<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 reveal" href="https://vanilla.milesallen.site/" target="_blank">
<div class="proj-top"><span class="proj-tag" style="background:var(--tape);color:#000;">UNRELEASED</span><span class="proj-meta">* Vanilla JS * active</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 reveal" href="projects/fedl.html">
<div class="proj-top"><span class="proj-tag" style="background:var(--accent);">LIVE</span><span class="proj-meta">* 2196 commits</span></div>
<div class="proj-title">FEDL</div>
+391
View File
@@ -0,0 +1,391 @@
(() => {
const USR = 'mileswolfallen2';
const ASCII = `
`.trim();
const bootLines = [
'BIOS v4.0 — Miles Systems Inc.',
'Copyright (c) 2026 Miles Allen',
'',
'Memory Test: 640K OK',
'Detecting hardware...',
' CPU: Developer Core™ @ 3.5GHz',
' GPU: Pixel Renderer v2.0',
' RAM: 640K Conventional + 384K Extended',
' HDD: 42 Repos Detected',
'',
'Loading MILES.EXE...',
' [████████████████████████] 100%',
'',
'Connecting to GitHub API...',
' Status: CONNECTED',
'',
'Connecting to FEDL Server...',
' Status: ONLINE',
'',
'Initializing Portfolio v4.0...',
' Rendering interface... DONE',
'',
'System Ready. Type HELP for commands.',
'> Starting MILES.EXE...'
];
const bootScreen = document.getElementById('boot-screen');
const bootText = document.getElementById('boot-text');
const site = document.getElementById('site');
let bootIndex = 0;
function typeBoot() {
if (bootIndex >= bootLines.length) {
setTimeout(() => {
bootScreen.classList.add('done');
site.classList.remove('hidden');
initSite();
}, 300);
return;
}
bootText.textContent += (bootIndex === 0 ? '' : '\n') + bootLines[bootIndex];
bootIndex++;
const delay = bootLines[bootIndex - 1] === '' ? 200 : bootLines[bootIndex - 1].startsWith(' [') ? 20 : 60;
setTimeout(typeBoot, delay);
}
setTimeout(typeBoot, 400);
// ═══════════ SITE INIT ═══════════
function initSite() {
renderAsciiArt();
renderSkills();
renderTools();
loadGitHub();
loadFEDL();
startClock();
initNav();
initScrollSpy();
initFilters();
}
// ═══════════ ASCII ART ═══════════
function renderAsciiArt() {
const el = document.getElementById('ascii-art');
if (!el) return;
el.textContent = ASCII;
}
// ═══════════ CLOCK ═══════════
function startClock() {
const el = document.getElementById('menu-clock');
function tick() {
const now = new Date();
el.textContent = now.toLocaleTimeString('en-US', { hour12: false });
}
tick();
setInterval(tick, 1000);
}
// ═══════════ NAVIGATION ═══════════
function initNav() {
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', e => {
e.preventDefault();
const target = link.getAttribute('href').slice(1);
document.getElementById(target)?.scrollIntoView({ behavior: 'smooth' });
});
});
}
// ═══════════ SCROLL SPY ═══════════
let spyTicking = false;
function initScrollSpy() {
const sections = document.querySelectorAll('.section');
const links = document.querySelectorAll('.nav-link');
function update() {
const scrollY = window.scrollY + 120;
let current = 'home';
sections.forEach(sec => {
if (sec.offsetTop <= scrollY) current = sec.id;
});
links.forEach(l => {
const isActive = l.dataset.section === current;
l.classList.toggle('active', isActive);
});
sections.forEach(sec => {
sec.classList.toggle('active-section', sec.id === current);
});
spyTicking = false;
}
window.addEventListener('scroll', () => {
if (!spyTicking) { spyTicking = true; requestAnimationFrame(update); }
});
setTimeout(update, 100);
}
// ═══════════ GITHUB API ═══════════
async function loadGitHub() {
try {
const userRes = await fetch(`https://api.github.com/users/${USR}`);
if (!userRes.ok) throw new Error(userRes.status);
const user = await userRes.json();
animateNum('stat-repos', user.public_repos || 0, 147);
animateNum('stat-followers', user.followers || 0, 12);
animateNum('stat-gists', user.public_gists || 0, 1);
document.getElementById('stat-joined').textContent = new Date(user.created_at).getFullYear().toString();
animateBar('bar-joined', 80);
const reposRes = await fetch(`https://api.github.com/users/${USR}/repos?per_page=100&sort=updated`);
if (!reposRes.ok) throw new Error(reposRes.status);
const repos = await reposRes.json();
let totalStars = 0, totalForks = 0;
repos.forEach(r => { totalStars += r.stargazers_count || 0; totalForks += r.forks_count || 0; });
animateNum('stat-stars', totalStars, 50);
animateNum('stat-forks', totalForks, 30);
animateBar('bar-repos', Math.min(100, (user.public_repos / 150) * 100));
animateBar('bar-stars', Math.min(100, totalStars * 10));
animateBar('bar-forks', Math.min(100, totalForks * 10));
animateBar('bar-followers', Math.min(100, (user.followers / 20) * 100));
animateBar('bar-gists', Math.min(100, user.public_gists * 20));
try {
const eventsRes = await fetch(`https://api.github.com/users/${USR}/events/public?per_page=15`);
if (eventsRes.ok) {
const events = await eventsRes.json();
renderActivity(events.slice(0, 10));
}
} catch (e) { /* skip */ }
renderProjects(repos);
document.getElementById('sb-status').textContent = 'GitHub API: Connected (' + repos.length + ' repos)';
} catch (err) {
document.getElementById('sb-status').textContent = 'GitHub API: Error — retrying...';
document.getElementById('activity-log').innerHTML = `<div class="log-line dim">&gt; API error: ${err.message}</div>`;
setTimeout(loadGitHub, 30000);
}
}
// ═══════════ FEDL API ═══════════
const FEDL_BASE = 'https://server.fedl.site/fedl';
async function loadFEDL() {
try {
const [listRes, runsRes, ghRes] = await Promise.allSettled([
fetch(`${FEDL_BASE}/api/list`),
fetch(`${FEDL_BASE}/api/runs`),
fetch(`https://api.github.com/repos/${USR}/fedl`)
]);
if (listRes.status === 'fulfilled' && listRes.value.ok) {
const listData = await listRes.value.json();
const levels = listData.items || [];
animateNum('fedl-levels', levels.length, 30);
const firstLevel = levels[0];
if (firstLevel) {
document.getElementById('fedl-top1').textContent =
(firstLevel.title || firstLevel.name || '').slice(0, 14) || '#1';
}
}
if (runsRes.status === 'fulfilled' && runsRes.value.ok) {
const runsData = await runsRes.value.json();
const runs = runsData.items || [];
const verified = runs.filter(r => r.status === 'approved');
animateNum('fedl-runs', verified.length, 25);
const playerCounts = {};
verified.forEach(r => {
const name = r.playerName || r.player || 'Unknown';
playerCounts[name] = (playerCounts[name] || 0) + 1;
});
const topPlayer = Object.entries(playerCounts).sort((a, b) => b[1] - a[1])[0];
if (topPlayer) {
document.getElementById('fedl-topplayer').textContent =
topPlayer[0].slice(0, 14);
}
animateNum('fedl-players', Object.keys(playerCounts).length, 20);
const sorted = [...verified].sort((a, b) =>
new Date(b.reviewedAt || b.submittedAt || 0) - new Date(a.reviewedAt || a.submittedAt || 0)
);
if (sorted[0]) {
const date = sorted[0].reviewedAt || sorted[0].submittedAt;
document.getElementById('fedl-updated').textContent = date ? getTimeAgo(date) : 'recently';
}
}
if (ghRes.status === 'fulfilled' && ghRes.value.ok) {
const gh = await ghRes.value.json();
animateNum('fedl-stars', gh.stargazers_count || 0, 30);
document.getElementById('fedl-lang').textContent = gh.language || 'HTML';
if (!document.getElementById('fedl-updated').textContent ||
document.getElementById('fedl-updated').textContent === '—') {
document.getElementById('fedl-updated').textContent = getTimeAgo(gh.pushed_at);
}
}
} catch (err) {
console.error('FEDL load error:', err);
}
}
// ═══════════ RENDER ═══════════
function renderActivity(events) {
const log = document.getElementById('activity-log');
log.innerHTML = events.map(e => {
let dot = 'other', msg = e.type;
if (e.type === 'PushEvent') { dot = 'push'; msg = `Push ${e.payload.commits?.length || 0} commit(s)`; }
else if (e.type === 'CreateEvent') { dot = 'create'; msg = `Create ${e.payload.ref_type}`; }
else if (e.type === 'DeleteEvent') { msg = `Delete ${e.payload.ref_type}`; }
else if (e.type === 'IssuesEvent') { msg = `${e.payload.action} issue`; }
else if (e.type === 'WatchEvent') { msg = 'Star'; }
else if (e.type === 'ForkEvent') { msg = 'Fork'; }
else if (e.type === 'PullRequestEvent') { msg = `${e.payload.action} PR`; }
return `<div class="log-line">
<span class="log-dot ${dot}"></span>
<span class="log-repo">${(e.repo?.name || '').replace(USR + '/', '')}</span>
<span class="log-msg">${msg}</span>
<span class="log-time">${getTimeAgo(e.created_at)}</span>
</div>`;
}).join('');
}
const SITE_URLS = {
'fedl': 'https://fedl.site',
'miles-web': 'https://me.fedl.site',
'OmniEmu2.0': 'https://github.com/mileswolfallen2/OmniEmu2.0',
'moon-gaming': 'https://moon-gaming.vercel.app',
'linuxWeb': 'https://linuxweb.vercel.app',
'FNFHTML5': 'https://fnfhtml5.pages.dev',
'fun-miles-lancher': 'https://github.com/mileswolfallen2/fun-miles-lancher',
'goplayer': 'https://github.com/mileswolfallen2/goplayer'
};
const PINNED = ['OmniEmu2.0', 'fedl', 'moon-gaming', 'miles-web'];
const ICONS = {
'OmniEmu2.0': '🎮', 'fedl': '🎯', 'moon-gaming': '🌙', 'miles-web': '🌐',
'linuxWeb': '🐧', 'FNFHTML5': '🎵', 'fun-miles-lancher': '🚀',
'the-start-of-the-thing': '⭐', 'for-a-frend': '🤝', 'v86-wasmless': '💻',
'goplayer': '🎶', 'open-fl-test': '🧪', 'A1R': '✈️'
};
function renderProjects(repos) {
const list = document.getElementById('projects-list');
const sorted = repos.sort((a, b) => {
const ai = PINNED.indexOf(a.name), bi = PINNED.indexOf(b.name);
if (ai !== -1 && bi !== -1) return ai - bi;
if (ai !== -1) return -1;
if (bi !== -1) return 1;
return (b.stargazers_count || 0) - (a.stargazers_count || 0);
}).slice(0, 20);
list.innerHTML = sorted.map(r => {
const icon = ICONS[r.name] || '📁';
const desc = r.description || 'No description.';
const isFork = r.fork;
const url = SITE_URLS[r.name] || r.homepage || `https://github.com/${USR}/${r.name}`;
return `<a href="${url}" target="_blank" class="project-row" data-type="${isFork ? 'fork' : 'original'}">
<span class="pr-icon">${icon}</span>
<div class="pr-info">
<div class="pr-name">${r.name}</div>
<div class="pr-desc">${desc}</div>
</div>
<div class="pr-meta">
${r.language ? `<span class="pr-tag lang">${r.language}</span>` : ''}
${r.stargazers_count > 0 ? `<span class="pr-tag stars">★ ${r.stargazers_count}</span>` : ''}
${isFork ? `<span class="pr-tag fork">FORK</span>` : ''}
</div>
<span class="pr-link">OPEN </span>
</a>`;
}).join('');
}
// ═══════════ FILTERS ═══════════
function initFilters() {
document.querySelectorAll('.pf-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.pf-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const filter = btn.dataset.filter;
document.querySelectorAll('.project-row').forEach(row => {
if (filter === 'all') { row.style.display = ''; return; }
row.style.display = row.dataset.type === filter ? '' : 'none';
});
});
});
}
// ═══════════ SKILLS ═══════════
function renderSkills() {
const skills = [
{ name: 'Python', pct: 80, color: 'green' },
{ name: 'JavaScript', pct: 75, color: 'amber' },
{ name: 'C++', pct: 55, color: 'cyan' },
{ name: 'HTML/CSS', pct: 85, color: 'green' },
{ name: 'React', pct: 60, color: 'purple' },
{ name: 'Node.js', pct: 65, color: 'green' },
{ name: 'Git', pct: 80, color: 'cyan' },
{ name: 'Linux', pct: 75, color: 'amber' }
];
document.getElementById('skills-list').innerHTML = skills.map(s =>
`<div class="skill-row">
<div class="skill-name">${s.name}</div>
<div class="skill-bar"><div class="skill-fill ${s.color}" data-pct="${s.pct}"></div></div>
<div class="skill-pct">${s.pct}%</div>
</div>`
).join('');
setTimeout(() => {
document.querySelectorAll('.skill-fill').forEach(f => { f.style.width = f.dataset.pct + '%'; });
}, 300);
}
function renderTools() {
const tools = ['VS Code', 'Git', 'GitHub', 'Replit', 'Node.js', 'Electron', 'Linux', 'Firefox DevTools', 'Figma', 'Docker', 'SQLite', 'Geode'];
document.getElementById('tools-grid').innerHTML = tools.map(t =>
`<span class="tool-chip">${t}</span>`
).join('');
}
// ═══════════ UTILITIES ═══════════
function animateNum(id, target, speed) {
const el = document.getElementById(id);
if (!el) return;
let current = 0;
const steps = speed || 30;
const step = Math.max(1, Math.floor(target / steps));
const interval = setInterval(() => {
current += step;
if (current >= target) { current = target; clearInterval(interval); }
el.textContent = current.toLocaleString();
}, 30);
}
function animateBar(id, pct) {
const el = document.getElementById(id);
if (!el) return;
setTimeout(() => { el.style.width = pct + '%'; }, 200);
}
function getTimeAgo(dateStr) {
const now = new Date();
const then = new Date(dateStr);
const mins = Math.floor((now - then) / 60000);
if (mins < 1) return 'just now';
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
if (days < 30) return `${days}d ago`;
const months = Math.floor(days / 30);
if (months < 12) return `${months}mo ago`;
return `${Math.floor(months / 12)}y ago`;
}
setInterval(() => { loadGitHub(); loadFEDL(); }, 300000);
})();
+469
View File
@@ -0,0 +1,469 @@
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#080808;--surface:#0f0f0f;--surface2:#181818;--surface3:#222;
--green:#33ff33;--green-dim:#1a8c1a;--green-dark:#0d4d0d;
--amber:#ffb000;--amber-dim:#996a00;
--cyan:#00ffff;--cyan-dim:#009999;
--purple:#cc77ff;--purple-dim:#8844bb;
--red:#ff4444;--red-dim:#cc2222;
--blue:#4488ff;--gold:#ffd700;
--text:#c0c0c0;--dim:#666;--dimmer:#333;
--border:#333;
--bevel-light:#444;--bevel-dark:#1a1a1a;
/* default accent = green */
--accent:#33ff33;
--accent-dim:#1a8c1a;
--accent-dark:#0d4d0d;
--accent-glow:rgba(51,255,51,.3);
}
/* per-section accent colors */
.section-home{--accent:#33ff33;--accent-dim:#1a8c1a;--accent-dark:#0d4d0d;--accent-glow:rgba(51,255,51,.3)}
.section-stats{--accent:#ffd700;--accent-dim:#b8960a;--accent-dark:#6b5900;--accent-glow:rgba(255,215,0,.3)}
.section-projects{--accent:#cc77ff;--accent-dim:#8844bb;--accent-dark:#552288;--accent-glow:rgba(204,119,255,.3)}
.section-fedl{--accent:#ff4444;--accent-dim:#cc2222;--accent-dark:#881111;--accent-glow:rgba(255,68,68,.3)}
.section-skills{--accent:#00ffff;--accent-dim:#009999;--accent-dark:#006666;--accent-glow:rgba(0,255,255,.3)}
.section-contact{--accent:#ffb000;--accent-dim:#cc8800;--accent-dark:#885500;--accent-glow:rgba(255,176,0,.3)}
html{scroll-behavior:smooth}
body{
background:var(--bg);color:var(--text);
font-family:'VT323','Courier New',monospace;
font-size:18px;line-height:1.5;
overflow-x:hidden;
}
::selection{background:var(--accent);color:var(--bg)}
a{color:var(--accent);text-decoration:none}
a:hover{text-shadow:0 0 8px var(--accent-glow)}
/* CRT */
.crt-overlay{
position:fixed;inset:0;z-index:9998;pointer-events:none;
background:radial-gradient(ellipse at center,transparent 60%,rgba(0,0,0,.35) 100%);
}
.scanlines{
position:fixed;inset:0;z-index:9997;pointer-events:none;
background:repeating-linear-gradient(0deg,rgba(0,0,0,.15) 0px,rgba(0,0,0,.15) 1px,transparent 1px,transparent 3px);
}
@keyframes flicker{
0%{opacity:.97}5%{opacity:.95}10%{opacity:.98}15%{opacity:.96}
20%{opacity:.99}50%{opacity:.97}80%{opacity:.98}90%{opacity:.96}100%{opacity:.98}
}
body{animation:flicker 4s infinite}
/* Boot */
#boot-screen{
position:fixed;inset:0;z-index:10000;
background:var(--bg);padding:40px;
display:flex;flex-direction:column;
font-family:'Share Tech Mono','Courier New',monospace;
font-size:14px;color:var(--green);
overflow:hidden;
}
#boot-screen.done{display:none}
#boot-text{white-space:pre-wrap;line-height:1.6;flex:1;overflow-y:auto}
#boot-screen .boot-cursor{display:inline;animation:blink .6s step-end infinite;color:var(--green)}
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
.hidden{display:none!important}
/* Titlebar */
.titlebar{
display:flex;align-items:center;justify-content:space-between;
padding:4px 8px;
background:linear-gradient(90deg,var(--green-dark),var(--green-dim),var(--green-dark));
color:var(--bg);font-family:'Press Start 2P',monospace;font-size:8px;
border-bottom:2px solid var(--green);
position:sticky;top:0;z-index:100;
}
.titlebar-left{display:flex;align-items:center;gap:8px}
.titlebar-icon{font-size:14px}
.titlebar-text{letter-spacing:.5px}
.titlebar-btns{display:flex;gap:4px}
.tb-btn{
width:20px;height:18px;
display:flex;align-items:center;justify-content:center;
background:var(--surface2);border:1px solid var(--green-dim);
color:var(--green);cursor:pointer;font-size:10px;
transition:all .1s;
}
.tb-btn:hover{background:var(--green);color:var(--bg)}
/* Navigation */
.nav-bar{
display:flex;align-items:center;gap:0;
padding:3px 8px;
background:var(--surface);border-bottom:1px solid var(--dimmer);
font-family:'VT323',monospace;font-size:16px;
position:sticky;top:28px;z-index:99;
overflow-x:auto;
}
.nav-link{
padding:2px 12px;color:var(--dim);cursor:pointer;
transition:all .15s;white-space:nowrap;
border-bottom:2px solid transparent;
}
.nav-link:hover{color:var(--green);border-bottom-color:var(--green-dim)}
.nav-link.active{color:var(--accent);border-bottom-color:var(--accent)}
.menu-clock{margin-left:auto;color:var(--amber);font-family:'Share Tech Mono',monospace;font-size:14px;white-space:nowrap}
/* Sections */
.section{
padding:24px 12px 48px;
border-bottom:2px solid var(--dimmer);
position:relative;
transition:border-color .3s;
scroll-margin-top:80px;
}
.section-accent{
position:absolute;top:0;left:0;right:0;height:3px;
background:var(--accent);
box-shadow:0 0 12px var(--accent-glow);
opacity:0;
transition:opacity .4s;
}
.section.active-section .section-accent{opacity:1}
.section.active-section{border-bottom-color:var(--accent)}
/* Retro Windows */
.retro-window{
border:2px solid var(--dimmer);
background:var(--surface);
box-shadow:inset 1px 1px 0 var(--bevel-light),inset -1px -1px 0 var(--bevel-dark),
4px 4px 0 rgba(0,0,0,.4);
max-width:900px;margin:0 auto;
transition:border-color .3s,box-shadow .3s;
}
.active-section .retro-window{
border-color:var(--accent-dim);
box-shadow:inset 1px 1px 0 var(--accent-dim),inset -1px -1px 0 var(--accent-dark),
4px 4px 0 rgba(0,0,0,.5),0 0 20px var(--accent-glow);
}
.rw-titlebar{
display:flex;align-items:center;justify-content:space-between;
padding:4px 8px;
background:linear-gradient(90deg,var(--accent-dark),var(--accent-dim),var(--accent-dark));
color:var(--bg);font-family:'Press Start 2P',monospace;font-size:7px;
transition:background .3s;
}
.rw-close{
width:18px;height:16px;display:flex;align-items:center;justify-content:center;
background:var(--surface2);border:1px solid var(--dimmer);
color:var(--text);cursor:pointer;font-size:10px;
font-family:'VT323',monospace;
transition:all .15s;
}
.rw-close:hover{background:var(--red);color:#fff;border-color:var(--red)}
.rw-body{padding:16px}
/* Hero */
.ascii-hero{text-align:center}
.ascii-art{
font-family:'Share Tech Mono',monospace;
font-size:10px;line-height:1.2;
color:var(--accent);
text-shadow:0 0 6px var(--accent-glow);
margin-bottom:20px;overflow-x:auto;
white-space:pre;
transition:color .3s,text-shadow .3s;
}
.hero-info{
text-align:left;max-width:600px;margin:0 auto 20px;
font-family:'VT323',monospace;font-size:20px;
}
.typewriter-line{
opacity:0;transform:translateX(-10px);
animation:typeIn .4s ease forwards;
margin-bottom:4px;color:var(--accent);
}
.typewriter-line:nth-child(1){animation-delay:.3s}
.typewriter-line:nth-child(2){animation-delay:.6s}
.typewriter-line:nth-child(3){animation-delay:.9s}
@keyframes typeIn{to{opacity:1;transform:none}}
.blink-line{animation:typeIn .4s ease 1.2s forwards;opacity:0;transform:translateX(-10px)}
.cursor{animation:blink .6s step-end infinite}
.hero-tags{
display:flex;flex-wrap:wrap;gap:8px;justify-content:center;
margin:20px 0;
}
.tag{
padding:4px 10px;
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--accent);border:1px solid var(--accent-dim);
background:var(--surface2);
transition:all .15s;
}
.tag:hover{box-shadow:0 0 8px var(--accent-glow);text-shadow:0 0 6px var(--accent-glow)}
.hero-links{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:16px}
/* Buttons */
.retro-btn{
display:inline-flex;align-items:center;gap:6px;
padding:8px 16px;
font-family:'Press Start 2P',monospace;font-size:8px;
color:var(--accent);background:var(--surface2);
border:2px solid var(--accent-dim);
cursor:pointer;transition:all .1s;
box-shadow:inset 1px 1px 0 var(--bevel-light),inset -1px -1px 0 var(--bevel-dark);
}
.retro-btn:hover{
background:var(--accent);color:var(--bg);
box-shadow:0 0 12px var(--accent-glow);
}
.retro-btn:active{
box-shadow:inset -1px -1px 0 var(--bevel-light),inset 1px 1px 0 var(--bevel-dark);
}
.green-btn{border-color:var(--green-dim);color:var(--green)}
.green-btn:hover{background:var(--green);color:var(--bg)}
/* Stats */
.stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.stat-box{
border:1px solid var(--dimmer);padding:14px;text-align:center;
background:var(--surface2);transition:all .15s;
}
.active-section .stat-box:hover{border-color:var(--accent);box-shadow:0 0 10px var(--accent-glow)}
.stat-box-label{
font-family:'Press Start 2P',monospace;font-size:6px;
color:var(--dim);margin-bottom:8px;letter-spacing:.5px;
}
.stat-box-value{
font-family:'Share Tech Mono',monospace;font-size:28px;
color:var(--accent);margin-bottom:8px;
text-shadow:0 0 8px var(--accent-glow);
transition:color .3s,text-shadow .3s;
}
.stat-box-value.gold{color:var(--gold);text-shadow:0 0 8px rgba(255,215,0,.4)}
.stat-box-value.green{color:var(--green);text-shadow:0 0 8px rgba(51,255,51,.4)}
.stat-box-value.cyan{color:var(--cyan);text-shadow:0 0 8px rgba(0,255,255,.4)}
.stat-box-value.purple{color:var(--purple);text-shadow:0 0 8px rgba(204,119,255,.4)}
.stat-box-value.amber{color:var(--amber);text-shadow:0 0 8px rgba(255,176,0,.4)}
.stat-bar{height:4px;background:var(--surface3);margin-top:4px;overflow:hidden}
.stat-bar-fill{
height:100%;width:0;background:var(--accent);
transition:width 1s ease-out,background .3s;
box-shadow:0 0 6px var(--accent-glow);
}
.stat-bar-fill.gold{background:var(--gold);box-shadow:0 0 6px rgba(255,215,0,.4)}
.stat-bar-fill.green{background:var(--green)}
.stat-bar-fill.cyan{background:var(--cyan);box-shadow:0 0 6px rgba(0,255,255,.4)}
.stat-bar-fill.purple{background:var(--purple);box-shadow:0 0 6px rgba(204,119,255,.4)}
.stat-bar-fill.amber{background:var(--amber);box-shadow:0 0 6px rgba(255,176,0,.4)}
/* Activity */
.activity-log{max-height:300px;overflow-y:auto;font-family:'Share Tech Mono',monospace;font-size:14px}
.log-line{
padding:4px 0;border-bottom:1px solid var(--dimmer);
display:flex;gap:8px;align-items:baseline;
}
.log-line.dim{color:var(--dim)}
.log-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;margin-top:5px}
.log-dot.push{background:var(--accent);box-shadow:0 0 4px var(--accent-glow)}
.log-dot.create{background:var(--cyan);box-shadow:0 0 4px rgba(0,255,255,.5)}
.log-dot.other{background:var(--amber)}
.log-repo{color:var(--accent);min-width:120px}
.log-msg{color:var(--dim);flex:1}
.log-time{color:var(--amber);font-size:12px}
/* Projects */
.project-filters{display:flex;gap:4px;margin-bottom:12px}
.pf-btn{
padding:4px 12px;
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--dim);background:var(--surface2);
border:1px solid var(--dimmer);cursor:pointer;transition:all .1s;
}
.pf-btn:hover,.pf-btn.active{color:var(--accent);border-color:var(--accent);background:var(--surface2)}
.projects-list{display:flex;flex-direction:column;gap:8px;max-height:60vh;overflow-y:auto}
.project-row{
display:flex;align-items:center;gap:12px;
padding:10px 12px;
border:1px solid var(--dimmer);background:var(--surface2);
cursor:pointer;transition:all .15s;
}
.project-row:hover{
border-color:var(--accent);
background:rgba(255,255,255,.02);
box-shadow:0 0 8px var(--accent-glow);
}
.project-row .pr-icon{font-size:18px;flex-shrink:0}
.project-row .pr-info{flex:1;min-width:0}
.project-row .pr-name{
font-family:'Press Start 2P',monospace;font-size:8px;
color:var(--accent);margin-bottom:3px;
transition:color .3s;
}
.project-row .pr-desc{
font-size:15px;color:var(--dim);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.project-row .pr-meta{display:flex;gap:6px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end}
.project-row .pr-tag{
font-family:'Press Start 2P',monospace;font-size:5px;
padding:2px 6px;border:1px solid var(--dimmer);color:var(--dim);
}
.project-row .pr-tag.stars{color:var(--gold);border-color:var(--amber-dim)}
.project-row .pr-tag.lang{color:var(--accent);border-color:var(--accent-dim)}
.project-row .pr-tag.fork{color:var(--amber);border-color:var(--amber-dim)}
.project-row .pr-link{
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--accent);flex-shrink:0;transition:color .3s;
}
.project-row:hover .pr-link{text-shadow:0 0 6px var(--accent-glow)}
/* FEDL */
.fedl-window{border-color:var(--accent-dim)!important}
.fedl-titlebar{background:linear-gradient(90deg,var(--accent-dark),var(--accent-dim),var(--accent-dark))!important}
.fedl-body{text-align:center}
.fedl-ascii{
font-family:'Share Tech Mono',monospace;font-size:10px;
color:var(--accent);text-shadow:0 0 6px var(--accent-glow);
margin-bottom:16px;line-height:1.1;
white-space:pre;overflow-x:auto;
transition:color .3s,text-shadow .3s;
}
.fedl-desc{
font-family:'VT323',monospace;font-size:20px;
color:var(--dim);text-align:left;max-width:500px;margin:0 auto 4px;
}
.fedl-stats-row{
display:grid;grid-template-columns:repeat(4,1fr);gap:8px;
margin:20px 0;
}
.fedl-stat{
border:1px solid var(--dimmer);padding:16px 8px;text-align:center;
background:var(--surface2);
}
.fedl-stat-val{
font-family:'Share Tech Mono',monospace;font-size:22px;
color:var(--accent);text-shadow:0 0 6px var(--accent-glow);
margin-bottom:4px;
transition:color .3s,text-shadow .3s;
}
.fedl-stat-lbl{
font-family:'Press Start 2P',monospace;font-size:5px;
color:var(--dim);letter-spacing:.5px;
}
.fedl-timeline{text-align:left;margin:16px 0;padding:12px;border:1px solid var(--dimmer);background:var(--surface2)}
.fedl-tl-title{
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--accent);margin-bottom:8px;
}
.fedl-tl-item{
font-family:'VT323',monospace;font-size:18px;
color:var(--dim);padding:3px 0;padding-left:12px;
border-left:2px solid var(--dimmer);margin-left:4px;
}
.fedl-tl-item:last-child{border-left-color:var(--accent)}
.tl-date{color:var(--amber);font-family:'Share Tech Mono',monospace;font-size:14px}
.fedl-actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:16px}
/* Skills */
.skills-list{display:flex;flex-direction:column;gap:6px;margin-bottom:20px}
.skill-row{display:flex;align-items:center;gap:12px}
.skill-name{
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--dim);width:100px;text-align:right;flex-shrink:0;
}
.skill-bar{flex:1;height:10px;background:var(--surface3);border:1px solid var(--dimmer);overflow:hidden}
.skill-fill{height:100%;width:0;transition:width 1s ease-out,background .3s;position:relative}
.skill-fill::after{
content:'';position:absolute;inset:0;
background:repeating-linear-gradient(90deg,transparent 0px,transparent 3px,rgba(0,0,0,.2) 3px,rgba(0,0,0,.2) 4px);
}
.skill-fill.green{background:var(--accent);box-shadow:0 0 6px var(--accent-glow)}
.skill-fill.cyan{background:var(--cyan);box-shadow:0 0 6px rgba(0,255,255,.3)}
.skill-fill.amber{background:var(--amber);box-shadow:0 0 6px rgba(255,176,0,.3)}
.skill-fill.purple{background:var(--purple);box-shadow:0 0 6px rgba(204,119,255,.3)}
.skill-pct{
font-family:'Share Tech Mono',monospace;font-size:14px;
color:var(--accent);width:36px;text-align:right;
transition:color .3s;
}
.tools-section{border-top:1px solid var(--dimmer);padding-top:16px}
.tools-title{
font-family:'Press Start 2P',monospace;font-size:7px;
color:var(--accent);margin-bottom:12px;
}
.tools-grid{display:flex;flex-wrap:wrap;gap:6px}
.tool-chip{
padding:4px 10px;
font-family:'Press Start 2P',monospace;font-size:6px;
color:var(--dim);border:1px solid var(--dimmer);
background:var(--surface2);transition:all .15s;cursor:default;
}
.tool-chip:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 6px var(--accent-glow);text-shadow:0 0 4px var(--accent-glow)}
/* Contact */
.contact-list{display:flex;flex-direction:column;gap:6px}
.contact-row{
display:flex;align-items:center;gap:12px;
padding:12px;border:1px solid var(--dimmer);
background:var(--surface2);transition:all .15s;cursor:pointer;
}
.contact-row:hover{
border-color:var(--accent);
background:rgba(255,255,255,.02);
box-shadow:0 0 8px var(--accent-glow);
}
.contact-icon{font-size:18px;flex-shrink:0;width:28px;text-align:center}
.contact-info{flex:1;min-width:0}
.contact-label{
font-family:'Press Start 2P',monospace;font-size:6px;
color:var(--dim);display:block;margin-bottom:2px;
}
.contact-val{
font-family:'VT323',monospace;font-size:20px;
color:var(--accent);transition:color .3s;
}
.contact-row:hover .contact-val{text-shadow:0 0 6px var(--accent-glow)}
.contact-arrow{
color:var(--dim);font-size:18px;flex-shrink:0;
transition:all .15s;
}
.contact-row:hover .contact-arrow{color:var(--accent);transform:translateX(4px);text-shadow:0 0 6px var(--accent-glow)}
/* Statusbar */
.statusbar{
display:flex;align-items:center;justify-content:space-between;
padding:3px 8px;
background:var(--surface);border-top:2px solid var(--dimmer);
font-family:'Press Start 2P',monospace;font-size:6px;
color:var(--dim);position:sticky;bottom:0;
}
.sb-center{color:var(--accent);transition:color .3s}
/* Scrollbar */
::-webkit-scrollbar{width:10px}
::-webkit-scrollbar-track{background:var(--surface)}
::-webkit-scrollbar-thumb{background:var(--dimmer);border:1px solid var(--surface3)}
::-webkit-scrollbar-thumb:hover{background:var(--accent-dim)}
/* Responsive */
@media(max-width:768px){
.stats-grid{grid-template-columns:repeat(2,1fr)}
.fedl-stats-row{grid-template-columns:repeat(2,1fr)}
.nav-bar{gap:0;overflow-x:auto}
.nav-link{font-size:14px;padding:2px 8px}
.ascii-art{font-size:7px}
.skill-name{width:70px;font-size:6px}
.project-row{flex-wrap:wrap}
.project-row .pr-meta{width:100%;justify-content:flex-start}
.section-title{margin-top:56px}
}
@media(max-width:480px){
body{font-size:16px}
.stats-grid{grid-template-columns:1fr 1fr}
.fedl-stats-row{grid-template-columns:1fr 1fr}
.hero-tags{gap:4px}
.tag{font-size:6px;padding:3px 6px}
}
/* Glitch */
@keyframes glitch{
0%{transform:none}20%{transform:translate(-2px,1px)}
40%{transform:translate(2px,-1px)}60%{transform:none}
80%{transform:translate(1px,2px)}100%{transform:none}
}
.glitch{animation:glitch .3s ease}