overview / js /pages /AreaPage.js
Yacine Jernite
sticky_nav
f07b5d3
// pages/AreaPage.js - Single-topic view for area pages
import { getFeaturedArtifacts } from '../init.js';
import { createArtifactCarousel } from '../components/Carousel.js';
import { renderAreaNavigation } from '../components/PageNavigation.js';
import { renderContentSection, renderOpennessCallout } from '../components/ContentSection.js';
import { initializeStickyNavigation } from '../utils/stickyNavigation.js';
// Use global areasData (loaded in index.html <head>)
const areasData = window.areasData;
export function renderAreaPage(areaId, topicId = null) {
const area = areasData[areaId];
if (!area) {
return {
content: `<div class="bg-white/90 backdrop-blur-md rounded-lg shadow-sm p-8 mx-4"><h1 class="text-2xl font-bold text-red-600">Area not found</h1></div>`,
init: () => {}
};
}
// Always render full page with all topics (single scrollable page)
return renderFullAreaPage(area, areaId, topicId);
}
function renderFullAreaPage(area, areaId, initialTopicId = null) {
// Get topics as array
const topics = Object.entries(area.topics).map(([key, value]) => ({
id: key,
name: typeof value === 'string' ? value : value.name,
navName: typeof value === 'string' ? value : (value.navName || value.name),
description: typeof value === 'string' ? '' : (value.description || ''),
color: typeof value === 'object' ? value.color : 'bg-gray-200 text-gray-700'
}));
const content = `
<!-- Top spacing -->
<div class="mb-8"></div>
${renderAreaNavigation(areaId, area.navTitle, topics, initialTopicId)}
<!-- Overview Section -->
${renderContentSection('overview', `
<h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">${area.title}</h1>
<!-- Area Description -->
<div class="mb-8">
${area.description.paragraphs ?
area.description.paragraphs.map(p => `<p class="text-gray-900 leading-relaxed mb-4">${p}</p>`).join('')
: `<p class="text-gray-900 leading-relaxed">${area.description.short || area.description}</p>`
}
</div>
<!-- Topics within this area -->
<div class="mb-8">
<p class="text-sm font-semibold text-gray-600 uppercase tracking-wide mb-3">Topics in this area:</p>
<div class="flex flex-wrap gap-2">
${topics.map(topic => {
// Extract colors from topic.color and enhance them
let bgColor = 'bg-gray-200';
let textColor = 'text-gray-800';
let borderColor = 'border-gray-300';
if (topic.color) {
const bgMatch = topic.color.match(/bg-(\w+)-(\d+)/);
const textMatch = topic.color.match(/text-(\w+)-(\d+)/);
if (bgMatch) {
const colorName = bgMatch[1];
const shade = parseInt(bgMatch[2]);
// Increase saturation: 100 β†’ 200, 200 β†’ 300, etc.
const newShade = Math.min(shade + 100, 300);
bgColor = `bg-${colorName}-${newShade}`;
borderColor = `border-${colorName}-${Math.min(newShade + 100, 400)}`;
}
if (textMatch) {
const colorName = textMatch[1];
textColor = `text-${colorName}-900`;
}
}
// Get short description for tooltip
const shortDesc = topic.description?.short || (typeof topic.description === 'string' ? topic.description.split('.')[0] + '.' : '');
const titleAttr = shortDesc ? ` title="${shortDesc}"` : '';
return `
<a href="/${areaId}#${topic.id}" class="inline-block px-3 py-1.5 text-sm font-semibold ${bgColor} ${textColor} ${borderColor} border rounded hover:opacity-90 transition-opacity"${titleAttr}>
${topic.navName}
</a>
`;
}).join('')}
</div>
</div>
<!-- Research and Resources -->
<div>
<p class="text-sm font-semibold text-gray-600 uppercase tracking-wide mb-3">Research and Resources:</p>
<div id="area-artifacts-carousel-${areaId}" class="overflow-x-auto -mx-2">
<!-- Carousel will be inserted here -->
</div>
</div>
`)}
<!-- Topic Sections (each as a separate content section) -->
${topics.map(topic => renderTopicSection(area, areaId, topic)).join('')}
`;
return {
content,
init: () => {
initializeAreaArtifactsCarousel(areaId);
// Initialize artifact carousels for all topics
topics.forEach(topic => {
initializeTopicArtifactsCarousel(areaId, topic.id);
});
initializeStickyNavigation('nav-container');
}
};
}
function renderTopicSection(area, areaId, topic) {
const topicId = topic.id;
const topicName = topic.navName || topic.name;
const topicDescription = topic.description?.paragraphs
? topic.description.paragraphs.map(p => `<p class="text-gray-900 leading-relaxed mb-4">${p}</p>`).join('')
: (topic.description?.short || topic.description || '');
return renderContentSection(topicId, `
<!-- Breadcrumb -->
<div class="mb-4 text-sm text-gray-600">
<a href="/${areaId}#overview" class="hover:text-blue-600 transition-colors">${area.navTitle}</a>
<span class="mx-2">β€Ί</span>
<span class="text-gray-900 font-medium">${topicName}</span>
</div>
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-6">${topic.name}</h2>
<!-- Topic Description -->
${topicDescription ? `
<div class="mb-8 text-gray-900">
${topicDescription}
</div>
` : ''}
<h3 class="text-xl md:text-2xl font-bold text-gray-900 mb-6">Related Research & Resources</h3>
<div id="${topicId}-artifacts-carousel" class="overflow-x-auto -mx-2">
<!-- Carousel will be inserted here -->
</div>
`);
}
// Helper function to initialize area artifacts carousel
function initializeAreaArtifactsCarousel(areaId) {
// Wait for DOM to be ready
setTimeout(() => {
try {
const allArtifacts = window.allArtifacts || [];
// Filter artifacts by area
const filteredArtifacts = allArtifacts.filter(artifact =>
artifact.areas && artifact.areas.includes(areaId)
);
// Transform field names to match what the carousel expects
const transformedArtifacts = filteredArtifacts.map(artifact => ({
...artifact,
areaTags: artifact.areas,
subAreaTags: artifact.topics,
sourceUrl: artifact.url
}));
const carouselId = `area-artifacts-carousel-${areaId}`;
createArtifactCarousel(transformedArtifacts.length > 0 ? transformedArtifacts : getFeaturedArtifacts(), carouselId);
} catch (error) {
console.error(`Error creating carousel for area ${areaId}:`, error);
const carouselId = `area-artifacts-carousel-${areaId}`;
createArtifactCarousel(getFeaturedArtifacts(), carouselId);
}
}, 50);
}
// Helper function to initialize topic artifacts carousel
function initializeTopicArtifactsCarousel(areaId, topicId) {
setTimeout(() => {
try {
const allArtifacts = window.allArtifacts || [];
// Filter artifacts by this specific topic
const filteredArtifacts = allArtifacts.filter(artifact =>
artifact.topics && artifact.topics.includes(topicId)
);
// Transform field names to match expected format
const transformedArtifacts = filteredArtifacts.map(artifact => ({
...artifact,
areaTags: artifact.areas,
subAreaTags: artifact.topics,
sourceUrl: artifact.url
}));
const carouselId = `${topicId}-artifacts-carousel`;
createArtifactCarousel(transformedArtifacts.length > 0 ? transformedArtifacts : getFeaturedArtifacts(), carouselId);
} catch (error) {
console.error(`Error creating carousel for ${topicId}:`, error);
const carouselId = `${topicId}-artifacts-carousel`;
createArtifactCarousel(getFeaturedArtifacts(), carouselId);
}
}, 50);
}