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> | |