diff --git a/.github/workflows/compute-activity-scores.yml b/.github/workflows/compute-activity-scores.yml new file mode 100644 index 0000000000..90f5e5f011 --- /dev/null +++ b/.github/workflows/compute-activity-scores.yml @@ -0,0 +1,31 @@ +name: Compute Community Activity Scores +on: + schedule: + - cron: '0 0 * * *' # Daily at midnight + workflow_dispatch: # Manual trigger +jobs: + compute-scores: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.2.0 + with: + node-version: '18' + - name: Run activity score computation + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node agent/scripts/compute-activity-scores.js + - name: Commit updated scores + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b2b3e36ac28eb95 # v7.0.8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: update community activity scores" + title: "chore: update community activity scores" + body: "Automated daily update of community activity scores." + branch: "auto/update-community-activity-scores" + delete-branch: true + add-paths: data/community_activity.json diff --git a/README.md b/README.md index e28730b3f4..b0e940f57e 100644 --- a/README.md +++ b/README.md @@ -486,6 +486,27 @@ The Edge Function proxies GitHub API calls so your token never hits the client. | `GET /api/github?repo=owner/repo&gfi=1` | Good First Issue count only (faster, cached separately) | | `GET /api/github?repo=owner/repo&gfi=1&issues=1` | Full list of up to 30 open Good First Issues | +--- +## 🏆 Community Activity Score + +Each organization is scored daily (0–100) based on GitHub activity signals fetched by `.github/workflows/compute-activity-scores.yml`: + +| Signal | Weight | Description | +|--------|--------|-------------| +| Issue resolution time | 30% | Average days to close issues | +| Commit frequency | 20% | Commits in last 90 days | +| PR merge rate | 15% | % of PRs merged in 90 days | +| Repository freshness | 20% | Days since last push | +| Stars score | 15% | Scaled stargazers count | + +**Tiers:** +- 🟢 **Very Active** — score 75–100 +- 🔵 **Active** — score 50–74 +- 🟡 **Moderate** — score 25–49 +- 🔴 **Low Activity** — score 0–24 + +Scores are stored in `data/community_activity.json` and updated daily. Organizations with no GitHub data are excluded from scoring. + All responses are cached in-memory for **1 hour** on the Edge runtime. # 🚀 Official Open Source Program Project diff --git a/agent/scripts/compute-activity-scores.js b/agent/scripts/compute-activity-scores.js new file mode 100644 index 0000000000..e2f58d4306 --- /dev/null +++ b/agent/scripts/compute-activity-scores.js @@ -0,0 +1,159 @@ +// @ts-check +/* eslint-env node */ +// Computes community activity scores for all GSoC orgs +// Run via GitHub Actions daily + +const fs = require('node:fs'); +const path = require('node:path'); +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const OUTPUT_FILE = path.join(__dirname, '../../data/community_activity.json'); + +if (!GITHUB_TOKEN) { + console.error('❌ GITHUB_TOKEN is required'); + process.exit(1); +} + +const headers = { + 'Authorization': `token ${GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'gsoc-org-finder' +}; + +// Load orgs from org.js +const orgDataRaw = fs.readFileSync( + path.join(__dirname, '../../src/js/org.js'), 'utf8' +); + +// Extract github field from each org +const githubRepos = []; +const orgMatches = orgDataRaw.matchAll(/name:\s*"([^"]+)"[^}]+github:\s*"([^"]+)"/g); +for (const m of orgMatches) { + const name = m[1]; + const repo = m[2]; + if (repo && repo.includes('/')) { + githubRepos.push({ name, repo }); + } else { + console.warn(`Skipping bare owner (no repo path): ${name} → "${repo}"`); + } +} + +async function fetchJSON(url) { + const res = await fetch(url, { headers }); + if (!res.ok) return null; + return res.json(); +} + +function computeScore({ issueResponseDays, commitFrequency, prMergeRate, ideasFreshnessDays, starsGrowth }) { + const issueScore = Math.max(0, 100 - (issueResponseDays * 5)); + const commitScore = Math.min(100, commitFrequency * 10); + const prScore = Math.min(100, prMergeRate * 100); + const ideasScore = Math.max(0, 100 - (ideasFreshnessDays * 0.5)); + const growthScore = Math.min(100, starsGrowth * 2); + + return Math.round( + issueScore * 0.3 + + commitScore * 0.2 + + prScore * 0.15 + + ideasScore * 0.2 + + growthScore * 0.15 + ); +} + +function getTier(score) { + if (score >= 80) return { tier: 'very-active', label: '🟢 Very Active' }; + if (score >= 60) return { tier: 'active', label: '🔵 Active' }; + if (score >= 40) return { tier: 'moderate', label: '🟡 Moderate' }; + return { tier: 'low', label: '🔴 Low Activity' }; +} + +async function analyzeOrg({ name, repo }) { + try { + const since = new Date(Date.now() - 90 * 86400000).toISOString(); + + const commits = await fetchJSON( + `https://api.github.com/repos/${repo}/commits?since=${since}&per_page=100` + ); + const commitFrequency = Array.isArray(commits) ? commits.length / 90 : 0; + + const issues = await fetchJSON( + `https://api.github.com/repos/${repo}/issues?state=closed&since=${since}&per_page=100` + ); + let issueResponseDays = null; + if (Array.isArray(issues) && issues.length > 0) { + const responseTimes = issues + .filter(i => i.created_at && i.closed_at && !i.pull_request) + .map(i => (+new Date(i.closed_at) - +new Date(i.created_at)) / 86400000); + if (responseTimes.length > 0) { + issueResponseDays = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length; + } + } + + // Fetch PRs + const prs = await fetchJSON( + `https://api.github.com/repos/${repo}/pulls?state=closed&per_page=100` + ); + let prMergeRate = null; + if (Array.isArray(prs) && prs.length > 0) { + const recentPrs = prs.filter(p => p.closed_at && new Date(p.closed_at) >= new Date(since)); + if (recentPrs.length > 0) { + const merged = recentPrs.filter(p => p.merged_at).length; + prMergeRate = merged / recentPrs.length; + } + } + + const repoData = await fetchJSON(`https://api.github.com/repos/${repo}`); + const starsGrowth = repoData ? Math.min(50, repoData.stargazers_count / 1000) : 0; + + const ideasFreshnessDays = repoData?.pushed_at + ? (Date.now() - new Date(repoData.pushed_at)) / 86400000 + : 60; + + if (issueResponseDays === null || prMergeRate === null) return null; + + const score = computeScore({ + issueResponseDays: issueResponseDays, + commitFrequency, + prMergeRate: prMergeRate, + ideasFreshnessDays, + starsGrowth + }); + const { tier, label } = getTier(score); + + return { + score, + tier, + label, + signals: { + issueResponseDays: issueResponseDays === null ? null : Math.round(issueResponseDays), + commitFrequency: Math.round(commitFrequency * 10) / 10, + prMergeRate: prMergeRate === null ? null : Math.round(prMergeRate * 100), + ideasFreshnessDays: Math.round(ideasFreshnessDays), + starsGrowth: Math.round(starsGrowth * 10) / 10 + }, + lastUpdated: new Date().toISOString() + }; + } catch (e) { + console.error(`Error analyzing ${name}:`, e.message); + return null; + } +} + +async function main() { + console.log(`Analyzing ${githubRepos.length} orgs...`); + const results = {}; + + for (const org of githubRepos) { + console.log(`Processing: ${org.name}`); + const data = await analyzeOrg(org); + if (data) results[org.name] = data; + await new Promise(r => setTimeout(r, 500)); + } + + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(results, null, 2)); + console.log(`✅ Done! Wrote ${Object.keys(results).length} orgs to community_activity.json`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/data/community_activity.json b/data/community_activity.json new file mode 100644 index 0000000000..aa641fe4b5 --- /dev/null +++ b/data/community_activity.json @@ -0,0 +1,1406 @@ +{ + "AboutCode": { + "score": 27, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 51, + "commitFrequency": 0.4, + "prMergeRate": 39, + "ideasFreshnessDays": 10, + "starsGrowth": 2.6 + }, + "lastUpdated": "2026-07-19T07:10:09.187Z" + }, + "Accord Project": { + "score": 30, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 21, + "commitFrequency": 0.4, + "prMergeRate": 65, + "ideasFreshnessDays": 6, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:10:14.210Z" + }, + "AFLplusplus": { + "score": 38, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 137, + "commitFrequency": 1.1, + "prMergeRate": 91, + "ideasFreshnessDays": 1, + "starsGrowth": 6.7 + }, + "lastUpdated": "2026-07-19T07:10:18.306Z" + }, + "Alaska": { + "score": 60, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 0.1, + "prMergeRate": 77, + "ideasFreshnessDays": 3, + "starsGrowth": 0.3 + }, + "lastUpdated": "2026-07-19T07:10:22.244Z" + }, + "AnkiDroid": { + "score": 60, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 5, + "commitFrequency": 1.1, + "prMergeRate": 83, + "ideasFreshnessDays": 1, + "starsGrowth": 11.4 + }, + "lastUpdated": "2026-07-19T07:10:26.297Z" + }, + "Apache Software Foundation": { + "score": 63, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 0, + "ideasFreshnessDays": 1, + "starsGrowth": 43.7 + }, + "lastUpdated": "2026-07-19T07:10:33.980Z" + }, + "API Dash": { + "score": 25, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 36, + "commitFrequency": 0.6, + "prMergeRate": 28, + "ideasFreshnessDays": 9, + "starsGrowth": 2.9 + }, + "lastUpdated": "2026-07-19T07:10:39.516Z" + }, + "ArduPilot": { + "score": 64, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 75, + "ideasFreshnessDays": 0, + "starsGrowth": 15.5 + }, + "lastUpdated": "2026-07-19T07:10:44.005Z" + }, + "Boa": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 90, + "commitFrequency": 0.4, + "prMergeRate": 65, + "ideasFreshnessDays": 1, + "starsGrowth": 7.4 + }, + "lastUpdated": "2026-07-19T07:10:51.166Z" + }, + "BRL-CAD": { + "score": 26, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 51, + "commitFrequency": 1.1, + "prMergeRate": 20, + "ideasFreshnessDays": 0, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:10:55.672Z" + }, + "cBioPortal for Cancer Genomics": { + "score": 47, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 9, + "commitFrequency": 0.6, + "prMergeRate": 60, + "ideasFreshnessDays": 2, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:11:00.414Z" + }, + "CCExtractor Development": { + "score": 28, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 46, + "commitFrequency": 0.1, + "prMergeRate": 60, + "ideasFreshnessDays": 12, + "starsGrowth": 0.9 + }, + "lastUpdated": "2026-07-19T07:11:04.179Z" + }, + "CERN-HSF": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 73, + "commitFrequency": 0.3, + "prMergeRate": 97, + "ideasFreshnessDays": 2, + "starsGrowth": 0.1 + }, + "lastUpdated": "2026-07-19T07:11:13.087Z" + }, + "CGAL Project": { + "score": 45, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 15, + "commitFrequency": 1.1, + "prMergeRate": 90, + "ideasFreshnessDays": 4, + "starsGrowth": 6 + }, + "lastUpdated": "2026-07-19T07:11:17.911Z" + }, + "checkstyle": { + "score": 66, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 90, + "ideasFreshnessDays": 0, + "starsGrowth": 9 + }, + "lastUpdated": "2026-07-19T07:11:21.485Z" + }, + "CircuitVerse.org": { + "score": 54, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 40, + "ideasFreshnessDays": 1, + "starsGrowth": 1.2 + }, + "lastUpdated": "2026-07-19T07:11:26.513Z" + }, + "CNCF": { + "score": 66, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1, + "prMergeRate": 81, + "ideasFreshnessDays": 1, + "starsGrowth": 9.9 + }, + "lastUpdated": "2026-07-19T07:11:30.798Z" + }, + "CRIU": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 32, + "commitFrequency": 1.1, + "prMergeRate": 82, + "ideasFreshnessDays": 3, + "starsGrowth": 3.9 + }, + "lastUpdated": "2026-07-19T07:11:34.517Z" + }, + "D Language Foundation": { + "score": 59, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 6, + "commitFrequency": 1.1, + "prMergeRate": 95, + "ideasFreshnessDays": 0, + "starsGrowth": 3.3 + }, + "lastUpdated": "2026-07-19T07:11:41.553Z" + }, + "Dart": { + "score": 50, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 1.1, + "prMergeRate": 0, + "ideasFreshnessDays": 1, + "starsGrowth": 11.2 + }, + "lastUpdated": "2026-07-19T07:11:46.370Z" + }, + "Data for the Common Good": { + "score": 67, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 92, + "ideasFreshnessDays": 0, + "starsGrowth": 12.3 + }, + "lastUpdated": "2026-07-19T07:11:50.666Z" + }, + "DBpedia": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 854, + "commitFrequency": 0.1, + "prMergeRate": 100, + "ideasFreshnessDays": 4, + "starsGrowth": 0.1 + }, + "lastUpdated": "2026-07-19T07:11:53.739Z" + }, + "dora-rs": { + "score": 56, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 32, + "ideasFreshnessDays": 0, + "starsGrowth": 3.8 + }, + "lastUpdated": "2026-07-19T07:12:09.000Z" + }, + "Eclipse Foundation": { + "score": 64, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 92, + "ideasFreshnessDays": 0, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:12:18.215Z" + }, + "FLARE": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 126, + "commitFrequency": 1.1, + "prMergeRate": 63, + "ideasFreshnessDays": 2, + "starsGrowth": 6.1 + }, + "lastUpdated": "2026-07-19T07:12:29.059Z" + }, + "FOSSology": { + "score": 39, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 16, + "commitFrequency": 1.1, + "prMergeRate": 72, + "ideasFreshnessDays": 2, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:12:36.333Z" + }, + "Fortran-lang": { + "score": 36, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 47, + "commitFrequency": 0.2, + "prMergeRate": 100, + "ideasFreshnessDays": 1, + "starsGrowth": 1.3 + }, + "lastUpdated": "2026-07-19T07:12:42.789Z" + }, + "Free and Open Source Silicon Foundation": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 270, + "commitFrequency": 0.6, + "prMergeRate": 78, + "ideasFreshnessDays": 2, + "starsGrowth": 0 + }, + "lastUpdated": "2026-07-19T07:12:46.672Z" + }, + "FreeCAD": { + "score": 72, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 82, + "ideasFreshnessDays": 0, + "starsGrowth": 32.2 + }, + "lastUpdated": "2026-07-19T07:12:52.407Z" + }, + "Gambit: The package for computation in game theory": { + "score": 51, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 9, + "commitFrequency": 0.9, + "prMergeRate": 89, + "ideasFreshnessDays": 3, + "starsGrowth": 0.6 + }, + "lastUpdated": "2026-07-19T07:12:57.058Z" + }, + "Gemini CLI": { + "score": 57, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 10, + "commitFrequency": 1.1, + "prMergeRate": 25, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:13:03.170Z" + }, + "German Center for Open Source AI": { + "score": 27, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 143, + "commitFrequency": 0.1, + "prMergeRate": 47, + "ideasFreshnessDays": 5, + "starsGrowth": 0.4 + }, + "lastUpdated": "2026-07-19T07:13:15.018Z" + }, + "GNU Radio": { + "score": 28, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 110, + "commitFrequency": 0.1, + "prMergeRate": 44, + "ideasFreshnessDays": 10, + "starsGrowth": 6.2 + }, + "lastUpdated": "2026-07-19T07:13:49.259Z" + }, + "GRAME": { + "score": 50, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 7, + "commitFrequency": 0.2, + "prMergeRate": 64, + "ideasFreshnessDays": 0, + "starsGrowth": 3.1 + }, + "lastUpdated": "2026-07-19T07:13:57.649Z" + }, + "Graphite": { + "score": 67, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 1.1, + "prMergeRate": 84, + "ideasFreshnessDays": 0, + "starsGrowth": 26.6 + }, + "lastUpdated": "2026-07-19T07:14:03.776Z" + }, + "Haskell.org": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 175, + "commitFrequency": 0.6, + "prMergeRate": 76, + "ideasFreshnessDays": 1, + "starsGrowth": 2.9 + }, + "lastUpdated": "2026-07-19T07:14:11.570Z" + }, + "Internet Archive": { + "score": 60, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 1.1, + "prMergeRate": 77, + "ideasFreshnessDays": 0, + "starsGrowth": 6.6 + }, + "lastUpdated": "2026-07-19T07:14:25.061Z" + }, + "Invesalius": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 1207, + "commitFrequency": 1.1, + "prMergeRate": 71, + "ideasFreshnessDays": 0, + "starsGrowth": 0.8 + }, + "lastUpdated": "2026-07-19T07:14:29.291Z" + }, + "JabRef e.V.": { + "score": 61, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 1.1, + "prMergeRate": 84, + "ideasFreshnessDays": 0, + "starsGrowth": 4.4 + }, + "lastUpdated": "2026-07-19T07:14:35.739Z" + }, + "Jenkins": { + "score": 67, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 69, + "ideasFreshnessDays": 1, + "starsGrowth": 25.6 + }, + "lastUpdated": "2026-07-19T07:14:46.899Z" + }, + "Jitsi": { + "score": 73, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 89, + "ideasFreshnessDays": 2, + "starsGrowth": 29.6 + }, + "lastUpdated": "2026-07-19T07:14:50.776Z" + }, + "Joomla!": { + "score": 59, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 5, + "commitFrequency": 1.1, + "prMergeRate": 84, + "ideasFreshnessDays": 1, + "starsGrowth": 5.1 + }, + "lastUpdated": "2026-07-19T07:14:55.193Z" + }, + "Joplin": { + "score": 72, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 66, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:14:59.287Z" + }, + "JSON Schema": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 205, + "commitFrequency": 0.1, + "prMergeRate": 92, + "ideasFreshnessDays": 5, + "starsGrowth": 5.1 + }, + "lastUpdated": "2026-07-19T07:15:03.903Z" + }, + "Kiwix": { + "score": 55, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 8, + "commitFrequency": 1.1, + "prMergeRate": 92, + "ideasFreshnessDays": 1, + "starsGrowth": 1.4 + }, + "lastUpdated": "2026-07-19T07:15:11.348Z" + }, + "Konflux": { + "score": 66, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 98, + "ideasFreshnessDays": 0, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:15:19.355Z" + }, + "Kornia": { + "score": 60, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 6, + "commitFrequency": 1.1, + "prMergeRate": 88, + "ideasFreshnessDays": 0, + "starsGrowth": 11.3 + }, + "lastUpdated": "2026-07-19T07:15:23.627Z" + }, + "Kubeflow": { + "score": 38, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 567, + "commitFrequency": 0.2, + "prMergeRate": 93, + "ideasFreshnessDays": 9, + "starsGrowth": 15.8 + }, + "lastUpdated": "2026-07-19T07:15:31.848Z" + }, + "KubeVirt": { + "score": 64, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 90, + "ideasFreshnessDays": 0, + "starsGrowth": 7 + }, + "lastUpdated": "2026-07-19T07:15:36.966Z" + }, + "Learning Equality": { + "score": 55, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 7, + "commitFrequency": 1.1, + "prMergeRate": 87, + "ideasFreshnessDays": 1, + "starsGrowth": 1.1 + }, + "lastUpdated": "2026-07-19T07:15:44.954Z" + }, + "Learning Unlimited": { + "score": 45, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 11, + "commitFrequency": 1.1, + "prMergeRate": 66, + "ideasFreshnessDays": 1, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:15:49.979Z" + }, + "LLVM Compiler Infrastructure": { + "score": 77, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 89, + "ideasFreshnessDays": 0, + "starsGrowth": 39.4 + }, + "lastUpdated": "2026-07-19T07:16:09.706Z" + }, + "MDAnalysis": { + "score": 32, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 96, + "commitFrequency": 0.4, + "prMergeRate": 75, + "ideasFreshnessDays": 4, + "starsGrowth": 1.6 + }, + "lastUpdated": "2026-07-19T07:16:21.313Z" + }, + "Meshery": { + "score": 64, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 70, + "ideasFreshnessDays": 0, + "starsGrowth": 11.4 + }, + "lastUpdated": "2026-07-19T07:16:25.676Z" + }, + "MetaCall": { + "score": 32, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 43, + "commitFrequency": 1.1, + "prMergeRate": 64, + "ideasFreshnessDays": 1, + "starsGrowth": 1.8 + }, + "lastUpdated": "2026-07-19T07:16:34.292Z" + }, + "Metaflow": { + "score": 47, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 11, + "commitFrequency": 0.9, + "prMergeRate": 62, + "ideasFreshnessDays": 0, + "starsGrowth": 10.2 + }, + "lastUpdated": "2026-07-19T07:16:38.928Z" + }, + "Metasploit": { + "score": 67, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 6, + "commitFrequency": 1.1, + "prMergeRate": 79, + "ideasFreshnessDays": 1, + "starsGrowth": 38.6 + }, + "lastUpdated": "2026-07-19T07:16:43.531Z" + }, + "MIT App Inventor": { + "score": 46, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 12, + "commitFrequency": 0.9, + "prMergeRate": 75, + "ideasFreshnessDays": 1, + "starsGrowth": 1.7 + }, + "lastUpdated": "2026-07-19T07:16:48.755Z" + }, + "Mixxx": { + "score": 64, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 81, + "ideasFreshnessDays": 1, + "starsGrowth": 6.9 + }, + "lastUpdated": "2026-07-19T07:16:53.156Z" + }, + "MoFA Org": { + "score": 26, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 30, + "commitFrequency": 0.3, + "prMergeRate": 36, + "ideasFreshnessDays": 2, + "starsGrowth": 0.3 + }, + "lastUpdated": "2026-07-19T07:17:04.426Z" + }, + "National Resource for Network Biology (NRNB)": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 176, + "commitFrequency": 0.8, + "prMergeRate": 72, + "ideasFreshnessDays": 3, + "starsGrowth": 11.1 + }, + "lastUpdated": "2026-07-19T07:17:11.480Z" + }, + "Neovim": { + "score": 79, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 88, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:17:15.613Z" + }, + "Neuroinformatics Unit": { + "score": 30, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 169, + "commitFrequency": 0.5, + "prMergeRate": 63, + "ideasFreshnessDays": 2, + "starsGrowth": 0.3 + }, + "lastUpdated": "2026-07-19T07:17:21.734Z" + }, + "Neutralinojs": { + "score": 34, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 55, + "commitFrequency": 0.8, + "prMergeRate": 63, + "ideasFreshnessDays": 0, + "starsGrowth": 8.6 + }, + "lastUpdated": "2026-07-19T07:17:26.130Z" + }, + "NixOS Foundation": { + "score": 74, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 94, + "ideasFreshnessDays": 0, + "starsGrowth": 25.5 + }, + "lastUpdated": "2026-07-19T07:17:31.048Z" + }, + "NumFOCUS": { + "score": 71, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 73, + "ideasFreshnessDays": 1, + "starsGrowth": 32.4 + }, + "lastUpdated": "2026-07-19T07:17:37.098Z" + }, + "omegaUp": { + "score": 49, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 11, + "commitFrequency": 0.9, + "prMergeRate": 84, + "ideasFreshnessDays": 0, + "starsGrowth": 0.4 + }, + "lastUpdated": "2026-07-19T07:17:43.334Z" + }, + "Open Food Facts": { + "score": 50, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 8, + "commitFrequency": 1.1, + "prMergeRate": 61, + "ideasFreshnessDays": 1, + "starsGrowth": 1.1 + }, + "lastUpdated": "2026-07-19T07:17:49.296Z" + }, + "Open Science Initiative for Perfusion Imaging": { + "score": 61, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 0.4, + "prMergeRate": 100, + "ideasFreshnessDays": 1, + "starsGrowth": 0 + }, + "lastUpdated": "2026-07-19T07:18:05.753Z" + }, + "Open Transit Software Foundation": { + "score": 67, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 96, + "ideasFreshnessDays": 0, + "starsGrowth": 0.6 + }, + "lastUpdated": "2026-07-19T07:18:15.584Z" + }, + "OpenAstronomy": { + "score": 46, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 12, + "commitFrequency": 1.1, + "prMergeRate": 89, + "ideasFreshnessDays": 10, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:18:23.468Z" + }, + "OpenELIS Global": { + "score": 35, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 27, + "commitFrequency": 1.1, + "prMergeRate": 86, + "ideasFreshnessDays": 1, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:18:32.886Z" + }, + "OpenMRS": { + "score": 63, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 72, + "ideasFreshnessDays": 0, + "starsGrowth": 1.9 + }, + "lastUpdated": "2026-07-19T07:18:37.495Z" + }, + "OpenMS Inc": { + "score": 65, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 95, + "ideasFreshnessDays": 0, + "starsGrowth": 0.6 + }, + "lastUpdated": "2026-07-19T07:18:42.513Z" + }, + "OpenStreetMap": { + "score": 60, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 5, + "commitFrequency": 1.1, + "prMergeRate": 93, + "ideasFreshnessDays": 3, + "starsGrowth": 2.8 + }, + "lastUpdated": "2026-07-19T07:18:47.239Z" + }, + "openSUSE Project": { + "score": 65, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 89, + "ideasFreshnessDays": 2, + "starsGrowth": 1.1 + }, + "lastUpdated": "2026-07-19T07:18:52.241Z" + }, + "OpenWISP": { + "score": 31, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 295, + "commitFrequency": 0.6, + "prMergeRate": 65, + "ideasFreshnessDays": 2, + "starsGrowth": 0.8 + }, + "lastUpdated": "2026-07-19T07:19:02.583Z" + }, + "Oppia Foundation": { + "score": 53, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 5, + "commitFrequency": 1.1, + "prMergeRate": 44, + "ideasFreshnessDays": 1, + "starsGrowth": 6.7 + }, + "lastUpdated": "2026-07-19T07:19:07.913Z" + }, + "OSGeo (Open Source Geospatial Foundation)": { + "score": 63, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 94, + "ideasFreshnessDays": 1, + "starsGrowth": 6 + }, + "lastUpdated": "2026-07-19T07:19:13.843Z" + }, + "PEcAn Project": { + "score": 43, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 16, + "commitFrequency": 1.1, + "prMergeRate": 93, + "ideasFreshnessDays": 3, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:19:23.393Z" + }, + "Pharo Consortium": { + "score": 62, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 92, + "ideasFreshnessDays": 3, + "starsGrowth": 1.5 + }, + "lastUpdated": "2026-07-19T07:19:27.875Z" + }, + "preCICE": { + "score": 32, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 388, + "commitFrequency": 0.4, + "prMergeRate": 75, + "ideasFreshnessDays": 7, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:19:35.531Z" + }, + "Processing Foundation": { + "score": 24, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 2246, + "commitFrequency": 0.1, + "prMergeRate": 16, + "ideasFreshnessDays": 4, + "starsGrowth": 6.5 + }, + "lastUpdated": "2026-07-19T07:19:41.348Z" + }, + "Project Mesa": { + "score": 31, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 481, + "commitFrequency": 0.4, + "prMergeRate": 57, + "ideasFreshnessDays": 1, + "starsGrowth": 3.7 + }, + "lastUpdated": "2026-07-19T07:19:48.157Z" + }, + "Python Software Foundation": { + "score": 80, + "tier": "very-active", + "label": "🟢 Very Active", + "signals": { + "issueResponseDays": 0, + "commitFrequency": 1.1, + "prMergeRate": 89, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:19:52.775Z" + }, + "Rizin": { + "score": 55, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 8, + "commitFrequency": 1.1, + "prMergeRate": 96, + "ideasFreshnessDays": 1, + "starsGrowth": 3.7 + }, + "lastUpdated": "2026-07-19T07:20:14.888Z" + }, + "SageMath": { + "score": 56, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 7, + "commitFrequency": 1.1, + "prMergeRate": 90, + "ideasFreshnessDays": 3, + "starsGrowth": 2.5 + }, + "lastUpdated": "2026-07-19T07:20:33.261Z" + }, + "stdlib": { + "score": 65, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 91, + "ideasFreshnessDays": 0, + "starsGrowth": 5.9 + }, + "lastUpdated": "2026-07-19T07:20:45.409Z" + }, + "Ste||ar group": { + "score": 65, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 92, + "ideasFreshnessDays": 1, + "starsGrowth": 2.9 + }, + "lastUpdated": "2026-07-19T07:20:52.469Z" + }, + "Stichting SU2": { + "score": 33, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 67, + "commitFrequency": 0.2, + "prMergeRate": 79, + "ideasFreshnessDays": 0, + "starsGrowth": 1.8 + }, + "lastUpdated": "2026-07-19T07:20:56.567Z" + }, + "Submitty": { + "score": 49, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 11, + "commitFrequency": 1.1, + "prMergeRate": 85, + "ideasFreshnessDays": 1, + "starsGrowth": 0.8 + }, + "lastUpdated": "2026-07-19T07:21:01.584Z" + }, + "SW360": { + "score": 61, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 1.1, + "prMergeRate": 96, + "ideasFreshnessDays": 2, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:21:10.765Z" + }, + "Swift": { + "score": 80, + "tier": "very-active", + "label": "🟢 Very Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 94, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:21:15.707Z" + }, + "SymPy": { + "score": 55, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 6, + "commitFrequency": 1.1, + "prMergeRate": 49, + "ideasFreshnessDays": 1, + "starsGrowth": 14.8 + }, + "lastUpdated": "2026-07-19T07:21:20.515Z" + }, + "Synfig": { + "score": 29, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 803, + "commitFrequency": 0.3, + "prMergeRate": 52, + "ideasFreshnessDays": 0, + "starsGrowth": 2.3 + }, + "lastUpdated": "2026-07-19T07:21:24.613Z" + }, + "TARDIS RT Collaboration": { + "score": 39, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 15, + "commitFrequency": 0.7, + "prMergeRate": 72, + "ideasFreshnessDays": 0, + "starsGrowth": 0.2 + }, + "lastUpdated": "2026-07-19T07:21:30.760Z" + }, + "The JPF team": { + "score": 25, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 1708, + "commitFrequency": 0, + "prMergeRate": 33, + "ideasFreshnessDays": 3, + "starsGrowth": 0.6 + }, + "lastUpdated": "2026-07-19T07:21:42.966Z" + }, + "The Julia Language": { + "score": 77, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 3, + "commitFrequency": 1.1, + "prMergeRate": 91, + "ideasFreshnessDays": 0, + "starsGrowth": 48.9 + }, + "lastUpdated": "2026-07-19T07:21:46.738Z" + }, + "The Libreswan Project": { + "score": 54, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 6, + "commitFrequency": 1.1, + "prMergeRate": 65, + "ideasFreshnessDays": 0, + "starsGrowth": 1 + }, + "lastUpdated": "2026-07-19T07:21:51.542Z" + }, + "The OpenROAD Initiative": { + "score": 58, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 5, + "commitFrequency": 1.1, + "prMergeRate": 85, + "ideasFreshnessDays": 0, + "starsGrowth": 2.9 + }, + "lastUpdated": "2026-07-19T07:22:11.726Z" + }, + "The P4 Language Consortium": { + "score": 36, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 60, + "commitFrequency": 0.8, + "prMergeRate": 91, + "ideasFreshnessDays": 0, + "starsGrowth": 0.8 + }, + "lastUpdated": "2026-07-19T07:22:16.025Z" + }, + "The Rust Foundation": { + "score": 78, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 74, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:22:21.669Z" + }, + "Tiled": { + "score": 38, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 569, + "commitFrequency": 0.8, + "prMergeRate": 83, + "ideasFreshnessDays": 2, + "starsGrowth": 12.7 + }, + "lastUpdated": "2026-07-19T07:22:26.062Z" + }, + "Typelevel": { + "score": 58, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 4, + "commitFrequency": 0.4, + "prMergeRate": 76, + "ideasFreshnessDays": 1, + "starsGrowth": 5.5 + }, + "lastUpdated": "2026-07-19T07:22:29.644Z" + }, + "Unikraft": { + "score": 34, + "tier": "low", + "label": "🔴 Low Activity", + "signals": { + "issueResponseDays": 34, + "commitFrequency": 0.5, + "prMergeRate": 84, + "ideasFreshnessDays": 3, + "starsGrowth": 3.8 + }, + "lastUpdated": "2026-07-19T07:22:37.632Z" + }, + "Wagtail": { + "score": 53, + "tier": "moderate", + "label": "🟡 Moderate", + "signals": { + "issueResponseDays": 9, + "commitFrequency": 1.1, + "prMergeRate": 49, + "ideasFreshnessDays": 1, + "starsGrowth": 20.4 + }, + "lastUpdated": "2026-07-19T07:22:48.793Z" + }, + "webpack": { + "score": 78, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 1, + "commitFrequency": 1.1, + "prMergeRate": 80, + "ideasFreshnessDays": 0, + "starsGrowth": 50 + }, + "lastUpdated": "2026-07-19T07:22:53.448Z" + }, + "Zulip": { + "score": 68, + "tier": "active", + "label": "🔵 Active", + "signals": { + "issueResponseDays": 2, + "commitFrequency": 1.1, + "prMergeRate": 71, + "ideasFreshnessDays": 2, + "starsGrowth": 25.5 + }, + "lastUpdated": "2026-07-19T07:23:01.186Z" + } +} \ No newline at end of file diff --git a/index.html b/index.html index 46da15d130..5024f268c0 100644 --- a/index.html +++ b/index.html @@ -200,36 +200,6 @@ font-size:11px; } } - /* Mobile filter/language dropdown styles */ - .mobile-filter-option.bg-orange-600, - .mobile-filter-option.bg-orange-600:hover { - background: #c2410c !important; - color: #fff !important; - } - .mobile-lang-pill.active { - border-color: #9d4300 !important; - color: #9d4300 !important; - background: #fff7ed !important; - } - .dark #mobileFilterPanel, - .dark #mobileLangPanel { - background: #27272a !important; - border-color: #3f3f46 !important; - } - .dark .mobile-filter-option:hover { - background: #3f3f46 !important; - } - .dark .mobile-lang-pill { - background: #3f3f46 !important; - border-color: #52525b !important; - color: #d4d4d8 !important; - } - .dark .mobile-lang-pill.active { - border-color: #f97316 !important; - color: #f97316 !important; - background: #431407 !important; - } - /* Screen reader only - for accessibility */ .sr-only { position: absolute; @@ -299,7 +269,7 @@ } .close-btn:hover { background: #e8e8e8; color: #1a1c1c; } - .modal-header { padding: 2rem 2rem; flex-shrink: 0; } + .modal-header { padding: 3rem 3rem 1.5rem; flex-shrink: 0; } .category-tag { background: rgba(157, 67, 0, 0.1); color: #9d4300; @@ -312,41 +282,35 @@ margin-bottom: 1rem; } .modal-header h2 { font-size: 2.5rem; font-weight: 800; line-height: 1.1; margin-bottom: 0.5rem; display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } - .modal-header p { color: #584237; font-size: 0.9rem; line-height: 1.5; margin-bottom: 0.5rem;} + .modal-header p { color: #584237; font-size: 1rem; line-height: 1.5; } .modal-body {padding: 0 3rem 3rem; overflow-y: auto; flex: 1; min-height: 0; } - .section-title { font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.15em; color: #555; margin-bottom: 0.5rem; margin-top: 2rem; border-bottom: 1px solid #f3f3f3; padding-bottom: 0.5rem; } + .section-title { font-size: 0.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.15em; color: #aaa; margin-bottom: 1rem; margin-top: 2rem; border-bottom: 1px solid #f3f3f3; padding-bottom: 0.5rem; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; } - .metric-card { background: #faf5f0; padding: 0.5rem 1.5rem 0.5rem 1.5rem; border-radius: 1.2rem; border: 1px solid #cb692c; text-align: center; } + .metric-card { background: #f9f9f9; padding: 1.2rem; border-radius: 1.2rem; border: 1px solid #eee; text-align: center; } .metric-value { font-size: 1.25rem; font-weight: 800; color: #1a1c1c; margin-bottom: 0.2rem; } - .metric-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; color: #555; } + .metric-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; color: #888; } - .github-stats-container { background: #1a1a1a; border-radius: 1.5rem; padding: 1rem; color: #fff; display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; position: relative; overflow: hidden; justify-content: space-between; } + .github-stats-container { background: #1a1a1a; border-radius: 1.5rem; padding: 1.5rem; color: #fff; display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; position: relative; overflow: hidden; justify-content: space-between; } .gh-fetch { background: #333; color: #aaa; border: none; padding: 0.3rem 0.8rem; border-radius: 99px; font-size: 0.65rem; font-weight: 700; cursor: pointer; transition: all 0.2s; margin-left: auto; align-self: flex-start; flex-shrink: 0; } .gh-fetch:hover { background: #444; color: #fff; } .gh-stat-item { font-family: 'Fira Code', monospace; font-size: 0.75rem; color: #4ade80; display: flex; align-items: center; gap: 0.5rem; } - .tech-tags, .fit-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.2rem; } + .tech-tags, .fit-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; } .tech-tag { background: #f3f4f6; color: #4b5563; font-size: 0.7rem; font-weight: 600; padding: 0.4rem 0.8rem; border-radius: 0.5rem; border: 1px solid #e5e7eb; } .fit-tag { background: #eff6ff; color: #2563eb; font-size: 0.7rem; font-weight: 600; padding: 0.4rem 0.8rem; border-radius: 0.5rem; border: 1px solid #dbeafe; } .mentor-link-chip { display:inline-flex; align-items:center; gap:.45rem; background:#fff7ed; color:#9d4300; border:1px solid #fed7aa; border-radius:999px; padding:.45rem .75rem; font-size:.75rem; font-weight:700; } .mentor-handle-chip { display:inline-flex; align-items:center; gap:.35rem; background:#f3f4f6; color:#374151; border:1px solid #e5e7eb; border-radius:999px; padding:.4rem .7rem; font-size:.75rem; font-weight:600; } - .mentor-card { background:#ffffff; border:1px solid #e5e7eb; border-radius:1rem; padding:1.25rem; box-shadow:0 1px 2px rgba(15,23,42,.04); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } - .mentor-card:hover , .mentor-empty:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); transform: translateY(-4px); border-color: #d1d5db; } - .mentor-empty { background:#ffffff; border:1px solid #e5e7eb; border-radius:1rem; padding:2rem; text-align:center; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } + .mentor-card { background:#ffffff; border:1px solid #e5e7eb; border-radius:1rem; padding:1.25rem; box-shadow:0 1px 2px rgba(15,23,42,.04); } + .mentor-empty { background:#ffffff; border:1px solid #e5e7eb; border-radius:1rem; padding:2rem; text-align:center; } .timeline-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; } .timeline-grid span { font-size: 0.75rem; font-weight: 500; color: #666; background: #fff; border: 1px solid #eee; padding: 0.3rem 0.7rem; border-radius: 0.5rem; } - .timeline-grid span.current-year { background: #fffbeb; color: #92400e; border-color: #fbbf24; font-weight: 700; } - .timeline-grid a.timeline-year-link { font-size: 0.75rem; font-weight: 500; color: #666; background: #fff; border: 1px solid #eee; padding: 0.3rem 0.7rem; border-radius: 0.5rem; text-decoration: none; display: inline-block; cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s; } - .timeline-grid a.timeline-year-link:hover { border-color: var(--orange); color: var(--orange); background: var(--orange-pale); } - .timeline-grid a.timeline-year-link.current-year { background: #fffbeb; color: #92400e; border-color: #fbbf24; font-weight: 700; } - .timeline-grid a.timeline-year-link.current-year:hover { border-color: #92400e; background: #fef3c7; color: #78350f; } - - .modal-footer { padding: 1.5rem 2rem; background: #f9f9f9; display: flex; gap: 1rem; border-top: 1px solid #eee; flex-shrink: 1; } - .modal-footer-secondary { display: flex; gap: 1rem; margin-left: auto; } - .modal-cta { flex: 1; padding: 0.75rem; border-radius: 1rem; font-size: 0.65rem; font-weight: 800; text-align: center; text-transform: uppercase; letter-spacing: 0.05em; transition: all 0.2s; } + .timeline-grid span.current-year { background: #fffbeb; color: #d97706; border-color: #fef3c7; font-weight: 700; } + + .modal-footer { padding: 2rem 3rem 3rem; background: #f9f9f9; display: flex; gap: 1rem; border-top: 1px solid #eee; flex-shrink: 0; } + .modal-cta { flex: 1; padding: 1rem; border-radius: 1rem; font-size: 0.8rem; font-weight: 800; text-align: center; text-transform: uppercase; letter-spacing: 0.05em; transition: all 0.2s; } .modal-ideas-link { background: #9d4300; color: white; } .modal-ideas-link:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(157, 67, 0, 0.3); } .modal-repo-link { background: white; color: #1a1c1c; border: 1px solid #ddd; } @@ -477,6 +441,26 @@ .complexity-badge.beginner { background: #dcfce7; color: #166534; } .complexity-badge.intermediate { background: #fef9c3; color: #854d0e; } .complexity-badge.advanced { background: #fee2e2; color: #991b1b; } + + /* Activity Badges */ +.activity-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 99px; + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.03em; +} +.tier-very-active { background: #dcfce7; color: #166534; } +.tier-active { background: #dbeafe; color: #1d4ed8; } +.tier-moderate { background: #fef9c3; color: #854d0e; } +.tier-low { background: #fee2e2; color: #991b1b; } +.dark .tier-very-active { background: #14532d; color: #86efac; } +.dark .tier-active { background: #1e3a5f; color: #93c5fd; } +.dark .tier-moderate { background: #422006; color: #fcd34d; } +.dark .tier-low { background: #450a0a; color: #fca5a5; } /* Bookmark Button */ .bookmark-btn { transition: all 0.2s; font-size: 1.25rem; } @@ -593,8 +577,7 @@ .dark .fit-tag { background: #1e293b; color: #93c5fd; border-color: #334155; } .dark .mentor-link-chip { background: rgba(124,45,18,0.72); color: #fff7ed; border-color: rgba(251,146,60,0.45); } .dark .mentor-handle-chip { background: #27272a; color: #d4d4d8; border-color: #3f3f46; } - .dark .mentor-card, .dark .mentor-empty { background:#18181b; border-color:#3f3f46; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } - .dark .mentor-card:hover, .dark .mentor-empty:hover { box-shadow: 0 12px 20px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.5); transform: translateY(-4px); border-color: #52525b; background: #27272a; } + .dark .mentor-card, .dark .mentor-empty { background:#18181b; border-color:#3f3f46; } :root { --section-fab-right: 24px; @@ -608,13 +591,9 @@ @media (max-width: 640px) { .modal-header { padding: 1rem 1.5rem 0.5rem; flex-shrink: 0; } .modal-header h2 { font-size: 1.6rem; } - .metric-card { padding: 0.5rem 1rem; margin-top: 0.5rem;} - .metric-value { font-size: 1rem; } - .metric-label { font-size: 0.6rem; } .modal-body { padding: 0 1.5rem 2rem; min-height: 0; } - .modal-footer { padding: 1rem; gap: 1rem; flex-direction: column; flex-shrink: 0; } - .modal-footer-secondary { display: flex; gap: 1rem; width: 100%; } - .modal-footer-secondary .modal-cta { flex-direction:column; flex: 1 1 0; min-width:0; width: auto; padding: 0.3rem 0.4rem; font-size: 0.65rem; min-height: 30px; } + .modal-footer { padding: 0.75rem 1.5rem; flex-direction: column; flex-shrink: 0; } + .modal-cta { width: 100%; padding: 0.6rem 1rem; font-size: 0.85rem; } .proposal-header { padding: 0.6rem 1rem; gap: 0.4rem; position: relative; } .proposal-header-title { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; } .proposal-header-title h2 { font-size: 0.95rem; } @@ -949,13 +928,6 @@ stroke-dasharray: none; stroke-dashoffset: 0; } - .section-scroll-btn { - transition: none !important; - } - .section-scroll-btn:hover, - .section-scroll-btn:active { - transform: none !important; - } } /* Smooth fading for search bar active animation on focus */ @@ -989,18 +961,9 @@ align-items: center; justify-content: center; box-shadow: 0 8px 24px rgba(157, 67, 0, 0.35); - transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); - overflow: hidden; -} -.section-scroll-btn:hover { - transform: translateY(-2px); - box-shadow: 0 12px 32px rgba(157, 67, 0, 0.45); -} -.section-scroll-btn:active { - transform: translateY(1px) scale(0.92); - box-shadow: 0 4px 12px rgba(157, 67, 0, 0.2); - filter: brightness(0.95); + transition: all 0.25s ease; } +.section-scroll-btn:hover { transform: translateY(-2px); } #nextSectionBtn { bottom: calc(var(--section-fab-bottom) + env(safe-area-inset-bottom)); } #prevSectionBtn { bottom: calc(var(--section-fab-bottom) + var(--section-fab-size) + var(--section-fab-gap) + env(safe-area-inset-bottom)); @@ -1014,25 +977,6 @@ .dark .section-scroll-btn:hover { box-shadow: 0 12px 32px rgba(194, 65, 12, 0.45); } -.dark .section-scroll-btn:active { - transform: translateY(1px) scale(0.92); - box-shadow: 0 4px 12px rgba(194, 65, 12, 0.2); - filter: brightness(0.95); -} - -/* Dark mode - Section scroll indicator */ -.dark .section-scroll-indicator { - background: rgba(24, 24, 27, 0.88); - border-color: rgba(249, 115, 22, 0.25); - color: #fb923c; -} -.dark .section-scroll-indicator-dot { - background: linear-gradient(135deg, #fb923c, #f97316); - box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.12); -} -.dark .section-scroll-indicator-value { - color: #e4e4e7; -} .section-scroll-btn:disabled { opacity: 0.3; @@ -1040,44 +984,6 @@ pointer-events: none; } -/* Custom ripple effect inside scroll buttons */ -.section-scroll-btn .click-ripple { - position: absolute; - border-radius: 50%; - transform: scale(0); - animation: ripple-animation 0.5s cubic-bezier(0.1, 0.8, 0.3, 1); - background-color: rgba(255, 255, 255, 0.45); - pointer-events: none; -} - -@keyframes ripple-animation { - to { - transform: scale(2.5); - opacity: 0; - } -} - -/* Icon animations inside scroll buttons on click */ -@keyframes bounce-down { - 0% { transform: translateY(0); } - 30% { transform: translateY(4px); } - 70% { transform: translateY(-2px); } - 100% { transform: translateY(0); } -} -@keyframes bounce-up { - 0% { transform: translateY(0); } - 30% { transform: translateY(-4px); } - 70% { transform: translateY(2px); } - 100% { transform: translateY(0); } -} - -.section-scroll-btn.bounce-down-active .material-symbols-outlined { - animation: bounce-down 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} -.section-scroll-btn.bounce-up-active .material-symbols-outlined { - animation: bounce-up 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} - .section-scroll-indicator { position: fixed; right: calc(var(--section-fab-right) + env(safe-area-inset-right)); @@ -1163,16 +1069,6 @@ border-radius: 999px; } } -@media (pointer: coarse) { - .filter-chip.bg-orange-600:hover { - background-color: #ea580c !important; - color: #1a0a00 !important; - } - .filter-chip.bg-surface-container-highest:hover { - background-color: revert !important; - color: revert !important; - } -} @@ -1201,9 +1097,9 @@
- @@ -1276,7 +1170,7 @@
- -
+
+
@@ -1980,7 +1783,7 @@

New to GSo
1

Explore Organizations

-

Browse all 184 open source organizations. Filter by language, domain, and competition level to find your match.

+

Browse 185+ open source organizations. Filter by language, domain, and competition level to find your match.

2
@@ -2057,10 +1860,25 @@

Join Discord

- - - - + + -
Participation Timeline · click a year to explore its projects
+
Participation Timeline
+
Mentors & Contact
@@ -2108,14 +1927,12 @@

Join Discord

- + + lightbulb Project Ideas + + + code Repository +
@@ -2256,40 +2073,18 @@

+ + @@ -5497,9 +4976,11 @@

Keyboard Shortcuts

if (!hoverCapable) { element.addEventListener('click', (event) => { event.stopPropagation(); - clearTimeout(globalThis._globalTooltipTimer); - showTooltip(element); - globalThis._globalTooltipTimer = setTimeout(() => hideTooltip(), 3000); + if (tooltip.classList.contains('show') && tooltip.textContent === element.dataset.tooltip) { + hideTooltip(); + } else { + showTooltip(element); + } }); } }); @@ -5531,8 +5012,6 @@

Keyboard Shortcuts

.map(id => document.getElementById(id)) .filter(Boolean); const mainHeader = document.querySelector("header.fixed.top-0"); - const nextBtn = document.getElementById("nextSectionBtn"); - const prevBtn = document.getElementById("prevSectionBtn"); let currentSection = 0; const sectionIndicatorValue = document.getElementById("sectionScrollIndicatorValue"); @@ -5543,9 +5022,15 @@

Keyboard Shortcuts

function scrollToSectionIndex(index) { const targetSection = sections[index]; + if (!targetSection) return; - // Use scrollIntoView to respect scroll-margin-top defined in CSS - targetSection.scrollIntoView({ behavior: "smooth", block: "start" }); + + const targetTop = Math.max(0, targetSection.offsetTop - getSectionDetectionOffset()); + + window.scrollTo({ + top: targetTop, + behavior: "smooth" + }); } function updateSectionIndicator() { @@ -5583,13 +5068,6 @@

Keyboard Shortcuts

} }); - if (prevBtn) { - prevBtn.disabled = (currentSection === 0); - } - if (nextBtn) { - nextBtn.disabled = (currentSection === sections.length - 1); - } - updateSectionIndicator(); } @@ -5604,50 +5082,23 @@

Keyboard Shortcuts

const logoLink = document.getElementById("logoLink"); - let isNavClicking = false; - let navScrollEndTimer = null; - - function triggerSmoothScroll(index) { - const targetSection = sections[index]; - if (!targetSection) return; - - isNavClicking = true; - currentSection = index; - updateNavState(); - scrollToSectionIndex(index); - - // Reset scroll-end timer in case scroll event doesn't fire (already at target) - clearTimeout(navScrollEndTimer); - navScrollEndTimer = setTimeout(() => { - isNavClicking = false; - updateCurrentSection(); - }, 150); - } - if (logoLink) { logoLink.addEventListener("click", (event) => { event.preventDefault(); - triggerSmoothScroll(0); // timeline is at index 0 + setCurrentSectionById("timeline"); + scrollToSectionIndex(currentSection); }); } document.querySelectorAll(".desktop-nav-link").forEach(link => { - link.addEventListener("click", (e) => { - e.preventDefault(); // stop browser anchor scroll - const nextSectionIndex = sections.findIndex(section => section.id === link.dataset.section); - if (nextSectionIndex !== -1) { - triggerSmoothScroll(nextSectionIndex); - } + link.addEventListener("click", () => { + setCurrentSectionById(link.dataset.section); }); }); document.querySelectorAll(".mobile-menu-link").forEach(link => { - link.addEventListener("click", (e) => { - e.preventDefault(); // stop browser anchor scroll - const nextSectionIndex = sections.findIndex(section => section.id === link.dataset.section); - if (nextSectionIndex !== -1) { - triggerSmoothScroll(nextSectionIndex); - } + link.addEventListener("click", () => { + setCurrentSectionById(link.dataset.section); if (typeof globalThis.toggleMenu === "function") { globalThis.toggleMenu(); @@ -5656,137 +5107,34 @@

Keyboard Shortcuts

}); function updateCurrentSection() { - if (isNavClicking) return; - - // Check if scrolled to bottom of the page - if (globalThis.innerHeight + globalThis.scrollY >= document.documentElement.scrollHeight - 50) { - currentSection = sections.length - 1; - updateNavState(); - return; - } - - const viewportTop = getSectionDetectionOffset(); - const viewportBottom = globalThis.innerHeight; - - let maxVisibleHeight = 0; - let found = 0; + const scrollPos = window.scrollY + getSectionDetectionOffset(); sections.forEach((section, index) => { - const rect = section.getBoundingClientRect(); - const visibleTop = Math.max(rect.top, viewportTop); - const visibleBottom = Math.min(rect.bottom, viewportBottom); - - const visibleHeight = visibleBottom - visibleTop; - if (visibleHeight > maxVisibleHeight) { - maxVisibleHeight = visibleHeight; - found = index; - } + if (scrollPos >= section.offsetTop) currentSection = index; }); - currentSection = found; updateNavState(); } - // Scroll listener debounced with requestAnimationFrame - let rafPending = false; - globalThis.addEventListener("scroll", () => { - if (isNavClicking) { - // During smooth scroll, debounce the scroll-end timer to release lock - clearTimeout(navScrollEndTimer); - navScrollEndTimer = setTimeout(() => { - isNavClicking = false; - updateCurrentSection(); - }, 100); - return; - } - - if (!rafPending) { - globalThis.requestAnimationFrame(() => { - updateCurrentSection(); - rafPending = false; - }); - rafPending = true; - } - }, { passive: true }); - - globalThis.addEventListener("resize", updateCurrentSection); + window.addEventListener("scroll", updateCurrentSection, { passive: true }); + window.addEventListener("resize", updateCurrentSection); updateCurrentSection(); - function addClickFeedback(btn, direction) { - if (!btn) return; - btn.addEventListener("click", (e) => { - const prefersReducedMotion = globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches; - - // Icon bounce animation - const animClass = direction === "up" ? "bounce-up-active" : "bounce-down-active"; - if (!prefersReducedMotion) { - btn.classList.add(animClass); - } - - // Create ripple effect - if (!prefersReducedMotion) { - const ripple = document.createElement("span"); - ripple.classList.add("click-ripple"); - const rect = btn.getBoundingClientRect(); - const size = Math.max(rect.width, rect.height); - ripple.style.width = ripple.style.height = `${size}px`; - - const isKeyboardClick = e.detail === 0; - const x = (!isKeyboardClick && e.clientX !== undefined && e.clientX !== null) ? (e.clientX - rect.left - size / 2) : (rect.width / 2 - size / 2); - const y = (!isKeyboardClick && e.clientY !== undefined && e.clientY !== null) ? (e.clientY - rect.top - size / 2) : (rect.height / 2 - size / 2); - ripple.style.left = `${x}px`; - ripple.style.top = `${y}px`; - - btn.appendChild(ripple); - - ripple.addEventListener("animationend", () => { - ripple.remove(); - }); - - btn.addEventListener("animationend", (event) => { - if (event.animationName === "bounce-up" || event.animationName === "bounce-down") { - btn.classList.remove(animClass); - } - }, { once: true }); - } - }); - } - - addClickFeedback(nextBtn, "down"); - addClickFeedback(prevBtn, "up"); - - nextBtn?.addEventListener("click", () => { + document.getElementById("nextSectionBtn")?.addEventListener("click", () => { if (currentSection < sections.length - 1) { - triggerSmoothScroll(currentSection + 1); + scrollToSectionIndex(currentSection + 1); } }); - prevBtn?.addEventListener("click", () => { + document.getElementById("prevSectionBtn")?.addEventListener("click", () => { if (currentSection > 0) { - triggerSmoothScroll(currentSection - 1); + scrollToSectionIndex(currentSection - 1); + } else { + window.scrollTo({ top: 0, behavior: "smooth" }); } }); })(); - - - + diff --git a/privacy.html b/privacy.html index 007162a3b7..49dbfa08b6 100644 --- a/privacy.html +++ b/privacy.html @@ -49,7 +49,7 @@
- @@ -61,7 +61,7 @@
-
-