Skip to content

Commit 9a0d5ae

Browse files
committed
feat: added gsoc 2026 proposals and new site exploration
1 parent 92cfca7 commit 9a0d5ae

19 files changed

Lines changed: 2250 additions & 5 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: false
16+
17+
jobs:
18+
deploy:
19+
environment:
20+
name: github-pages
21+
url: ${{ steps.deployment.outputs.page_url }}
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Pages
28+
uses: actions/configure-pages@v4
29+
30+
- name: Upload artifact
31+
uses: actions/upload-pages-artifact@v3
32+
with:
33+
path: '.'
34+
35+
- name: Deploy to GitHub Pages
36+
id: deployment
37+
uses: actions/deploy-pages@v4

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ Your Proposal Document needs to have:
4545

4646
## Ideas page
4747

48+
### 🎯 Interactive Dashboard
49+
50+
**NEW!** Explore all our GSoC projects in an interactive dashboard with advanced filtering:
51+
52+
👉 **[View Interactive Projects Dashboard](https://ruxailab.github.io/gsoc/)** 👈
53+
54+
Features:
55+
- Filter by year, project size, and difficulty
56+
- Search across all project attributes
57+
- Beautiful, responsive design
58+
- All projects from 2021-2026 in one place
59+
60+
### Original Ideas Pages
61+
4862
Check out our project's ideas on the [Ideas Page](/ideas2026.md), and see if you find something that catches your attention. If you don't get interested in one of the proposals or have another idea that you want to make, validate it with one of the mentors before submitting your proposal. Check also our ideas list from previous years:
4963

5064
- [2021](/ideas2021.md)

app.js

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// Global variables
2+
let allProjects = [];
3+
let filteredProjects = [];
4+
5+
// DOM elements
6+
const projectsTableBody = document.getElementById('projectsTableBody');
7+
const sizeFilter = document.getElementById('sizeFilter');
8+
const difficultyFilter = document.getElementById('difficultyFilter');
9+
const searchInput = document.getElementById('searchInput');
10+
const resetBtn = document.getElementById('resetBtn');
11+
const projectModal = document.getElementById('projectModal');
12+
const modalBody = document.getElementById('modalBody');
13+
const closeBtn = document.querySelector('.close-btn');
14+
15+
// Load projects from JSON file
16+
async function loadProjects() {
17+
try {
18+
const categories = ['heuristic', 'nlp', 'ruxailab', 'eye-tracker', 'accessibility', 'user-test', 'sentimental'];
19+
const projectPromises = categories.map(category =>
20+
fetch(`gsoc-ideas/${category}/2026.json`)
21+
.then(res => res.ok ? res.json() : null)
22+
.catch(() => null)
23+
);
24+
25+
const results = await Promise.all(projectPromises);
26+
const allCategoryProjects = results
27+
.filter(data => data !== null)
28+
.flatMap(data => data.projects.map(p => ({...p, category: data.category, year: 2026})));
29+
30+
allProjects = allCategoryProjects;
31+
32+
// Sort by title
33+
allProjects.sort((a, b) => a.title.localeCompare(b.title));
34+
35+
filteredProjects = [...allProjects];
36+
renderProjects();
37+
} catch (error) {
38+
console.error('Error loading projects:', error);
39+
console.error('Please refresh the page.');
40+
}
41+
}
42+
43+
// Render projects in the table
44+
function renderProjects() {
45+
projectsTableBody.innerHTML = '';
46+
47+
if (filteredProjects.length === 0) {
48+
projectsTableBody.innerHTML = `
49+
<tr>
50+
<td colspan="8" style="text-align: center; padding: 40px; color: #999;">
51+
No projects found matching your filters. Try adjusting your search criteria.
52+
</td>
53+
</tr>
54+
`;
55+
return;
56+
}
57+
58+
filteredProjects.forEach(project => {
59+
const row = document.createElement('tr');
60+
61+
// Truncate description for table view
62+
const maxLength = 120;
63+
const shortDescription = project.description.length > maxLength
64+
? project.description.substring(0, maxLength) + '...'
65+
: project.description;
66+
67+
row.innerHTML = `
68+
<td><strong>${project.title}</strong></td>
69+
<td><span class="size-badge size-${project.size}">${capitalizeFirst(project.size)}</span></td>
70+
<td>${project.hours}h</td>
71+
<td><span class="difficulty-badge difficulty-${project.difficulty}">${project.difficulty}</span></td>
72+
<td>${project.mentor}</td>
73+
<td>
74+
<div class="keywords-list">
75+
${project.keywords.map(keyword => `<span class="keyword-tag">${keyword}</span>`).join('')}
76+
</div>
77+
</td>
78+
<td>
79+
<div class="skills-list">
80+
${project.skills.map(skill => `<span class="skill-tag">${skill}</span>`).join('')}
81+
</div>
82+
</td>
83+
<td class="description-cell">${shortDescription}</td>
84+
`;
85+
86+
row.addEventListener('click', () => showProjectDetail(project));
87+
projectsTableBody.appendChild(row);
88+
});
89+
}
90+
91+
// Filter projects based on all criteria
92+
function filterProjects() {
93+
const sizeValue = sizeFilter.value;
94+
const difficultyValue = difficultyFilter.value;
95+
const searchValue = searchInput.value.toLowerCase();
96+
97+
filteredProjects = allProjects.filter(project => {
98+
// Size filter
99+
if (sizeValue !== 'all' && project.size !== sizeValue) {
100+
return false;
101+
}
102+
103+
// Difficulty filter
104+
if (difficultyValue !== 'all' && project.difficulty !== difficultyValue) {
105+
return false;
106+
}
107+
108+
// Search filter
109+
if (searchValue) {
110+
const searchableText = [
111+
project.title,
112+
project.description,
113+
project.mentor,
114+
...project.keywords,
115+
...project.skills
116+
].join(' ').toLowerCase();
117+
118+
if (!searchableText.includes(searchValue)) {
119+
return false;
120+
}
121+
}
122+
123+
return true;
124+
});
125+
126+
renderProjects();
127+
}
128+
129+
// Reset all filters
130+
function resetFilters() {
131+
sizeFilter.value = 'all';
132+
difficultyFilter.value = 'all';
133+
searchInput.value = '';
134+
filterProjects();
135+
}
136+
137+
// Show project detail in modal
138+
function showProjectDetail(project) {
139+
let modalContent = `
140+
<div class="modal-header">
141+
<h2 class="modal-title">${project.title}</h2>
142+
<div class="modal-meta">
143+
<span class="year-badge">${project.year}</span>
144+
<span class="size-badge size-${project.size}">${capitalizeFirst(project.size)} Project</span>
145+
<span class="difficulty-badge difficulty-${project.difficulty}">${project.difficulty}</span>
146+
</div>
147+
</div>
148+
149+
<div class="modal-section">
150+
<h3>Description</h3>
151+
<p>${project.description}</p>
152+
</div>
153+
`;
154+
155+
// Add Key Features if available
156+
if (project.keyFeatures && project.keyFeatures.length > 0) {
157+
modalContent += `
158+
<div class="modal-section">
159+
<h3>Key Features</h3>
160+
<ul class="modal-features-list">
161+
${project.keyFeatures.map(feature => `<li>${feature}</li>`).join('')}
162+
</ul>
163+
</div>
164+
`;
165+
}
166+
167+
// Add Expected Outcome if available
168+
if (project.expectedOutcome) {
169+
modalContent += `
170+
<div class="modal-section">
171+
<h3>Expected Outcome</h3>
172+
<p>${project.expectedOutcome}</p>
173+
</div>
174+
`;
175+
}
176+
177+
modalContent += `
178+
<div class="modal-section">
179+
<h3>Project Information</h3>
180+
<div class="modal-info-grid">
181+
<div class="modal-info-item">
182+
<div class="modal-info-label">Duration</div>
183+
<div class="modal-info-value">${project.hours} hours</div>
184+
</div>
185+
<div class="modal-info-item">
186+
<div class="modal-info-label">Project Size</div>
187+
<div class="modal-info-value">${capitalizeFirst(project.size)}</div>
188+
</div>
189+
<div class="modal-info-item">
190+
<div class="modal-info-label">Difficulty</div>
191+
<div class="modal-info-value">${project.difficulty}</div>
192+
</div>
193+
<div class="modal-info-item">
194+
<div class="modal-info-label">Mentor</div>
195+
<div class="modal-info-value">${project.mentor}</div>
196+
</div>
197+
</div>
198+
</div>
199+
200+
<div class="modal-section">
201+
<h3>Keywords</h3>
202+
<div class="modal-list">
203+
${project.keywords.map(keyword => `<span class="modal-list-item">${keyword}</span>`).join('')}
204+
</div>
205+
</div>
206+
207+
<div class="modal-section">
208+
<h3>Required Skills</h3>
209+
<div class="modal-list">
210+
${project.skills.map(skill => `<span class="modal-list-item">${skill}</span>`).join('')}
211+
</div>
212+
</div>
213+
`;
214+
215+
modalBody.innerHTML = modalContent;
216+
projectModal.classList.add('show');
217+
document.body.style.overflow = 'hidden';
218+
}
219+
220+
// Hide modal
221+
function hideModal() {
222+
projectModal.classList.remove('show');
223+
document.body.style.overflow = 'auto';
224+
}
225+
226+
// Helper function to capitalize first letter
227+
function capitalizeFirst(str) {
228+
return str.charAt(0).toUpperCase() + str.slice(1);
229+
}
230+
231+
// Event listeners
232+
sizeFilter.addEventListener('change', filterProjects);
233+
difficultyFilter.addEventListener('change', filterProjects);
234+
searchInput.addEventListener('input', filterProjects);
235+
resetBtn.addEventListener('click', resetFilters);
236+
237+
// Modal event listeners
238+
closeBtn.addEventListener('click', hideModal);
239+
projectModal.addEventListener('click', (e) => {
240+
if (e.target === projectModal) {
241+
hideModal();
242+
}
243+
});
244+
245+
// Close modal on Escape key
246+
document.addEventListener('keydown', (e) => {
247+
if (e.key === 'Escape' && projectModal.classList.contains('show')) {
248+
hideModal();
249+
}
250+
});
251+
252+
// Initialize on page load
253+
document.addEventListener('DOMContentLoaded', () => {
254+
loadProjects();
255+
});
256+
257+
// Add some statistics display
258+
function getProjectStatistics() {
259+
const stats = {
260+
byYear: {},
261+
bySize: {},
262+
byDifficulty: {}
263+
};
264+
265+
allProjects.forEach(project => {
266+
// Count by year
267+
stats.byYear[project.year] = (stats.byYear[project.year] || 0) + 1;
268+
269+
// Count by size
270+
stats.bySize[project.size] = (stats.bySize[project.size] || 0) + 1;
271+
272+
// Count by difficulty
273+
stats.byDifficulty[project.difficulty] = (stats.byDifficulty[project.difficulty] || 0) + 1;
274+
});
275+
276+
return stats;
277+
}
278+
279+
// Log statistics to console for debugging
280+
window.addEventListener('load', () => {
281+
setTimeout(() => {
282+
if (allProjects.length > 0) {
283+
console.log('Project Statistics:', getProjectStatistics());
284+
console.log('Total Projects:', allProjects.length);
285+
}
286+
}, 1000);
287+
});

0 commit comments

Comments
 (0)