apiVersion: v1 kind: ConfigMap metadata: name: gitea-landing-page namespace: gitea data: home.tmpl: | {{template "base/head" .}}

Alex Mickelson

Recent Projects

Latest commits across all repositories

Recent Activity

What Alex has been up to View full profile β†’
{{template "base/footer" .}} custom-landing.css: | #alex-landing { min-height: 100vh; background: #0d1117; color: #e6edf3; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } /* Hero */ .hero { display: flex; justify-content: center; align-items: center; padding: 80px 24px 60px; text-align: center; } .hero-inner { max-width: 640px; } .hero h1 { font-size: 3rem; font-weight: 800; margin: 0 0 12px; background: linear-gradient(135deg, #e6edf3 0%, #58a6ff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .hero-links { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } .btn-primary { padding: 10px 24px; border-radius: 8px; background: #238636; color: #fff; font-weight: 600; font-size: 0.95rem; text-decoration: none; transition: background 0.2s; } .btn-primary:hover { background: #2ea043; } .btn-ghost { padding: 10px 24px; border-radius: 8px; border: 1px solid #30363d; color: #e6edf3; font-weight: 600; font-size: 0.95rem; text-decoration: none; transition: border-color 0.2s, background 0.2s; } .btn-ghost:hover { border-color: #58a6ff; background: #58a6ff11; } /* Projects section */ .projects-section { max-width: 1100px; margin: 0 auto; padding: 0 24px 80px; } .section-header { margin-bottom: 24px; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; } .section-header h2 { font-size: 1.5rem; font-weight: 700; margin: 0; color: #e6edf3; } .subtitle { font-size: 0.9rem; color: #8b949e; } /* Grid */ .repo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; } /* Skeleton loaders */ .skeleton-card { height: 160px; border-radius: 12px; background: linear-gradient(90deg, #161b22 25%, #21262d 50%, #161b22 75%); background-size: 200% 100%; animation: shimmer 1.4s infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Repo cards */ .repo-card { background: #161b22; border: 1px solid #21262d; border-radius: 12px; padding: 20px; text-decoration: none; color: inherit; display: flex; flex-direction: column; gap: 10px; transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s; cursor: pointer; } .repo-card:hover { border-color: #58a6ff; transform: translateY(-2px); box-shadow: 0 8px 24px #58a6ff1a; } .repo-card-header { display: flex; align-items: center; gap: 10px; } .repo-icon { font-size: 1.1rem; flex-shrink: 0; } .repo-name { font-size: 1rem; font-weight: 600; color: #58a6ff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .repo-private { font-size: 0.7rem; background: #21262d; border: 1px solid #30363d; border-radius: 4px; padding: 1px 6px; color: #8b949e; flex-shrink: 0; } .repo-desc { font-size: 0.875rem; color: #8b949e; line-height: 1.5; flex: 1; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .repo-commit { font-size: 0.8rem; color: #6e7681; border-top: 1px solid #21262d; padding-top: 10px; display: flex; align-items: center; gap: 6px; overflow: hidden; } .commit-dot { width: 6px; height: 6px; border-radius: 50%; background: #238636; flex-shrink: 0; } .commit-msg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; } .commit-time { color: #484f58; flex-shrink: 0; } .repo-meta { display: flex; gap: 14px; font-size: 0.8rem; color: #6e7681; } .repo-meta span { display: flex; align-items: center; gap: 4px; } /* Error state */ .error-msg { grid-column: 1 / -1; text-align: center; padding: 40px; color: #8b949e; font-size: 0.95rem; } /* Activity section */ .activity-section { max-width: 1100px; margin: 0 auto; padding: 0 24px 80px; } .view-all-link { font-size: 0.85rem; color: #58a6ff; text-decoration: none; margin-left: auto; } .view-all-link:hover { text-decoration: underline; } .activity-feed { display: flex; flex-direction: column; gap: 0; border: 1px solid #21262d; border-radius: 12px; overflow: hidden; } .skeleton-activity { height: 52px; background: linear-gradient(90deg, #161b22 25%, #21262d 50%, #161b22 75%); background-size: 200% 100%; animation: shimmer 1.4s infinite; border-top: 1px solid #0d1117; } .skeleton-activity:first-child { border-top: none; } .activity-item { display: flex; align-items: flex-start; gap: 12px; padding: 12px 16px; background: #161b22; border-top: 1px solid #21262d; font-size: 0.875rem; transition: background 0.15s; } .activity-item:first-child { border-top: none; } .activity-item:hover { background: #1c2128; } .activity-op-icon { flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; background: #21262d; display: flex; align-items: center; justify-content: center; font-size: 0.8rem; margin-top: 1px; } .activity-body { flex: 1; min-width: 0; } .activity-headline { color: #e6edf3; line-height: 1.5; } .activity-headline a { color: #58a6ff; text-decoration: none; font-weight: 600; } .activity-headline a:hover { text-decoration: underline; } .activity-commits { margin-top: 6px; display: flex; flex-direction: column; gap: 3px; } .activity-commit-line { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; color: #8b949e; overflow: hidden; } .activity-commit-sha { font-family: monospace; font-size: 0.75rem; color: #58a6ff; flex-shrink: 0; text-decoration: none; } .activity-commit-sha:hover { text-decoration: underline; } .activity-commit-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .activity-time { flex-shrink: 0; font-size: 0.78rem; color: #484f58; margin-left: auto; padding-left: 12px; white-space: nowrap; } custom-landing.js: | (async function () { const grid = document.getElementById('repo-grid'); if (!grid) return; const baseUrl = window.GITEA_SUB_URL || ''; function timeAgo(dateStr) { const diff = (Date.now() - new Date(dateStr)) / 1000; if (diff < 60) return 'just now'; if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; if (diff < 2592000) return Math.floor(diff / 86400) + 'd ago'; if (diff < 31536000) return Math.floor(diff / 2592000) + 'mo ago'; return Math.floor(diff / 31536000) + 'y ago'; } function escapeHtml(str) { return (str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function langDot(lang) { const colors = { Go:'#00ADD8', Python:'#3572A5', JavaScript:'#f1e05a', TypeScript:'#2b7489', Rust:'#dea584', Java:'#b07219', 'C#':'#178600', Nix:'#7e7eff', Shell:'#89e051', HTML:'#e34c26', CSS:'#563d7c', Elixir:'#6e4a7e', }; return colors[lang] ? `` : 'πŸ“¦'; } async function fetchJson(url) { const resp = await fetch(url, { credentials: 'include' }); if (!resp.ok) throw new Error(resp.status); return resp.json(); } async function getLatestCommit(repo) { try { const commits = await fetchJson( `${baseUrl}/api/v1/repos/${encodeURIComponent(repo.full_name)}/commits?limit=1&page=1` ); if (commits && commits.length > 0) return commits[0]; } catch (_) {} return null; } async function loadRepos() { let repos; try { const resp = await fetch(`${baseUrl}/api/v1/repos/search?sort=updated&order=desc&limit=12`, { credentials: 'include', }); if (!resp.ok) { const msg = resp.status === 401 || resp.status === 403 ? `Sign in to see repositories (HTTP ${resp.status})` : `API error: HTTP ${resp.status}`; grid.innerHTML = `
${msg}. Browse manually β†’
`; return; } const json = await resp.json(); repos = json.data || json; } catch (e) { console.error('Gitea landing: repo fetch failed', e); grid.innerHTML = `
Could not load repositories (${e.message}). Browse manually β†’
`; return; } if (!repos || repos.length === 0) { grid.innerHTML = `
No public repositories found.
`; return; } // Sort by updated desc (API may already do this) repos.sort((a, b) => new Date(b.updated) - new Date(a.updated)); // Render cards immediately, then fill in commits async grid.innerHTML = ''; for (const repo of repos) { const card = document.createElement('a'); card.className = 'repo-card'; card.href = `${baseUrl}/${escapeHtml(repo.full_name)}`; card.dataset.repoName = repo.full_name; card.innerHTML = `
${langDot(repo.language)} ${escapeHtml(repo.name)} ${repo.private ? 'private' : ''}
${escapeHtml(repo.description) || 'No description'}
${repo.language ? `${langDot(repo.language)} ${escapeHtml(repo.language)}` : ''} ⭐ ${repo.stars_count || 0} 🍴 ${repo.forks_count || 0} πŸ•’ ${timeAgo(repo.updated)}
loading commit…
`.trim(); grid.appendChild(card); } // Fetch commits in parallel await Promise.all(repos.map(async (repo) => { const commit = await getLatestCommit(repo); const el = document.getElementById(`commit-${CSS.escape(repo.full_name)}`); if (!el) return; if (commit) { const msg = commit.commit?.message?.split('\n')[0] || ''; const when = timeAgo(commit.commit?.committer?.date || commit.created); el.innerHTML = ` ${escapeHtml(msg)} ${when} `; } else { el.innerHTML = `no commits visible`; } })); } loadRepos(); })(); (async function loadActivity() { const feed = document.getElementById('activity-feed'); if (!feed) return; const baseUrl = window.GITEA_SUB_URL || ''; const username = 'alex'; function timeAgo(dateStr) { const diff = (Date.now() - new Date(dateStr)) / 1000; if (diff < 60) return 'just now'; if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; if (diff < 2592000) return Math.floor(diff / 86400) + 'd ago'; if (diff < 31536000) return Math.floor(diff / 2592000) + 'mo ago'; return Math.floor(diff / 31536000) + 'y ago'; } function esc(str) { return (str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } const opIcon = { commit:'πŸ“', push:'πŸ“€', mirror_sync_push:'πŸ”„', create_repo:'πŸ“', fork:'🍴', open_issue:'πŸ”΄', close_issue:'🟒', reopen_issue:'πŸ”΄', comment_issue:'πŸ’¬', comment_pull:'πŸ’¬', open_pull_request:'πŸ”€', close_pull_request:'πŸ”€', merge_pull_request:'βœ…', approve_pull_request:'βœ…', create_branch:'🌿', delete_branch:'🌿', create_tag:'🏷️', delete_tag:'🏷️', }; let events; try { const resp = await fetch( `${baseUrl}/api/v1/users/${username}/activities/feeds?only-performed-by=true&limit=20`, { credentials: 'include' } ); if (!resp.ok) { feed.innerHTML = `
Activity unavailable (HTTP ${resp.status})
`; return; } events = await resp.json(); } catch (e) { console.error('activity feed error', e); feed.innerHTML = `
Could not load activity
`; return; } if (!events || events.length === 0) { feed.innerHTML = `
No public activity yet.
`; return; } feed.innerHTML = ''; for (const ev of events) { const repoFull = ev.repo ? (ev.repo.full_name || ev.repo.name || '') : ''; const repoUrl = repoFull ? `${baseUrl}/${esc(repoFull)}` : '#'; const repoLink = repoFull ? `${esc(repoFull)}` : 'unknown repo'; const op = ev.op_type || ''; const icon = opIcon[op] || '⚑'; const when = timeAgo(ev.created); let headline = ''; let commitsHtml = ''; if (op === 'commit' || op === 'push' || op === 'mirror_sync_push') { let branch = ev.ref_name || ''; if (branch.startsWith('refs/heads/')) branch = branch.slice(11); const branchUrl = branch ? `${repoUrl}/src/branch/${esc(branch)}` : repoUrl; headline = `Pushed to ${esc(branch) || 'branch'} in ${repoLink}`; let commits = []; try { const c = typeof ev.content === 'string' ? JSON.parse(ev.content) : (ev.content || {}); commits = c.Commits || []; } catch(_) {} if (commits.length > 0) { commitsHtml = '
' + commits.slice(0, 3).map(c => { const sha = (c.Sha1 || '').slice(0, 7); const msg = esc((c.Message || '').split('\n')[0]); return `
${sha} ${msg}
`; }).join('') + (commits.length > 3 ? `
+${commits.length - 3} more
` : '') + '
'; } } else if (op === 'create_repo') { headline = `Created repository ${repoLink}`; } else if (op === 'fork') { headline = `Forked ${repoLink}`; } else if (op === 'open_issue') { headline = `Opened an issue in ${repoLink}`; } else if (op === 'close_issue') { headline = `Closed an issue in ${repoLink}`; } else if (op === 'open_pull_request') { headline = `Opened a pull request in ${repoLink}`; } else if (op === 'merge_pull_request') { headline = `Merged a pull request in ${repoLink}`; } else if (op === 'create_branch') { headline = `Created a branch in ${repoLink}`; } else if (op === 'create_tag') { headline = `Created a tag in ${repoLink}`; } else { headline = `Activity in ${repoLink}`; } const item = document.createElement('div'); item.className = 'activity-item'; item.innerHTML = `
${icon}
${headline}
${commitsHtml}
${when} `; feed.appendChild(item); } })();