GoofyLM-Research-Hub / index.html
FlameF0X's picture
Update index.html
dbad3d1 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GoofyLM Research Hub</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<script>
// Custom configuration for Tailwind to extend the color palette
tailwind.config = {
theme: {
extend: {
colors: {
'primary': {
'light': '#fef3c7', // amber-100
'DEFAULT': '#f59e0b', // amber-500
'dark': '#b45309' // amber-700
},
'secondary': '#1f2937', // gray-800
'light': '#f9fafb', // gray-50
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
}
}
}
</script>
<style>
/* --- Base Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: #f9fafb; /* Use light gray from palette */
color: #374151; /* gray-700 */
}
/* --- Custom Utility for Clamping Text --- */
.paper-abstract {
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* --- Modal Styles --- */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.6);
/* Added for smooth transition */
opacity: 0;
transition: opacity 0.3s ease-in-out;
align-items: center;
justify-content: center;
}
.modal.is-open {
display: flex;
opacity: 1;
}
.modal-content {
background-color: #ffffff;
margin: auto;
padding: 2.5rem; /* 40px */
border-radius: 0.75rem; /* 12px */
width: 90%;
max-width: 900px;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
position: relative;
max-height: 90vh;
overflow-y: auto;
/* Added for smooth entrance */
transform: scale(0.95);
transition: transform 0.3s ease-in-out;
}
.modal.is-open .modal-content {
transform: scale(1);
}
.close-button {
color: #9ca3af; /* gray-400 */
position: absolute;
top: 1rem;
right: 1.5rem;
font-size: 2rem;
font-weight: 300;
cursor: pointer;
transition: color 0.3s ease, transform 0.3s ease;
}
.close-button:hover,
.close-button:focus {
color: #111827; /* gray-900 */
transform: rotate(90deg);
}
/* --- Full Paper Content Typography (Prose-like styling) --- */
.full-paper-content h2 {
font-size: 1.75rem; /* text-2xl */
font-weight: 700;
color: #111827; /* gray-900 */
margin-top: 2rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #e5e7eb; /* gray-200 */
}
.full-paper-content h3 {
font-size: 1.25rem; /* text-xl */
font-weight: 600;
color: #1f2937; /* gray-800 */
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.full-paper-content p {
font-size: 1rem;
line-height: 1.75;
color: #4b5563; /* gray-600 */
margin-bottom: 1rem;
}
.full-paper-content ul, .full-paper-content ol {
margin-left: 1.5rem;
list-style-position: outside;
margin-bottom: 1rem;
color: #4b5563;
}
.full-paper-content ul li, .full-paper-content ol li {
padding-left: 0.5rem;
margin-bottom: 0.5rem;
}
.full-paper-content ul { list-style-type: disc; }
.full-paper-content ol { list-style-type: decimal; }
</style>
</head>
<body class="antialiased">
<div class="container mx-auto p-5 sm:p-8">
<!-- Header Section -->
<header class="text-center mb-12">
<h1 class="text-4xl md:text-5xl font-extrabold text-secondary tracking-tight mb-3">
GoofyLM Research Hub
</h1>
<p class="text-lg text-gray-500 max-w-2xl mx-auto mb-6">
A comprehensive collection of academic research in Artificial Intelligence and Large Language Models.
</p>
<!-- New Button for Main Website -->
<a href="https://goofylm.site/" rel="noopener noreferrer"
class="inline-block bg-primary text-white py-2.5 px-6 rounded-lg font-semibold text-lg no-underline
hover:bg-primary-dark transition-colors duration-300 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-primary shadow-md hover:shadow-lg">
Go to Main Website
</a>
</header>
<!-- Stats Section -->
<section class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-12" id="stats">
<!-- Stats cards will be populated by JS -->
</section>
<!-- Search and Filters Section -->
<section class="search-filters bg-white p-6 md:p-8 rounded-xl shadow-sm border border-gray-200 mb-10">
<h2 class="text-2xl font-bold text-secondary mb-6">Explore Research Papers</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<input type="text" id="searchInput" placeholder="Search by title, author, or keywords..."
class="md:col-span-3 p-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-all duration-200">
<select id="yearFilter" class="p-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-all duration-200 bg-white">
<option value="">All Years</option>
</select>
<select id="categoryFilter" class="md:col-span-2 p-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-all duration-200 bg-white">
<option value="">All Categories</option>
</select>
</div>
<div class="filter-tags flex flex-wrap gap-2" id="filterTags">
<!-- Filter tags will be populated by JS -->
</div>
</section>
<!-- Papers Grid Section -->
<section class="papers-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-10" id="papersGrid">
<!-- Paper cards will be populated by JS -->
</section>
<!-- No Results Message -->
<div class="no-results hidden text-center py-16 px-5 bg-white rounded-xl shadow-sm border border-gray-200" id="noResultsMessage">
<h3 class="text-2xl font-semibold text-gray-700 mb-3">No Papers Found</h3>
<p class="text-gray-500">Try adjusting your search criteria or filters.</p>
</div>
</div>
<!-- Modal for Full Paper View -->
<div id="paperModal" class="modal">
<div class="modal-content">
<span class="close-button" id="closeModalBtn">&times;</span>
<h2 class="text-3xl font-bold text-secondary mb-3" id="modalPaperTitle"></h2>
<div class="text-primary-dark font-semibold mb-2 text-base" id="modalPaperAuthors"></div>
<div class="text-gray-500 text-sm mb-6" id="modalPaperMeta"></div>
<div class="full-paper-content" id="modalPaperContent">
<!-- Full paper content populated by JS -->
</div>
</div>
</div>
<script>
// --- DATA AND STATE MANAGEMENT ---
let researchPapers = {};
let filteredPapers = [];
let activeFilters = new Set();
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', init);
async function init() {
try {
// Fetch the JSON data from a local file
const response = await fetch('papers.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
researchPapers = await response.json();
// Once data is loaded, populate the page
populateStats();
populateFilters();
populateFilterTags();
displayPapers(researchPapers.papers);
setupEventListeners();
} catch (error) {
console.error("Could not load research papers:", error);
const grid = document.getElementById('papersGrid');
grid.innerHTML = `
<div class="no-results text-center py-16 px-5 bg-red-50 text-red-700 rounded-lg shadow-md border border-red-200 col-span-full">
<h3 class="text-2xl font-semibold mb-3">Error Loading Papers</h3>
<p>We apologize, but there was an issue loading the research paper data. Please try again later.</p>
</div>
`;
}
}
// --- UI POPULATION FUNCTIONS ---
function populateStats() {
const statsContainer = document.getElementById('stats');
const totalPapers = researchPapers.papers.length;
const totalAuthors = new Set(researchPapers.papers.flatMap(p => p.authors)).size;
const totalCategories = researchPapers.metadata.categories.length;
const latestYear = Math.max(...researchPapers.papers.map(p => p.year));
const stats = [
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>`,
value: totalPapers,
label: "Research Papers"
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>`,
value: totalAuthors,
label: "Contributing Authors"
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg>`,
value: totalCategories,
label: "Research Categories"
},
{
icon: `<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>`,
value: latestYear,
label: "Latest Publication"
},
];
statsContainer.innerHTML = stats.map(stat => `
<div class="stat-card bg-white p-6 rounded-xl flex items-center gap-5 shadow-sm border border-gray-200">
<div>${stat.icon}</div>
<div>
<span class="stat-number text-3xl font-bold text-secondary block">${stat.value}</span>
<div class="stat-label text-gray-500 text-sm">${stat.label}</div>
</div>
</div>
`).join('');
}
function populateFilters() {
const yearFilter = document.getElementById('yearFilter');
const categoryFilter = document.getElementById('categoryFilter');
const years = [...new Set(researchPapers.papers.map(p => p.year))].sort((a, b) => b - a);
years.forEach(year => {
const option = document.createElement('option');
option.value = year;
option.textContent = year;
yearFilter.appendChild(option);
});
const categories = [...researchPapers.metadata.categories].sort();
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categoryFilter.appendChild(option);
});
}
function populateFilterTags() {
const tagsContainer = document.getElementById('filterTags');
const allTags = [...new Set(researchPapers.papers.flatMap(p => p.tags))].sort();
tagsContainer.innerHTML = allTags.map(tag =>
`<span class="tag bg-light text-primary-dark py-1.5 px-4 rounded-full text-sm font-medium cursor-pointer border border-primary-light hover:bg-primary-light hover:border-primary transition-colors duration-200" data-tag="${tag}">${tag}</span>`
).join('');
}
function displayPapers(papers) {
const grid = document.getElementById('papersGrid');
const noResultsMessage = document.getElementById('noResultsMessage');
if (papers.length === 0) {
grid.innerHTML = '';
noResultsMessage.classList.remove('hidden');
return;
} else {
noResultsMessage.classList.add('hidden');
}
grid.innerHTML = papers.map(paper => `
<div class="paper-card flex flex-col bg-white p-6 rounded-xl shadow-sm border border-gray-200 hover:shadow-lg hover:-translate-y-1 transition-all duration-300">
<div class="flex-grow">
<h3 class="paper-title text-xl font-bold text-secondary mb-2 leading-tight">${paper.title}</h3>
<div class="paper-authors text-primary-dark font-semibold mb-1 text-sm">${paper.authors.join(', ')}</div>
<div class="paper-year text-gray-500 text-sm mb-4">${paper.year} &bull; ${paper.category}</div>
<div class="paper-tags flex flex-wrap gap-2 mb-4">
${paper.tags.map(tag => `<span class="paper-tag bg-gray-100 text-gray-600 py-1 px-2 rounded-md text-xs font-medium border border-gray-200">${tag}</span>`).join('')}
</div>
<p class="paper-abstract text-gray-600 text-base leading-relaxed mb-4">${paper.abstract}</p>
</div>
<div class="mt-auto">
<button class="paper-link w-full bg-primary text-white py-2.5 px-5 rounded-lg font-semibold text-sm no-underline hover:bg-primary-dark transition-colors duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary" data-paper-id="${paper.id}">Read Full Paper</button>
</div>
</div>
`).join('');
// Re-attach event listeners to the new "Read Full Paper" buttons
document.querySelectorAll('.paper-card .paper-link').forEach(button => {
button.addEventListener('click', (e) => {
const paperId = parseInt(e.target.dataset.paperId, 10);
const paper = researchPapers.papers.find(p => p.id === paperId);
if (paper) displayFullPaper(paper);
});
});
}
// --- FILTERING LOGIC ---
function filterPapers() {
if (!researchPapers.papers) {
console.warn("Research papers data not yet loaded.");
return;
}
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const yearFilter = document.getElementById('yearFilter').value;
const categoryFilter = document.getElementById('categoryFilter').value;
filteredPapers = researchPapers.papers.filter(paper => {
const matchesSearch = !searchTerm ||
paper.title.toLowerCase().includes(searchTerm) ||
paper.authors.some(author => author.toLowerCase().includes(searchTerm)) ||
paper.abstract.toLowerCase().includes(searchTerm) ||
paper.tags.some(tag => tag.toLowerCase().includes(searchTerm));
const matchesYear = !yearFilter || paper.year.toString() === yearFilter;
const matchesCategory = !categoryFilter || paper.category === categoryFilter;
const matchesTags = activeFilters.size === 0 ||
[...activeFilters].every(filterTag => paper.tags.includes(filterTag));
return matchesSearch && matchesYear && matchesCategory && matchesTags;
});
displayPapers(filteredPapers);
}
// --- EVENT LISTENERS ---
function setupEventListeners() {
document.getElementById('searchInput').addEventListener('input', filterPapers);
document.getElementById('yearFilter').addEventListener('change', filterPapers);
document.getElementById('categoryFilter').addEventListener('change', filterPapers);
document.getElementById('filterTags').addEventListener('click', (e) => {
if (e.target.classList.contains('tag')) {
const tag = e.target.dataset.tag;
if (activeFilters.has(tag)) {
activeFilters.delete(tag);
e.target.classList.remove('active', 'bg-primary', 'text-white', 'border-primary');
e.target.classList.add('bg-light', 'text-primary-dark', 'border-primary-light');
} else {
activeFilters.add(tag);
e.target.classList.add('active', 'bg-primary', 'text-white', 'border-primary');
e.target.classList.remove('bg-light', 'text-primary-dark', 'border-primary-light');
}
filterPapers();
}
});
// --- Modal Event Listeners ---
const modal = document.getElementById('paperModal');
const closeModalBtn = document.getElementById('closeModalBtn');
closeModalBtn.addEventListener('click', () => {
modal.classList.remove('is-open');
});
window.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('is-open');
}
});
window.addEventListener('keydown', (e) => {
if (e.key === "Escape" && modal.classList.contains('is-open')) {
modal.classList.remove('is-open');
}
});
}
// --- MODAL DISPLAY LOGIC ---
function displayFullPaper(paper) {
const modal = document.getElementById('paperModal');
document.getElementById('modalPaperTitle').textContent = paper.title;
document.getElementById('modalPaperAuthors').textContent = paper.authors.join(', ');
document.getElementById('modalPaperMeta').textContent = `${paper.year}${paper.category}`;
const contentDiv = document.getElementById('modalPaperContent');
contentDiv.innerHTML = ''; // Clear previous content
// A simple recursive renderer for nested content
function renderContent(container, contentObject) {
for (const key in contentObject) {
if (Object.prototype.hasOwnProperty.call(contentObject, key)) {
const value = contentObject[key];
if (typeof value === 'string') {
const titleEl = document.createElement('h3');
titleEl.textContent = key;
container.appendChild(titleEl);
const p = document.createElement('p');
p.innerHTML = value; // Use innerHTML to render any potential HTML tags
container.appendChild(p);
} else if (typeof value === 'object') {
const sectionTitle = document.createElement('h2');
sectionTitle.textContent = key;
container.appendChild(sectionTitle);
renderContent(container, value);
}
}
}
}
// This handles the top-level sections like "Abstract", "Introduction", etc.
for (const key in paper.content) {
if (Object.prototype.hasOwnProperty.call(paper.content, key)) {
const value = paper.content[key];
const sectionTitle = document.createElement('h2');
sectionTitle.textContent = key;
contentDiv.appendChild(sectionTitle);
if(typeof value === 'string') {
const p = document.createElement('p');
p.textContent = value;
contentDiv.appendChild(p);
} else if (typeof value === 'object') {
for (const subKey in value) {
if (Object.prototype.hasOwnProperty.call(value, subKey)) {
const subValue = value[subKey];
const subSectionTitle = document.createElement('h3');
subSectionTitle.textContent = subKey;
contentDiv.appendChild(subSectionTitle);
const paragraph = document.createElement('p');
paragraph.textContent = subValue;
contentDiv.appendChild(paragraph);
}
}
}
}
}
modal.classList.add('is-open');
modal.querySelector('.modal-content').scrollTop = 0; // Scroll to top on open
}
</script>
</body>
</html>