# Projects

> Open-source software and technical projects built by Gabriel.

- Site: https://gabrielkoo.com
- HTML: https://gabrielkoo.com/projects/

Open-source projects on [GitHub](https://github.com/gabrielkoo).

<div style="margin-bottom:1rem;font-size:0.9em">
  Sort by:
  <button id="btn-stars" onclick="sortBy('stars')" style="margin:0 4px;padding:3px 10px;cursor:pointer;font-weight:bold;text-decoration:underline">⭐ Stars</button>
  <button id="btn-forks" onclick="sortBy('forks')" style="margin:0 4px;padding:3px 10px;cursor:pointer">🍴 Forks</button>
  <button id="btn-updated" onclick="sortBy('updated')" style="margin:0 4px;padding:3px 10px;cursor:pointer">🕐 Updated</button>
</div>

<div id="projects-list"><p><em>Loading...</em></p></div>

<script>
var allRepos = [];

function escapeHtml(value) {
  return String(value == null ? '' : value).replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
  })[c]);
}

function renderRepos(repos) {
  document.getElementById('projects-list').innerHTML = repos.map(r =>
    `<div style="margin-bottom:1.8rem;padding-bottom:1.2rem;border-bottom:1px solid #eee">
      <h3 style="margin-bottom:0.2rem"><a href="${escapeHtml(r.url)}" target="_blank" rel="noopener">${escapeHtml(r.name)}</a></h3>
      <p style="margin:0.2rem 0 0.5rem;color:#666;font-size:0.95em">${escapeHtml(r.description)}</p>
      <span>
        <img src="https://img.shields.io/github/stars/gabrielkoo/${encodeURIComponent(r.name)}?style=flat" alt="stars" loading="lazy">
        <img src="https://img.shields.io/github/forks/gabrielkoo/${encodeURIComponent(r.name)}?style=flat" alt="forks" loading="lazy">
        ${r.language ? `<img src="https://img.shields.io/badge/-${encodeURIComponent(r.language)}-555?style=flat" alt="${escapeHtml(r.language)}" loading="lazy">` : ''}
      </span>
    </div>`
  ).join('') || '<p>No projects found.</p>';
}

function sortBy(key) {
  ['stars','forks','updated'].forEach(k => {
    var btn = document.getElementById('btn-' + k);
    btn.style.fontWeight = k === key ? 'bold' : 'normal';
    btn.style.textDecoration = k === key ? 'underline' : 'none';
  });
  renderRepos(allRepos.slice().sort((a, b) => {
    if (key === 'stars')   return b.stars - a.stars;
    if (key === 'forks')   return b.forks - a.forks;
    if (key === 'updated') return new Date(b.updated_at) - new Date(a.updated_at);
    return 0;
  }));
}

fetch('/data/projects.json')
  .then(r => r.json())
  .then(repos => { allRepos = repos; sortBy('stars'); })
  .catch(() => {
    document.getElementById('projects-list').innerHTML =
      '<p>Failed to load. <a href="https://github.com/gabrielkoo" target="_blank">View on GitHub →</a></p>';
  });
</script>
