mirror of
https://github.com/mileswolfallen2/miles-web.git
synced 2026-09-08 11:23:17 +00:00
update
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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">> 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);
|
||||
})();
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user