Fetch the repository succeeded.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitHub Stars - myOpenSourceCollection</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #151515;
color: #eaeaea;
font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
font-size: 14px;
line-height: 1.5;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.container { width: 90%; max-width: 1400px; margin: 0 auto; padding: 20px 40px; }
header {
border-bottom: 1px dashed #b5e853;
padding-bottom: 20px;
margin-bottom: 40px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
h1 {
font-size: 28px;
color: #b5e853;
font-weight: bold;
letter-spacing: -1px;
text-shadow: 0 1px 1px rgba(0,0,0,0.1), 0 0 5px rgba(181,232,83,0.1), 0 0 10px rgba(181,232,83,0.1);
}
h1::before { content: "./ "; font-size: 22px; }
.back-link {
display: inline-block;
color: rgba(255,255,255,0.8);
text-decoration: none;
padding: 8px 18px;
border-radius: 50px;
border: 2px solid rgba(0,0,0,0.7);
border-top: 2px solid #000;
font-family: Helvetica, Arial, sans-serif;
font-weight: bold;
font-size: 11px;
text-shadow: 0 -1px 0 rgba(0,0,0,0.75);
background: -webkit-linear-gradient(top, rgba(40,40,40,0.3), rgba(35,35,35,0.3) 50%, rgba(10,10,10,0.3) 50%, rgba(0,0,0,0.3));
transition: all 0.3s;
}
.back-link:hover {
background: -webkit-linear-gradient(top, rgba(40,40,40,0.6), rgba(35,35,35,0.6) 50%, rgba(10,10,10,0.8) 50%, rgba(0,0,0,0.8));
}
.subtitle { color: #aaa; font-size: 16px; font-weight: 300; }
.controls { margin-bottom: 30px; }
.search-box {
width: 100%;
padding: 15px;
background: rgba(0,0,0,0.9);
border: 1px solid rgba(255,255,255,0.15);
color: #b5e853;
font-family: inherit;
font-size: 14px;
margin-bottom: 20px;
border-radius: 2px;
}
.search-box:focus { outline: none; box-shadow: 0 0 10px rgba(181,232,83,0.3); border-color: #b5e853; }
.search-box::placeholder { color: #888; }
.filter-bar { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; }
.filter-btn {
background: transparent;
border: 1px dashed #b5e853;
color: #aaa;
padding: 8px 16px;
cursor: pointer;
font-family: inherit;
font-size: 0.9em;
transition: all 0.3s;
}
.filter-btn:hover { color: #b5e853; }
.filter-btn.active { background: #b5e853; color: #151515; border-color: #b5e853; border-style: solid; }
.sort-bar { display: flex; align-items: center; gap: 10px; color: #aaa; font-size: 0.9em; }
.sort-btn {
background: transparent;
border: none;
color: #aaa;
cursor: pointer;
font-family: inherit;
font-size: 1em;
padding: 5px 10px;
transition: color 0.3s;
}
.sort-btn:hover, .sort-btn.active { color: #b5e853; }
.stats { color: #aaa; margin-bottom: 20px; font-size: 0.9em; }
.repo-list { display: grid; gap: 15px; }
.repo-card {
background: rgba(0,0,0,0.9);
border: 1px solid rgba(255,255,255,0.15);
padding: 20px;
transition: all 0.3s;
text-decoration: none;
display: block;
border-radius: 2px;
}
.repo-card:hover {
border-color: #b5e853;
box-shadow: 0 0 15px rgba(181,232,83,0.2);
transform: translateY(-2px);
}
.repo-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
gap: 10px;
}
.repo-name {
color: #b5e853;
font-size: 1.1em;
font-weight: bold;
word-break: break-word;
text-shadow: 0 1px 1px rgba(0,0,0,0.1), 0 0 5px rgba(181,232,83,0.1), 0 0 10px rgba(181,232,83,0.1);
}
.repo-row { display: flex; align-items: center; gap: 15px; }
.repo-idx { color: #b5e853; font-size: 0.9em; min-width: 2em; }
.repo-stars { color: #f4bf75; white-space: nowrap; font-size: 0.9em; }
.repo-desc { color: #eaeaea; margin-bottom: 10px; font-size: 0.9em; }
.repo-meta { display: flex; flex-wrap: wrap; gap: 15px; font-size: 0.8em; color: #bbb; }
.lang-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.repo-lang { color: #63c0f5; text-shadow: 0 0 5px rgba(104,182,255,0.5); }
.repo-topics { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.topic-tag { background: #2a2a2a; color: #bbb; padding: 2px 8px; font-size: 0.75em; border-radius: 2px; }
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 30px;
flex-wrap: wrap;
}
.page-btn {
background: transparent;
border: 1px solid rgba(255,255,255,0.15);
color: #aaa;
padding: 8px 14px;
cursor: pointer;
font-family: inherit;
font-size: 0.9em;
transition: all 0.3s;
border-radius: 2px;
}
.page-btn:hover { border-color: #b5e853; color: #b5e853; }
.page-btn.active { background: #b5e853; color: #151515; border-color: #b5e853; }
.page-btn.disabled { opacity: 0.3; cursor: default; pointer-events: none; }
.page-info { color: #aaa; font-size: 0.85em; }
footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px dashed #b5e853;
color: #aaa;
font-size: 0.85em;
text-align: center;
}
.loading { text-align: center; padding: 50px; color: #aaa; }
.loading::after { content: '...'; animation: dots 1.5s infinite; }
@keyframes dots {
0%, 20% { content: '.'; }
40% { content: '..'; }
60%, 100% { content: '...'; }
}
.error { text-align: center; padding: 50px; color: #ac4142; }
.no-results { text-align: center; padding: 50px; color: #aaa; }
@media (max-width: 600px) {
.container { padding: 15px; }
h1 { font-size: 1.4em; }
.repo-card { padding: 15px; }
.page-btn { padding: 6px 10px; font-size: 0.85em; }
}
</style>
</head>
<body>
<div class="container">
<header>
<div>
<h1>GitHub Stars</h1>
<div class="subtitle">@think3r's starred repositories</div>
</div>
<a href="/" class="back-link">← Back to Home</a>
</header>
<div class="controls">
<input type="text" class="search-box" id="searchInput" placeholder="Search repositories by name, description, or topics...">
<div class="filter-bar" id="filterBar">
<button class="filter-btn active" data-lang="all">All</button>
</div>
<div class="sort-bar">
<span>Sort by:</span>
<button class="sort-btn active" data-sort="stars">⭐ Stars</button>
<button class="sort-btn" data-sort="name">Name</button>
<button class="sort-btn" data-sort="pushed">Pushed</button>
<button class="sort-btn" data-sort="updated">Updated</button>
</div>
</div>
<div class="stats" id="stats"></div>
<div class="repo-list" id="repoList">
<div class="loading">Loading repositories</div>
</div>
<div class="pagination" id="pagination"></div>
<footer>
<p>Data auto-generated from GitHub API · Last updated: <span id="updateTime">-</span></p>
</footer>
</div>
<script>
const PAGE_SIZE = 50;
let allRepos = [];
let currentFilter = 'all';
let currentSort = 'stars';
let currentSearch = '';
let currentPage = 1;
let dataTimestamp = '';
const LANG_COLORS = {
'C': '#555555', 'C++': '#f34b7d', 'C#': '#178600', 'Go': '#00ADD8',
'Java': '#b07219', 'JavaScript': '#f1e05a', 'Kotlin': '#A97BFF',
'Lua': '#000080', 'Objective-C': '#438eff', 'PHP': '#4F5D95',
'Python': '#3572A5', 'R': '#198CE7', 'Ruby': '#701516',
'Rust': '#dea584', 'Scala': '#c22d40', 'Shell': '#89e051',
'Swift': '#F05138', 'TypeScript': '#2b7489', 'Vim script': '#199f4d',
'Vue': '#41b883', 'Zig': '#ec915c', 'Dart': '#00B4AB',
'Elixir': '#6e4a7e', 'Erlang': '#B83998', 'Haskell': '#29b544',
'Perl': '#0298c3', 'PowerShell': '#012456', 'SQL': '#e38c00',
'Assembly': '#6E4C13', 'Clojure': '#db5855', 'CoffeeScript': '#244776',
'F#': '#b845fc', 'Groovy': '#e69f56', 'Julia': '#a270ba',
'Matlab': '#e16737', 'OCaml': '#3be133', 'Racket': '#ae17ff',
'Sass': '#a53b70', 'Solidity': '#AA6746', 'Tcl': '#e4cc98',
'Wasm': '#654ff0', 'CSS': '#563d7c', 'HTML': '#e34c26',
'Markdown': '#083fa1', 'YAML': '#cb171e', 'JSON': '#292929',
'Makefile': '#427819', 'Nix': '#7e7eff', 'D': '#ba595e',
'Vala': '#fbe5cd', 'Apex': '#1798c1', 'WebAssembly': '#654ff0',
};
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
}
function safeUrl(url) {
if (!url) return '';
try {
const parsed = new URL(url);
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return '';
return url;
} catch { return ''; }
}
function formatStars(num) {
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
return num.toString();
}
function formatDate(dateStr) {
return new Date(dateStr).toISOString().split('T')[0];
}
function getLangColor(lang) {
return LANG_COLORS[lang] || '#8b8b8b';
}
function getFilteredRepos() {
let filtered = allRepos.filter(repo => {
if (currentFilter !== 'all' && (repo.language || 'Other') !== currentFilter) return false;
if (currentSearch) {
const s = currentSearch.toLowerCase();
return repo.full_name.toLowerCase().includes(s)
|| (repo.description || '').toLowerCase().includes(s)
|| (repo.topics || []).some(t => t.toLowerCase().includes(s));
}
return true;
});
filtered.sort((a, b) => {
if (currentSearch) {
const s = currentSearch.toLowerCase();
const maxStars = Math.max(...filtered.map(r => r.stargazers_count), 1);
const logMax = Math.log(maxStars + 1);
const relA = a.full_name.toLowerCase().includes(s) ? 2
: (a.description || '').toLowerCase().includes(s) ? 1 : 0;
const relB = b.full_name.toLowerCase().includes(s) ? 2
: (b.description || '').toLowerCase().includes(s) ? 1 : 0;
const starsScoreA = Math.log(a.stargazers_count + 1) / logMax;
const starsScoreB = Math.log(b.stargazers_count + 1) / logMax;
let wRel, wStars;
switch (currentSort) {
case 'stars': wRel = 3; wStars = 5; break;
case 'name': wRel = 5; wStars = 2; break;
case 'pushed': wRel = 3; wStars = 4; break;
case 'updated': wRel = 3; wStars = 4; break;
default: wRel = 3; wStars = 5; break;
}
const scoreA = relA * wRel + starsScoreA * wStars;
const scoreB = relB * wRel + starsScoreB * wStars;
if (scoreA !== scoreB) return scoreB - scoreA;
switch (currentSort) {
case 'stars': return b.stargazers_count - a.stargazers_count;
case 'name': return a.full_name.localeCompare(b.full_name);
case 'pushed': return new Date(b.pushed_at) - new Date(a.pushed_at);
case 'updated': return new Date(b.updated_at) - new Date(a.updated_at);
default: return 0;
}
}
switch (currentSort) {
case 'stars': return b.stargazers_count - a.stargazers_count;
case 'name': return a.full_name.localeCompare(b.full_name);
case 'pushed': return new Date(b.pushed_at) - new Date(a.pushed_at);
case 'updated': return new Date(b.updated_at) - new Date(a.updated_at);
default: return 0;
}
});
return filtered;
}
function saveState() {
const params = new URLSearchParams();
if (currentSearch) params.set('search', currentSearch);
if (currentFilter !== 'all') params.set('lang', currentFilter);
if (currentSort !== 'stars') params.set('sort', currentSort);
if (currentPage !== 1) params.set('page', currentPage);
const hash = params.toString();
history.replaceState(null, '', hash ? '#' + hash : location.pathname);
}
function loadState() {
const hash = location.hash.slice(1);
if (!hash) return;
const params = new URLSearchParams(hash);
if (params.has('search')) {
currentSearch = params.get('search');
document.getElementById('searchInput').value = currentSearch;
}
if (params.has('lang')) currentFilter = params.get('lang');
if (params.has('sort')) currentSort = params.get('sort');
if (params.has('page')) currentPage = parseInt(params.get('page')) || 1;
}
function applyStateToUI() {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.lang === currentFilter);
});
document.querySelectorAll('.sort-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.sort === currentSort);
});
}
function renderFilters() {
const langs = {};
allRepos.forEach(repo => {
const lang = repo.language || 'Other';
langs[lang] = (langs[lang] || 0) + 1;
});
const sortedLangs = Object.entries(langs).sort((a, b) => b[1] - a[1]).slice(0, 8);
const filterBar = document.getElementById('filterBar');
filterBar.innerHTML = '<button class="filter-btn' + (currentFilter === 'all' ? ' active' : '') + '" data-lang="all">All (' + allRepos.length + ')</button>';
sortedLangs.forEach(([lang, count]) => {
const btn = document.createElement('button');
btn.className = 'filter-btn' + (currentFilter === lang ? ' active' : '');
btn.dataset.lang = lang;
const dot = '<span class="lang-dot" style="background:' + getLangColor(lang) + '"></span>';
btn.innerHTML = dot + escapeHtml(lang) + ' (' + count + ')';
filterBar.appendChild(btn);
});
filterBar.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentFilter = btn.dataset.lang;
currentPage = 1;
saveState();
renderFilters();
renderRepos();
});
});
}
function renderPagination(filtered) {
const totalPages = Math.ceil(filtered.length / PAGE_SIZE);
const pagEl = document.getElementById('pagination');
if (totalPages <= 1) { pagEl.innerHTML = ''; return; }
let html = '';
html += '<button class="page-btn' + (currentPage === 1 ? ' disabled' : '') + '" data-page="prev">← Prev</button>';
const maxVisible = 7;
let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
let end = Math.min(totalPages, start + maxVisible - 1);
if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
if (start > 1) {
html += '<button class="page-btn" data-page="1">1</button>';
if (start > 2) html += '<span class="page-info">...</span>';
}
for (let i = start; i <= end; i++) {
html += '<button class="page-btn' + (i === currentPage ? ' active' : '') + '" data-page="' + i + '">' + i + '</button>';
}
if (end < totalPages) {
if (end < totalPages - 1) html += '<span class="page-info">...</span>';
html += '<button class="page-btn" data-page="' + totalPages + '">' + totalPages + '</button>';
}
html += '<button class="page-btn' + (currentPage === totalPages ? ' disabled' : '') + '" data-page="next">Next →</button>';
html += '<span class="page-info">Page ' + currentPage + ' / ' + totalPages + '</span>';
pagEl.innerHTML = html;
pagEl.querySelectorAll('.page-btn:not(.disabled)').forEach(btn => {
btn.addEventListener('click', () => {
const val = btn.dataset.page;
if (val === 'prev') currentPage = Math.max(1, currentPage - 1);
else if (val === 'next') currentPage = Math.min(totalPages, currentPage + 1);
else currentPage = parseInt(val);
saveState();
renderRepos();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
}
function renderRepos() {
const filtered = getFilteredRepos();
const totalPages = Math.ceil(filtered.length / PAGE_SIZE);
if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;
const startIdx = (currentPage - 1) * PAGE_SIZE;
const pageItems = filtered.slice(startIdx, startIdx + PAGE_SIZE);
document.getElementById('stats').textContent = 'Showing ' + (filtered.length === 0 ? 0 : startIdx + 1) + '-' + Math.min(startIdx + PAGE_SIZE, filtered.length) + ' of ' + filtered.length + ' repositories (total: ' + allRepos.length + ')';
const repoList = document.getElementById('repoList');
if (filtered.length === 0) {
repoList.innerHTML = '<div class="no-results">No repositories found</div>';
renderPagination(filtered);
return;
}
repoList.innerHTML = pageItems.map((repo, i) => {
const topics = (repo.topics || []).slice(0, 5)
.map(t => '<span class="topic-tag">' + escapeHtml(t) + '</span>').join('');
const lang = repo.language || 'Other';
const langColor = getLangColor(lang);
return '<div class="repo-row">'
+ '<span class="repo-idx">' + (i + 1) + '.</span>'
+ '<a href="' + escapeHtml(safeUrl(repo.html_url)) + '" target="_blank" class="repo-card">'
+ '<div class="repo-header">'
+ '<span class="repo-name">' + escapeHtml(repo.full_name) + '</span>'
+ '<span class="repo-stars">⭐ ' + formatStars(repo.stargazers_count) + '</span>'
+ '</div>'
+ '<div class="repo-desc">' + escapeHtml(repo.description || 'No description') + '</div>'
+ '<div class="repo-meta">'
+ '<span class="repo-lang"><span class="lang-dot" style="background:' + langColor + '"></span>' + escapeHtml(lang) + '</span>'
+ '<span>Pushed: ' + formatDate(repo.pushed_at) + '</span>'
+ '<span>Updated: ' + formatDate(repo.updated_at) + '</span>'
+ '</div>'
+ (topics ? '<div class="repo-topics">' + topics + '</div>' : '')
+ '</a>'
+ '</div>';
}).join('');
renderPagination(filtered);
saveState();
}
document.getElementById('searchInput').addEventListener('input', e => {
currentSearch = e.target.value;
currentPage = 1;
saveState();
renderRepos();
});
document.querySelectorAll('.sort-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentSort = btn.dataset.sort;
currentPage = 1;
saveState();
applyStateToUI();
renderRepos();
});
});
fetch('./stars-data.json')
.then(res => {
if (!res.ok) throw new Error('Failed to load data');
return res.json();
})
.then(data => {
allRepos = data.repos;
dataTimestamp = data.timestamp;
document.getElementById('updateTime').textContent = new Date(dataTimestamp).toISOString().slice(0, 16).replace("T", " ");
loadState();
applyStateToUI();
renderFilters();
renderRepos();
})
.catch(err => {
document.getElementById('repoList').innerHTML =
'<div class="error">Failed to load repositories: ' + err.message + '</div>';
});
</script>
</body>
</html>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。