|
|
|
|
|
import { homeBackgroundImage, areasData } from '../data/areas.js'; |
|
|
import { pressMentions } from '../data/press.js'; |
|
|
import { sampleResources } from '../data/resources.js'; |
|
|
import { createResourceCard, initializeResourceCards } from '../cards/ResourceCard.js'; |
|
|
|
|
|
export function renderResourcesPage() { |
|
|
const content = ` |
|
|
<!-- Technical Resources Section --> |
|
|
<section id="technical-resources" class="mb-12 relative z-20"> |
|
|
<div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8"> |
|
|
<h2 class="text-3xl font-bold text-gray-900 mb-6">Technical Resources</h2> |
|
|
|
|
|
<div class="mb-8"> |
|
|
<p class="text-gray-700 leading-relaxed mb-6"> |
|
|
Our team develops technical artifacts in the course of their research and policy work. |
|
|
These resources include datasets, tools, benchmarks, and interactive applications that |
|
|
support transparency, reproducibility, and accessibility in AI research and governance. |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<!-- Resource Cards Carousel --> |
|
|
<div class="relative"> |
|
|
<div class="resource-carousel-container overflow-hidden" style="width: calc(100% - 2rem); margin: 0 auto;"> |
|
|
<div class="resource-carousel flex gap-6 transition-transform duration-300 ease-in-out" style="width: ${sampleResources.length * 624}px;"> |
|
|
${sampleResources.map(resource => createResourceCard(resource)).join('')} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Carousel Navigation --> |
|
|
<div class="flex justify-center mt-4 space-x-2"> |
|
|
<button class="resource-carousel-prev bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors"> |
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path> |
|
|
</svg> |
|
|
</button> |
|
|
<button class="resource-carousel-next bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors"> |
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
<!-- Press Mentions Section --> |
|
|
<section id="press-mentions" class="mb-12 relative z-20"> |
|
|
<div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8"> |
|
|
<h2 class="text-3xl font-bold text-gray-900 mb-6">Press Mentions</h2> |
|
|
|
|
|
<div class="space-y-3"> |
|
|
${pressMentions.map((article, index) => { |
|
|
// Get the primary area for this article |
|
|
const primaryArea = areasData[article.areaTags[0]]; |
|
|
const primaryColor = primaryArea?.primaryColor || 'gray'; |
|
|
|
|
|
// Determine alignment (alternating) |
|
|
const isLeftAligned = index % 2 === 0; |
|
|
const alignment = isLeftAligned ? 'justify-start' : 'justify-end'; |
|
|
const borderSide = isLeftAligned ? 'border-l-2' : 'border-r-2'; |
|
|
const borderColor = `border-${primaryColor}-200`; |
|
|
|
|
|
// Format date |
|
|
const formattedDate = new Date(article.date).toLocaleDateString('en-US', { |
|
|
year: 'numeric', |
|
|
month: 'long', |
|
|
day: 'numeric' |
|
|
}); |
|
|
|
|
|
// Generate area tags |
|
|
const areaTagsHtml = article.areaTags.map(areaId => { |
|
|
const area = areasData[areaId]; |
|
|
if (!area) return ''; |
|
|
return `<span class="px-2 py-1 rounded-full text-xs font-medium ${area.colors.medium}">${area.name}</span>`; |
|
|
}).join(''); |
|
|
|
|
|
// Generate sub-area tags |
|
|
const subAreaTagsHtml = article.subAreaTags.map(subAreaKey => { |
|
|
// Find the sub-area name and color by looking through all areas |
|
|
let subAreaName = subAreaKey; // fallback to key if not found |
|
|
let textColor = 'text-gray-700'; // fallback color |
|
|
|
|
|
for (const areaKey in areasData) { |
|
|
const area = areasData[areaKey]; |
|
|
if (area.subAreas && area.subAreas[subAreaKey]) { |
|
|
const subArea = area.subAreas[subAreaKey]; |
|
|
subAreaName = typeof subArea === 'string' ? subArea : subArea.name; |
|
|
// Extract just the text color from the sub-area color |
|
|
if (typeof subArea === 'object' && subArea.color) { |
|
|
const colorMatch = subArea.color.match(/text-(\w+)-(\d+)/); |
|
|
if (colorMatch) { |
|
|
textColor = `text-${colorMatch[1]}-700`; // Use consistent 700 shade |
|
|
} |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
return `<span class="inline-block px-2 py-0.5 text-xs bg-gray-200 ${textColor} rounded">${subAreaName}</span>`; |
|
|
}).join(''); |
|
|
|
|
|
return ` |
|
|
<!-- Press Article ${index + 1} - ${isLeftAligned ? 'Left' : 'Right'} aligned --> |
|
|
<div class="flex items-center ${alignment}"> |
|
|
<div class="flex-1 max-w-2xl"> |
|
|
<div class="bg-gray-50 rounded-lg p-4 ${borderSide} ${borderColor} relative overflow-hidden"> |
|
|
<!-- Background image for this article --> |
|
|
<div class="absolute inset-0 opacity-5 pointer-events-none"> |
|
|
<img src="/images/${primaryArea?.image || 'ai.png'}" alt="" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
|
|
|
<div class="relative z-10"> |
|
|
<h3 class="text-base font-medium text-gray-900 mb-2"> |
|
|
<a href="${article.sourceUrl}" target="_blank" class="text-gray-900 hover:text-blue-600 transition-colors"> |
|
|
"${article.title}" |
|
|
</a> |
|
|
</h3> |
|
|
|
|
|
<div class="flex items-center text-sm text-gray-600 mb-2"> |
|
|
<span class="bg-gray-600 text-white px-3 py-1 rounded-md text-xs font-semibold mr-3 shadow-sm">${article.source}</span> |
|
|
<span class="mr-3">${formattedDate}</span> |
|
|
<a href="${article.sourceUrl}" target="_blank" class="text-gray-400 hover:text-blue-600 transition-colors"> |
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path> |
|
|
</svg> |
|
|
</a> |
|
|
</div> |
|
|
|
|
|
<!-- Tags --> |
|
|
<div class="flex flex-wrap gap-1"> |
|
|
${areaTagsHtml} |
|
|
${subAreaTagsHtml} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
}).join('')} |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
`; |
|
|
|
|
|
return { |
|
|
content, |
|
|
init: () => { |
|
|
|
|
|
initializeResourcesBackgroundAttribution(); |
|
|
|
|
|
initializeResourceCards(); |
|
|
|
|
|
initializeResourceCarousel(); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
function initializeResourcesBackgroundAttribution() { |
|
|
const backgroundContainer = document.getElementById('resources-background-container'); |
|
|
const attribution = document.getElementById('resources-bg-attribution'); |
|
|
|
|
|
if (!backgroundContainer || !attribution) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
backgroundContainer.addEventListener('mouseenter', () => { |
|
|
attribution.style.opacity = '1'; |
|
|
}); |
|
|
|
|
|
backgroundContainer.addEventListener('mouseleave', () => { |
|
|
attribution.style.opacity = '0'; |
|
|
}); |
|
|
} |
|
|
|
|
|
function initializeResourceCarousel() { |
|
|
const carousel = document.querySelector('.resource-carousel'); |
|
|
const prevButton = document.querySelector('.resource-carousel-prev'); |
|
|
const nextButton = document.querySelector('.resource-carousel-next'); |
|
|
|
|
|
if (!carousel || !prevButton || !nextButton) { |
|
|
return; |
|
|
} |
|
|
|
|
|
let currentIndex = 0; |
|
|
const cardWidth = 624; |
|
|
const visibleCards = 1.5; |
|
|
const maxIndex = sampleResources.length - 1; |
|
|
|
|
|
function updateCarousel() { |
|
|
const translateX = -currentIndex * cardWidth; |
|
|
carousel.style.transform = `translateX(${translateX}px)`; |
|
|
|
|
|
|
|
|
prevButton.classList.remove('opacity-50', 'cursor-not-allowed'); |
|
|
nextButton.classList.remove('opacity-50', 'cursor-not-allowed'); |
|
|
} |
|
|
|
|
|
prevButton.addEventListener('click', () => { |
|
|
currentIndex = currentIndex > 0 ? currentIndex - 1 : maxIndex; |
|
|
updateCarousel(); |
|
|
}); |
|
|
|
|
|
nextButton.addEventListener('click', () => { |
|
|
currentIndex = currentIndex < maxIndex ? currentIndex + 1 : 0; |
|
|
updateCarousel(); |
|
|
}); |
|
|
|
|
|
|
|
|
updateCarousel(); |
|
|
} |
|
|
|