|
|
|
|
|
import { areasData } from '../data/areas.js'; |
|
|
import { featuredArtifacts } from '../data/artifacts.js'; |
|
|
|
|
|
export function createArtifactSummaryCard(artifact, index) { |
|
|
const { title, date, type, description, areaTags, subAreaTags, sourceUrl } = artifact; |
|
|
|
|
|
|
|
|
const typeStyles = { |
|
|
'blog': { |
|
|
icon: 'π', |
|
|
textColor: 'text-blue-700' |
|
|
}, |
|
|
'paper': { |
|
|
icon: 'π', |
|
|
textColor: 'text-green-700' |
|
|
}, |
|
|
'dataset': { |
|
|
icon: 'π', |
|
|
textColor: 'text-purple-700' |
|
|
}, |
|
|
'space': { |
|
|
icon: 'π', |
|
|
textColor: 'text-orange-700' |
|
|
}, |
|
|
'external': { |
|
|
icon: 'π', |
|
|
textColor: 'text-gray-700' |
|
|
} |
|
|
}; |
|
|
|
|
|
const style = typeStyles[type] || typeStyles['external']; |
|
|
|
|
|
|
|
|
const areaData = areasData; |
|
|
|
|
|
const primaryArea = areaTags[0]; |
|
|
const primaryAreaData = areaData[primaryArea]; |
|
|
const backgroundImage = primaryAreaData?.image || ''; |
|
|
const imageCredit = primaryAreaData?.imageAttribution || ''; |
|
|
|
|
|
const areaTagsHtml = areaTags.map(tag => { |
|
|
const area = areaData[tag]; |
|
|
return area ? `<span class="inline-block px-2 py-1 text-xs rounded-full ${area.color}">${area.name}</span>` : ''; |
|
|
}).join(''); |
|
|
|
|
|
const subAreaTagsHtml = subAreaTags.map(subAreaKey => { |
|
|
|
|
|
let subAreaName = subAreaKey; |
|
|
let textColor = 'text-gray-700'; |
|
|
|
|
|
for (const areaKey in areaData) { |
|
|
const area = areaData[areaKey]; |
|
|
if (area.subAreas && area.subAreas[subAreaKey]) { |
|
|
const subArea = area.subAreas[subAreaKey]; |
|
|
subAreaName = typeof subArea === 'string' ? subArea : subArea.name; |
|
|
|
|
|
if (typeof subArea === 'object' && subArea.color) { |
|
|
const colorMatch = subArea.color.match(/text-(\w+)-(\d+)/); |
|
|
if (colorMatch) { |
|
|
textColor = `text-${colorMatch[1]}-700`; |
|
|
} |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
return `<span class="inline-block px-2 py-0.5 text-xs bg-gray-200 ${textColor} rounded">${subAreaName}</span>`; |
|
|
}).join(''); |
|
|
|
|
|
const cardId = `artifact-card-${index}`; |
|
|
|
|
|
return ` |
|
|
<div class="flex-none w-80 h-60 border border-gray-200 rounded-lg overflow-hidden bg-white relative group hover:shadow-lg transition-shadow duration-200"> |
|
|
<!-- Background image --> |
|
|
${backgroundImage ? ` |
|
|
<div class="absolute inset-0 opacity-10"> |
|
|
<img src="images/${backgroundImage}" alt="" class="w-full h-full object-cover"> |
|
|
</div> |
|
|
` : ''} |
|
|
|
|
|
<!-- Toggle indicator --> |
|
|
<div class="absolute top-2 right-2 z-10"> |
|
|
<button class="toggle-btn w-6 h-6 bg-white bg-opacity-80 hover:bg-opacity-100 rounded-full flex items-center justify-center text-gray-600 hover:text-gray-800 transition-all shadow-sm" onclick="toggleCardView('${cardId}')"> |
|
|
<svg class="w-3 h-3 expand-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path> |
|
|
</svg> |
|
|
<svg class="w-3 h-3 collapse-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<!-- Content --> |
|
|
<div class="relative p-4 h-full flex flex-col overflow-hidden" id="${cardId}"> |
|
|
<!-- Default view --> |
|
|
<div class="default-view"> |
|
|
<!-- Header: Type and Date --> |
|
|
<div class="flex justify-between items-start mb-3 pr-8"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<span class="text-lg">${style.icon}</span> |
|
|
<span class="text-xs font-medium font-bold uppercase tracking-wide">${type}</span> |
|
|
</div> |
|
|
<span class="text-xs text-gray-600">${date}</span> |
|
|
</div> |
|
|
|
|
|
<!-- Title --> |
|
|
<div class="mb-4 flex-grow min-h-0"> |
|
|
<h3 class="font-semibold text-gray-900 text-sm leading-tight line-clamp-3">${title}</h3> |
|
|
</div> |
|
|
|
|
|
<!-- Bottom section with tags and image --> |
|
|
<div class="flex justify-between items-end"> |
|
|
<!-- Left: Tags --> |
|
|
<div class="flex-1 mr-4"> |
|
|
<!-- Area Tags --> |
|
|
<div class="flex flex-wrap gap-1 mb-2"> |
|
|
${areaTagsHtml} |
|
|
</div> |
|
|
|
|
|
<!-- Sub-area Tags --> |
|
|
${subAreaTags.length > 0 ? ` |
|
|
<div class="flex flex-wrap gap-1"> |
|
|
${subAreaTagsHtml} |
|
|
</div> |
|
|
` : ''} |
|
|
</div> |
|
|
|
|
|
<!-- Right: Area image with credit on hover --> |
|
|
${backgroundImage ? ` |
|
|
<div class="relative group/image"> |
|
|
<div class="w-12 h-12 rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center cursor-help" title="${imageCredit}"> |
|
|
<img src="images/${backgroundImage}" alt="${primaryAreaData.name}" class="w-full h-full object-cover opacity-80"> |
|
|
</div> |
|
|
</div> |
|
|
` : ''} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- Description view (hidden by default) --> |
|
|
<div class="description-view hidden h-full flex flex-col min-h-0"> |
|
|
<!-- Title (single line with overflow) --> |
|
|
<div class="mb-3 flex-shrink-0"> |
|
|
<h3 class="font-semibold text-gray-900 text-sm leading-tight truncate" title="${title}">${title}</h3> |
|
|
</div> |
|
|
|
|
|
<!-- Description (scrollable, takes remaining space) --> |
|
|
<div class="flex-grow overflow-y-auto min-h-0"> |
|
|
<p class="text-xs text-gray-700 leading-relaxed">${description}</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- URL link (always visible) --> |
|
|
${sourceUrl ? ` |
|
|
<div class="absolute bottom-2 right-2"> |
|
|
<a href="${sourceUrl}" target="_blank" rel="noopener noreferrer" 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> |
|
|
` : ''} |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
export function createArtifactCarousel(artifacts, containerId) { |
|
|
const container = document.getElementById(containerId); |
|
|
if (!container) return; |
|
|
|
|
|
const cardsHtml = artifacts.map((artifact, index) => createArtifactSummaryCard(artifact, index)).join(''); |
|
|
|
|
|
container.innerHTML = ` |
|
|
<div class="relative"> |
|
|
<!-- Carousel container --> |
|
|
<div class="flex overflow-x-auto scrollbar-hide space-x-4 pb-4" id="${containerId}-scroll"> |
|
|
${cardsHtml} |
|
|
</div> |
|
|
|
|
|
<!-- Navigation arrows --> |
|
|
<button class="absolute left-0 top-1/2 transform -translate-y-1/2 bg-white shadow-lg rounded-full p-2 hover:bg-gray-50 transition-colors z-10" onclick="scrollCarousel('${containerId}-scroll', -320)"> |
|
|
<svg class="w-4 h-4 text-gray-600" 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="absolute right-0 top-1/2 transform -translate-y-1/2 bg-white shadow-lg rounded-full p-2 hover:bg-gray-50 transition-colors z-10" onclick="scrollCarousel('${containerId}-scroll', 320)"> |
|
|
<svg class="w-4 h-4 text-gray-600" 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> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
window.scrollCarousel = function(containerId, scrollAmount) { |
|
|
const container = document.getElementById(containerId); |
|
|
if (container) { |
|
|
container.scrollBy({ left: scrollAmount, behavior: 'smooth' }); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
window.toggleCardView = function(cardId) { |
|
|
const card = document.getElementById(cardId); |
|
|
const defaultView = card.querySelector('.default-view'); |
|
|
const descriptionView = card.querySelector('.description-view'); |
|
|
const expandIcon = card.parentElement.querySelector('.expand-icon'); |
|
|
const collapseIcon = card.parentElement.querySelector('.collapse-icon'); |
|
|
|
|
|
if (defaultView.classList.contains('hidden')) { |
|
|
|
|
|
defaultView.classList.remove('hidden'); |
|
|
descriptionView.classList.add('hidden'); |
|
|
expandIcon.classList.remove('hidden'); |
|
|
collapseIcon.classList.add('hidden'); |
|
|
} else { |
|
|
|
|
|
defaultView.classList.add('hidden'); |
|
|
descriptionView.classList.remove('hidden'); |
|
|
expandIcon.classList.add('hidden'); |
|
|
collapseIcon.classList.remove('hidden'); |
|
|
} |
|
|
}; |