overview / js /cards /ArtifactSummaryCard.js
yjernite's picture
yjernite HF Staff
Upload 3 files
aeeb334 verified
raw
history blame
10.7 kB
// ArtifactSummaryCard.js - Summary card component for artifacts
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;
// Type-based styling (simplified - just icon and text color)
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'];
// Use imported areas data
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 => {
// 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 areaData) {
const area = areaData[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('');
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>
`;
}
// Utility function for carousel navigation
window.scrollCarousel = function(containerId, scrollAmount) {
const container = document.getElementById(containerId);
if (container) {
container.scrollBy({ left: scrollAmount, behavior: 'smooth' });
}
};
// Add toggle function
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')) {
// Show default view
defaultView.classList.remove('hidden');
descriptionView.classList.add('hidden');
expandIcon.classList.remove('hidden');
collapseIcon.classList.add('hidden');
} else {
// Show description view
defaultView.classList.add('hidden');
descriptionView.classList.remove('hidden');
expandIcon.classList.add('hidden');
collapseIcon.classList.remove('hidden');
}
};