|
|
|
|
|
import { areasData } from '../data/areas.js'; |
|
|
import { featuredArtifacts } from '../data/artifacts.js'; |
|
|
import { createArtifactCarousel } from '../cards/ArtifactSummaryCard.js'; |
|
|
import { sampleResources } from '../data/resources.js'; |
|
|
import { createResourceCard, initializeResourceCards } from '../cards/ResourceCard.js'; |
|
|
|
|
|
export function renderAreaPage(areaId) { |
|
|
const area = areasData[areaId]; |
|
|
if (!area) { |
|
|
return { |
|
|
content: `<div class="bg-white rounded-lg shadow-sm p-8"><h1 class="text-2xl font-bold text-red-600">Area not found</h1></div>`, |
|
|
init: () => {} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
const areaResources = sampleResources.filter(resource => |
|
|
resource.areaTags.includes(areaId) |
|
|
); |
|
|
|
|
|
|
|
|
const subAreas = Object.entries(area.subAreas).map(([key, value]) => ({ |
|
|
id: key, |
|
|
name: typeof value === 'string' ? value : value.name, |
|
|
description: typeof value === 'string' ? '' : (value.description || ''), |
|
|
openness: typeof value === 'string' ? '' : (value.openness || ''), |
|
|
gradient: typeof value === 'object' ? value.gradient : null |
|
|
})); |
|
|
|
|
|
const content = ` |
|
|
<!-- Page Background Image for Main Content --> |
|
|
<div class="fixed opacity-40 z-0" style="top: 104px; left: 256px; right: 0; bottom: 0;" id="area-background-container"> |
|
|
<img src="images/${area.image}" alt="" class="w-full h-full object-cover pointer-events-none"> |
|
|
<!-- Attribution Tooltip --> |
|
|
<div id="area-bg-attribution" class="absolute bottom-4 right-4 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded opacity-0 transition-opacity duration-200 max-w-xs z-50"> |
|
|
<a href="${area.imageSourceUrl}" target="_blank" class="text-blue-300 hover:text-blue-100"> |
|
|
${area.imageAttribution} |
|
|
</a> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Overview Section --> |
|
|
<section id="overview" class="mb-12 relative z-20"> |
|
|
<!-- Overview Background Image --> |
|
|
<div class="absolute inset-0 opacity-10 rounded-lg overflow-hidden"> |
|
|
<img src="images/${area.image}" alt="" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
<div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8 mb-6 relative z-10"> |
|
|
<h1 class="text-3xl font-bold text-gray-900 mb-6">${area.title}</h1> |
|
|
|
|
|
<!-- Description --> |
|
|
<div class="mb-8"> |
|
|
<p class="text-gray-700 leading-relaxed mb-6">${area.description}</p> |
|
|
</div> |
|
|
|
|
|
<!-- Role of Openness --> |
|
|
<div class="mb-8 px-4 py-4 bg-gradient-to-r from-orange-50 to-yellow-50 border-l-4 border-orange-300 rounded-r-lg"> |
|
|
<p class="font-bold text-orange-900 mb-2">π€ The Role of Openness</p> |
|
|
<p class="text-orange-800 italic leading-relaxed">${area.openness}</p> |
|
|
</div> |
|
|
|
|
|
<!-- Sub-areas Navigation --> |
|
|
<div class="mb-8"> |
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Explore Sub-areas</h3> |
|
|
<div class="grid grid-cols-1 md:grid-cols-${Math.min(subAreas.length, 3)} gap-4"> |
|
|
${subAreas.map(subArea => ` |
|
|
<a |
|
|
href="#${subArea.id}" |
|
|
class="group p-4 bg-gradient-to-br ${subArea.gradient || 'from-gray-50 to-gray-100 hover:from-gray-100 hover:to-gray-200 border-gray-200 hover:border-gray-300 text-gray-900'} rounded-lg border transition-all duration-200 text-left block no-underline" |
|
|
> |
|
|
<h4 class="font-semibold text-sm group-hover:scale-105 transition-transform">${subArea.name}</h4> |
|
|
</a> |
|
|
`).join('')} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
<!-- Sub-areas Sections --> |
|
|
${subAreas.map(subArea => ` |
|
|
<section id="${subArea.id}" 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-2xl font-bold text-gray-900 mb-6">${subArea.name}</h2> |
|
|
|
|
|
${subArea.description ? ` |
|
|
<div class="mb-6"> |
|
|
<p class="text-gray-700 leading-relaxed">${subArea.description}</p> |
|
|
</div> |
|
|
` : ''} |
|
|
|
|
|
${subArea.openness ? ` |
|
|
<div class="mb-6 px-4 py-4 bg-gradient-to-r from-orange-50 to-yellow-50 border-l-4 border-orange-300 rounded-r-lg"> |
|
|
<p class="font-bold text-orange-900 mb-2">π€ The Role of Openness</p> |
|
|
<p class="text-orange-800 italic leading-relaxed">${subArea.openness}</p> |
|
|
</div> |
|
|
` : ''} |
|
|
|
|
|
<!-- Artifacts Carousel --> |
|
|
<div class="mb-6"> |
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Related Research & Resources</h3> |
|
|
<div id="${subArea.id}-artifacts-carousel"> |
|
|
<!-- Carousel will be inserted here --> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
`).join('')} |
|
|
|
|
|
<!-- Resources Section --> |
|
|
<section id="resources" class="mb-12 relative z-10"> |
|
|
<div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8"> |
|
|
<h2 class="text-2xl font-bold text-gray-900 mb-6">Technical Resources</h2> |
|
|
|
|
|
<!-- Filter resources by area --> |
|
|
<div class="mb-6"> |
|
|
<p class="text-gray-700 leading-relaxed"> |
|
|
Technical resources and tools related to ${area.name.toLowerCase()} research and development. |
|
|
</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: ${areaResources.length * 624}px;"> |
|
|
${areaResources.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> |
|
|
`; |
|
|
|
|
|
return { |
|
|
content, |
|
|
init: () => { |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
subAreas.forEach(subArea => { |
|
|
|
|
|
|
|
|
const carouselId = `${subArea.id}-artifacts-carousel`; |
|
|
createArtifactCarousel(featuredArtifacts, carouselId); |
|
|
}); |
|
|
}, 50); |
|
|
|
|
|
|
|
|
initializeResourceCards(); |
|
|
|
|
|
|
|
|
if (areaResources.length > 0) { |
|
|
initializeAreaResourceCarousel(areaResources); |
|
|
} |
|
|
|
|
|
|
|
|
initializeAreaBackgroundAttribution(); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
export function getAreaPageSidebar(areaId) { |
|
|
const area = areasData[areaId]; |
|
|
if (!area) { |
|
|
return '<div class="p-4">Area not found</div>'; |
|
|
} |
|
|
|
|
|
const subAreas = Object.entries(area.subAreas).map(([key, value]) => ({ |
|
|
id: key, |
|
|
name: typeof value === 'string' ? value : value.name |
|
|
})); |
|
|
|
|
|
return ` |
|
|
<nav class="p-4"> |
|
|
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">On This Page</h3> |
|
|
<ul class="space-y-1"> |
|
|
<li><a href="#overview" class="page-nav-link block px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-md">Overview</a></li> |
|
|
<li class="ml-4"> |
|
|
<ul class="space-y-1"> |
|
|
${subAreas.map(subArea => ` |
|
|
<li><a href="#${subArea.id}" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">${subArea.name}</a></li> |
|
|
`).join('')} |
|
|
</ul> |
|
|
</li> |
|
|
<li><a href="#resources" class="page-nav-link block px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors">Resources</a></li> |
|
|
</ul> |
|
|
</nav> |
|
|
`; |
|
|
} |
|
|
|
|
|
function initializeAreaResourceCarousel(resources) { |
|
|
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 = resources.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(); |
|
|
} |
|
|
|
|
|
function initializeAreaBackgroundAttribution() { |
|
|
const backgroundContainer = document.getElementById('area-background-container'); |
|
|
const attribution = document.getElementById('area-bg-attribution'); |
|
|
|
|
|
if (!backgroundContainer || !attribution) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
backgroundContainer.addEventListener('mouseenter', () => { |
|
|
attribution.style.opacity = '1'; |
|
|
}); |
|
|
|
|
|
backgroundContainer.addEventListener('mouseleave', () => { |
|
|
attribution.style.opacity = '0'; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|