Spaces:
Running
Running
<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">×</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} • ${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> | |