Atom feed for our EEG site

more

Changed files
+667 -46
+667 -46
index.html
···
margin: 0 auto;
}
.tabs {
display: flex;
align-items: center;
···
padding: 15px 0;
z-index: 50;
scrollbar-width: none; /* For Firefox */
}
.timeline-sidebar::-webkit-scrollbar {
···
font-size: 0.8rem;
font-family: 'JetBrains Mono', monospace;
position: relative;
}
.timeline-month {
···
font-size: 0.7rem;
opacity: 0.8;
position: relative;
}
.timeline-year::before,
···
.timeline-year.active {
color: var(--accent-color);
font-weight: 600;
}
.timeline-month.active {
color: var(--accent-alt);
font-weight: 600;
}
.timeline-year.active::after {
···
margin-top: 4px;
max-width: none;
}
}
@media (max-width: 600px) {
.feed-item-author {
min-width: 50px;
···
.tabs {
gap: 2px;
}
.tab-button {
-
padding: 6px 10px;
-
font-size: 0.8rem;
}
}
</style>
···
<body>
<header>
<div class="header-container">
-
<div class="logo">Atomic<span>EEG</span></div>
<div class="tabs">
<button class="tab-button active" data-tab="posts">Posts</button>
<button class="tab-button" data-tab="links">Links</button>
</div>
-
<div class="info-panel">
-
<span id="entry-count">0</span> entries | <span id="source-count">0</span> sources
</div>
</div>
</header>
···
</div>
<div id="feed-items" class="tab-content active" data-tab="posts"></div>
<div id="link-items" class="tab-content" data-tab="links"></div>
</section>
<aside class="timeline-sidebar" id="timeline-sidebar">
<!-- Timeline will be populated via JavaScript -->
···
}
// Tab switching functionality
function setupTabs() {
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
···
// Activate selected tab
button.classList.add('active');
document.querySelector(`.tab-content[data-tab="${tabName}"]`).classList.add('active');
});
});
}
const feedItemsContainer = document.getElementById('feed-items');
const loadingContainer = document.getElementById('loading');
-
const entryCountElement = document.getElementById('entry-count');
-
const sourceCountElement = document.getElementById('source-count');
// Function to format date (only date, no time)
function formatDate(dateString) {
···
const entries = xmlDoc.getElementsByTagName('entry');
const sources = new Set();
-
// Update counter
-
entryCountElement.textContent = entries.length;
// Map to store entries by ID for easy lookup
const entriesById = {};
···
// All articles have been processed in the main loop above
-
// Update sources count
-
sourceCountElement.textContent = sources.size;
// No toggle functions needed anymore
···
// Skip adding data attributes - we've already done this during HTML generation
// Create observer to track which period is in view
-
const feedObserver = new IntersectionObserver((entries) => {
-
entries.forEach(entry => {
-
if (entry.isIntersecting) {
-
const year = entry.target.getAttribute('data-year');
-
const month = entry.target.getAttribute('data-month');
-
-
if (year && month) {
-
// Clear all active classes
-
document.querySelectorAll('.timeline-year.active, .timeline-month.active').forEach(el => {
-
el.classList.remove('active');
-
});
-
-
// Set active classes
-
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
-
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
-
-
if (yearEl) yearEl.classList.add('active');
-
if (monthEl) {
-
monthEl.classList.add('active');
-
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
-
}
-
}
-
}
-
});
-
}, observerOptions);
// Hide loading, show content
loadingContainer.style.display = 'none';
feedItemsContainer.innerHTML = entriesHTML;
-
// Observe all feed items for scroll tracking
-
document.querySelectorAll('.feed-item').forEach(item => {
-
feedObserver.observe(item);
-
});
-
// Also observe link items for timeline highlighting
-
document.querySelectorAll('.link-item').forEach(item => {
-
feedObserver.observe(item);
-
});
// Set up hover effects
setupHoverEffects();
···
// Update the links container
linksContainer.innerHTML = linksHTML;
// Initialize tabs
setupTabs();
-
// Make timeline items clickable to scroll to relevant posts
document.querySelectorAll('.timeline-year, .timeline-month').forEach(item => {
item.addEventListener('click', () => {
const year = item.getAttribute('data-year');
const month = item.getAttribute('data-month');
// Find the first element with this date
let selector = `[data-year="${year}"]`;
if (month !== null && month !== undefined) {
···
// Get the active tab
const activeTab = document.querySelector('.tab-content.active');
// Look for the target within the active tab
const targetItem = activeTab.querySelector(selector);
-
if (targetItem) {
targetItem.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
···
margin: 0 auto;
}
+
.header-left {
+
display: flex;
+
align-items: baseline;
+
gap: 15px;
+
}
+
+
.tagline {
+
font-size: 0.75rem;
+
color: var(--text-muted);
+
font-family: 'JetBrains Mono', monospace;
+
white-space: nowrap;
+
}
+
+
.external-links {
+
display: flex;
+
gap: 15px;
+
}
+
+
.header-link {
+
color: var(--accent-alt);
+
text-decoration: none;
+
font-size: 0.8rem;
+
font-family: 'JetBrains Mono', monospace;
+
transition: all 0.2s ease;
+
white-space: nowrap;
+
}
+
+
.header-link:hover {
+
color: var(--accent-color);
+
text-decoration: underline;
+
}
+
.tabs {
display: flex;
align-items: center;
···
padding: 15px 0;
z-index: 50;
scrollbar-width: none; /* For Firefox */
+
cursor: pointer; /* Show pointer cursor for the entire sidebar */
}
.timeline-sidebar::-webkit-scrollbar {
···
font-size: 0.8rem;
font-family: 'JetBrains Mono', monospace;
position: relative;
+
transition: all 0.2s ease;
}
.timeline-month {
···
font-size: 0.7rem;
opacity: 0.8;
position: relative;
+
transition: all 0.2s ease;
+
}
+
+
.timeline-year:hover, .timeline-month:hover {
+
color: var(--accent-color);
+
transform: scale(1.05);
}
.timeline-year::before,
···
.timeline-year.active {
color: var(--accent-color);
font-weight: 600;
+
background-color: rgba(77, 250, 123, 0.1);
+
border-radius: 4px;
}
.timeline-month.active {
color: var(--accent-alt);
font-weight: 600;
+
background-color: rgba(77, 250, 123, 0.05);
+
border-radius: 4px;
}
.timeline-year.active::after {
···
margin-top: 4px;
max-width: none;
}
+
+
.header-container {
+
flex-direction: column;
+
align-items: flex-start;
+
padding: 10px 0;
+
}
+
+
.header-left {
+
flex-direction: column;
+
align-items: flex-start;
+
gap: 5px;
+
}
+
+
.tagline {
+
white-space: normal;
+
font-size: 0.7rem;
+
}
+
+
header {
+
height: auto;
+
}
+
+
main {
+
margin-top: 140px;
+
}
+
+
.timeline-sidebar {
+
top: 140px;
+
height: calc(100vh - 140px);
+
}
+
+
.tabs {
+
margin: 10px 0;
+
}
+
+
.external-links {
+
margin-top: 10px;
+
}
}
+
/* People tab styling */
+
.people-header {
+
font-family: 'JetBrains Mono', monospace;
+
color: var(--accent-color);
+
margin-bottom: 20px;
+
font-size: 1.4rem;
+
font-weight: 600;
+
}
+
+
.people-container {
+
display: grid;
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+
gap: 20px;
+
}
+
+
.person-card {
+
background-color: var(--card-bg);
+
border: 1px solid var(--border-color);
+
border-radius: 4px;
+
overflow: hidden;
+
transition: all 0.2s ease;
+
padding: 15px;
+
}
+
+
.person-card:hover {
+
border-left-color: var(--accent-color);
+
background-color: rgba(77, 250, 123, 0.03);
+
}
+
+
.person-name {
+
font-family: 'JetBrains Mono', monospace;
+
color: var(--accent-alt);
+
font-size: 1.1rem;
+
margin-bottom: 8px;
+
font-weight: 600;
+
}
+
+
.person-site {
+
font-size: 0.9rem;
+
color: var(--text-muted);
+
margin-bottom: 12px;
+
}
+
+
.person-site a {
+
color: var(--text-muted);
+
text-decoration: none;
+
transition: color 0.2s ease;
+
}
+
+
.person-site a:hover {
+
color: var(--accent-color);
+
}
+
+
.person-stats {
+
display: flex;
+
gap: 15px;
+
margin-bottom: 15px;
+
font-family: 'JetBrains Mono', monospace;
+
font-size: 0.85rem;
+
}
+
+
.person-stat {
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
}
+
+
.stat-value {
+
color: var(--accent-color);
+
font-size: 1.1rem;
+
font-weight: 600;
+
}
+
+
.stat-label {
+
color: var(--text-muted);
+
font-size: 0.75rem;
+
}
+
+
.person-recent {
+
margin-top: 12px;
+
}
+
+
.recent-title {
+
font-family: 'JetBrains Mono', monospace;
+
color: var(--text-muted);
+
font-size: 0.85rem;
+
margin-bottom: 8px;
+
}
+
+
.recent-posts {
+
display: flex;
+
flex-direction: column;
+
gap: 8px;
+
}
+
+
.recent-post {
+
padding: 8px;
+
background-color: rgba(77, 250, 123, 0.03);
+
border-radius: 3px;
+
font-size: 0.9rem;
+
}
+
+
.recent-post a {
+
color: var(--text-color);
+
text-decoration: none;
+
transition: color 0.2s ease;
+
}
+
+
.recent-post a:hover {
+
color: var(--accent-color);
+
}
+
+
.recent-post-date {
+
font-family: 'JetBrains Mono', monospace;
+
color: var(--text-muted);
+
font-size: 0.75rem;
+
margin-top: 3px;
+
}
+
@media (max-width: 600px) {
.feed-item-author {
min-width: 50px;
···
.tabs {
gap: 2px;
+
width: 100%;
+
justify-content: space-between;
}
.tab-button {
+
padding: 6px 8px;
+
font-size: 0.75rem;
+
flex-grow: 1;
+
text-align: center;
+
}
+
+
.people-container {
+
grid-template-columns: 1fr;
+
}
+
+
.external-links {
+
width: 100%;
+
justify-content: space-around;
+
}
+
+
main {
+
margin-top: 150px;
+
}
+
+
.timeline-sidebar {
+
top: 150px;
+
height: calc(100vh - 150px);
}
}
</style>
···
<body>
<header>
<div class="header-container">
+
<div class="header-left">
+
<div class="logo">Atomic<span>EEG</span></div>
+
<div class="tagline">musings from the Energy & Environment Group at the University of Cambridge</div>
+
</div>
<div class="tabs">
<button class="tab-button active" data-tab="posts">Posts</button>
<button class="tab-button" data-tab="links">Links</button>
+
<button class="tab-button" data-tab="people">People</button>
</div>
+
<div class="external-links">
+
<a href="https://www.cst.cam.ac.uk/research/eeg" target="_blank" class="header-link">Home</a>
+
<a href="https://watch.eeg.cl.cam.ac.uk" target="_blank" class="header-link">Videos</a>
</div>
</div>
</header>
···
</div>
<div id="feed-items" class="tab-content active" data-tab="posts"></div>
<div id="link-items" class="tab-content" data-tab="links"></div>
+
<div id="people-items" class="tab-content" data-tab="people">
+
<h2 class="people-header">EEG Contributors</h2>
+
<div class="people-container"></div>
+
</div>
</section>
<aside class="timeline-sidebar" id="timeline-sidebar">
<!-- Timeline will be populated via JavaScript -->
···
}
// Tab switching functionality
+
// Create global variables to store state
+
let globalFeedObserver = null;
+
let lastActiveYear = null;
+
let lastActiveMonth = null;
+
+
function setupObserver(options) {
+
// Create a new intersection observer for handling timeline scrolling
+
return new IntersectionObserver((entries) => {
+
entries.forEach(entry => {
+
if (entry.isIntersecting) {
+
const year = entry.target.getAttribute('data-year');
+
const month = entry.target.getAttribute('data-month');
+
+
// Get the active tab
+
const activeTab = document.querySelector('.tab-content.active');
+
const activeTabId = activeTab.getAttribute('data-tab');
+
+
// Only process if we're on posts or links tab
+
if ((activeTabId === 'posts' || activeTabId === 'links') && year && month) {
+
// Clear all active classes
+
document.querySelectorAll('.timeline-year.active, .timeline-month.active').forEach(el => {
+
el.classList.remove('active');
+
});
+
+
// Set active classes
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
+
+
if (yearEl) {
+
yearEl.classList.add('active');
+
// Store the last active year globally
+
lastActiveYear = year;
+
}
+
if (monthEl) {
+
monthEl.classList.add('active');
+
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
// Store the last active month globally
+
lastActiveMonth = month;
+
}
+
+
}
+
}
+
});
+
}, options);
+
}
+
function setupTabs() {
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
+
const timeline = document.getElementById('timeline-sidebar');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
···
// Activate selected tab
button.classList.add('active');
document.querySelector(`.tab-content[data-tab="${tabName}"]`).classList.add('active');
+
+
// Show or hide timeline sidebar based on active tab
+
if (tabName === 'people') {
+
timeline.style.display = 'none';
+
document.querySelector('.content').style.paddingRight = '0';
+
} else {
+
timeline.style.display = 'flex';
+
document.querySelector('.content').style.paddingRight = 'var(--sidebar-width)';
+
+
// Reset timeline highlighting when switching between posts/links
+
document.querySelectorAll('.timeline-year.active, .timeline-month.active').forEach(el => {
+
el.classList.remove('active');
+
});
+
+
// Check if we have stored the active year/month
+
// If we don't have stored dates yet, try to find them from the active timeline elements
+
if (!lastActiveYear || !lastActiveMonth) {
+
const activeYearEl = document.querySelector('.timeline-year.active');
+
const activeMonthEl = document.querySelector('.timeline-month.active');
+
+
if (activeYearEl) {
+
lastActiveYear = activeYearEl.getAttribute('data-year');
+
}
+
+
if (activeMonthEl) {
+
lastActiveMonth = activeMonthEl.getAttribute('data-month');
+
} else {
+
// If still no active month, try to find the first visible item in current view
+
const previousTabName = document.querySelector('.tab-button.active').getAttribute('data-tab');
+
const selector = previousTabName === 'posts' ? '.feed-item' : '.link-item';
+
const visibleItems = Array.from(document.querySelectorAll(selector))
+
.filter(item => {
+
const rect = item.getBoundingClientRect();
+
return rect.top >= 0 && rect.bottom <= window.innerHeight;
+
});
+
+
if (visibleItems.length > 0) {
+
lastActiveYear = visibleItems[0].getAttribute('data-year');
+
lastActiveMonth = visibleItems[0].getAttribute('data-month');
+
+
}
+
}
+
}
+
+
+
// If switching to links view, ensure link items are properly observed
+
if (tabName === 'links') {
+
// Disconnect and recreate the observer to ensure proper tracking
+
if (globalFeedObserver) {
+
globalFeedObserver.disconnect();
+
}
+
+
// Setup a new observer
+
globalFeedObserver = setupObserver({
+
root: null,
+
rootMargin: '0px',
+
threshold: 0.3
+
});
+
+
// Observe all items in the active tab
+
observeAllDateItems();
+
+
// If we have active year/month from previous tab, find closest match
+
if (lastActiveYear && lastActiveMonth) {
+
// Find link items from this time period
+
let selector = `.link-item[data-year="${lastActiveYear}"][data-month="${lastActiveMonth}"]`;
+
let matchingItems = document.querySelectorAll(selector);
+
+
+
// If no exact match, try just matching the year
+
if (matchingItems.length === 0) {
+
selector = `.link-item[data-year="${lastActiveYear}"]`;
+
matchingItems = document.querySelectorAll(selector);
+
}
+
+
// If still no match, find the closest date
+
if (matchingItems.length === 0) {
+
const targetDate = new Date(lastActiveYear, lastActiveMonth);
+
const allLinkItems = Array.from(document.querySelectorAll('.link-item'));
+
+
// Sort by closest date
+
if (allLinkItems.length > 0) {
+
allLinkItems.sort((a, b) => {
+
const dateA = new Date(a.getAttribute('data-year'), a.getAttribute('data-month'));
+
const dateB = new Date(b.getAttribute('data-year'), b.getAttribute('data-month'));
+
+
return Math.abs(dateA - targetDate) - Math.abs(dateB - targetDate);
+
});
+
+
// Use closest match
+
if (allLinkItems.length > 0) {
+
matchingItems = [allLinkItems[0]];
+
}
+
}
+
}
+
+
// Scroll to the matching item
+
if (matchingItems.length > 0) {
+
matchingItems[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+
// Update timeline
+
const year = matchingItems[0].getAttribute('data-year');
+
const month = matchingItems[0].getAttribute('data-month');
+
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
+
+
if (yearEl) yearEl.classList.add('active');
+
if (monthEl) {
+
monthEl.classList.add('active');
+
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
}
+
}
+
} else {
+
// If no active period, default to highlighting first visible
+
const visibleLinks = Array.from(document.querySelectorAll('.link-item'))
+
.filter(item => {
+
const rect = item.getBoundingClientRect();
+
return rect.top >= 0 && rect.bottom <= window.innerHeight;
+
});
+
+
if (visibleLinks.length > 0) {
+
const firstVisible = visibleLinks[0];
+
const year = firstVisible.getAttribute('data-year');
+
const month = firstVisible.getAttribute('data-month');
+
+
if (year && month) {
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
+
+
if (yearEl) yearEl.classList.add('active');
+
if (monthEl) {
+
monthEl.classList.add('active');
+
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
}
+
}
+
}
+
}
+
} else if (tabName === 'posts') {
+
// Same for posts view
+
if (globalFeedObserver) {
+
globalFeedObserver.disconnect();
+
}
+
+
globalFeedObserver = setupObserver({
+
root: null,
+
rootMargin: '0px',
+
threshold: 0.3
+
});
+
+
observeAllDateItems();
+
+
// If we have active year/month from previous tab, find closest match
+
if (lastActiveYear && lastActiveMonth) {
+
// Find feed items from this time period
+
let selector = `.feed-item[data-year="${lastActiveYear}"][data-month="${lastActiveMonth}"]`;
+
let matchingItems = document.querySelectorAll(selector);
+
+
+
// If no exact match, try just matching the year
+
if (matchingItems.length === 0) {
+
selector = `.feed-item[data-year="${lastActiveYear}"]`;
+
matchingItems = document.querySelectorAll(selector);
+
}
+
+
// If still no match, find the closest date
+
if (matchingItems.length === 0) {
+
const targetDate = new Date(lastActiveYear, lastActiveMonth);
+
const allFeedItems = Array.from(document.querySelectorAll('.feed-item'));
+
+
// Sort by closest date
+
if (allFeedItems.length > 0) {
+
allFeedItems.sort((a, b) => {
+
const dateA = new Date(a.getAttribute('data-year'), a.getAttribute('data-month'));
+
const dateB = new Date(b.getAttribute('data-year'), b.getAttribute('data-month'));
+
+
return Math.abs(dateA - targetDate) - Math.abs(dateB - targetDate);
+
});
+
+
// Use closest match
+
if (allFeedItems.length > 0) {
+
matchingItems = [allFeedItems[0]];
+
}
+
}
+
}
+
+
// Scroll to the matching item
+
if (matchingItems.length > 0) {
+
matchingItems[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+
// Update timeline
+
const year = matchingItems[0].getAttribute('data-year');
+
const month = matchingItems[0].getAttribute('data-month');
+
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
+
+
if (yearEl) yearEl.classList.add('active');
+
if (monthEl) {
+
monthEl.classList.add('active');
+
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
}
+
}
+
} else {
+
// If no active period, default to highlighting first visible
+
const visiblePosts = Array.from(document.querySelectorAll('.feed-item'))
+
.filter(item => {
+
const rect = item.getBoundingClientRect();
+
return rect.top >= 0 && rect.bottom <= window.innerHeight;
+
});
+
+
if (visiblePosts.length > 0) {
+
const firstVisible = visiblePosts[0];
+
const year = firstVisible.getAttribute('data-year');
+
const month = firstVisible.getAttribute('data-month');
+
+
if (year && month) {
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`);
+
+
if (yearEl) yearEl.classList.add('active');
+
if (monthEl) {
+
monthEl.classList.add('active');
+
monthEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
}
+
}
+
}
+
}
+
}
+
}
});
});
}
const feedItemsContainer = document.getElementById('feed-items');
const loadingContainer = document.getElementById('loading');
// Function to format date (only date, no time)
function formatDate(dateString) {
···
const entries = xmlDoc.getElementsByTagName('entry');
const sources = new Set();
+
// No longer updating the entry count element since it's been removed
// Map to store entries by ID for easy lookup
const entriesById = {};
···
// All articles have been processed in the main loop above
+
// No longer updating the source count element since it's been removed
// No toggle functions needed anymore
···
// Skip adding data attributes - we've already done this during HTML generation
// Create observer to track which period is in view
+
globalFeedObserver = setupObserver(observerOptions);
// Hide loading, show content
loadingContainer.style.display = 'none';
feedItemsContainer.innerHTML = entriesHTML;
+
// Helper function to observe all items with date attributes
+
function observeAllDateItems() {
+
// Observe all feed items for scroll tracking
+
document.querySelectorAll('.feed-item').forEach(item => {
+
globalFeedObserver.observe(item);
+
});
+
+
// Also observe link items for timeline highlighting
+
document.querySelectorAll('.link-item').forEach(item => {
+
globalFeedObserver.observe(item);
+
});
+
}
+
// Initial observation of all items
+
observeAllDateItems();
+
+
// Set initial display state for timeline based on initial active tab
+
const initialActiveTab = document.querySelector('.tab-button.active').getAttribute('data-tab');
+
if (initialActiveTab === 'people') {
+
document.getElementById('timeline-sidebar').style.display = 'none';
+
document.querySelector('.content').style.paddingRight = '0';
+
} else {
+
// Initialize the last active date from the first visible item
+
const selector = initialActiveTab === 'posts' ? '.feed-item' : '.link-item';
+
const visibleItems = Array.from(document.querySelectorAll(selector))
+
.filter(item => {
+
const rect = item.getBoundingClientRect();
+
return rect.top >= 0 && rect.bottom <= window.innerHeight;
+
});
+
+
if (visibleItems.length > 0) {
+
lastActiveYear = visibleItems[0].getAttribute('data-year');
+
lastActiveMonth = visibleItems[0].getAttribute('data-month');
+
}
+
}
// Set up hover effects
setupHoverEffects();
···
// Update the links container
linksContainer.innerHTML = linksHTML;
+
// Process people data
+
const peopleContainer = document.querySelector('.people-container');
+
const peopleMap = new Map(); // Map to store people data
+
+
// Fetch the mapping.json file to get author information
+
const mappingResponse = await fetch('mapping.json');
+
if (!mappingResponse.ok) {
+
throw new Error('Failed to fetch mapping data');
+
}
+
const mappingData = await mappingResponse.json();
+
+
// Process author information from mapping data
+
Object.entries(mappingData).forEach(([feedUrl, info]) => {
+
const { name, site } = info;
+
if (!peopleMap.has(name)) {
+
peopleMap.set(name, {
+
name: name,
+
site: site,
+
feedUrl: feedUrl,
+
posts: [],
+
postCount: 0,
+
mostRecent: null
+
});
+
}
+
});
+
+
// Associate entries with authors
+
entriesArray.forEach(entry => {
+
// Find the person who matches this entry's author
+
// (taking into account potential differences in formatting)
+
const person = Array.from(peopleMap.values()).find(p =>
+
p.name === entry.author ||
+
entry.author.includes(p.name) ||
+
p.name.includes(entry.author)
+
);
+
+
if (person) {
+
person.posts.push(entry);
+
person.postCount++;
+
+
// Track most recent post date
+
const entryDate = new Date(entry.published);
+
if (!person.mostRecent || entryDate > new Date(person.mostRecent.published)) {
+
person.mostRecent = entry;
+
}
+
}
+
});
+
+
// Generate HTML for people cards
+
let peopleHTML = '';
+
Array.from(peopleMap.values())
+
.sort((a, b) => b.postCount - a.postCount) // Sort by post count
+
.forEach(person => {
+
const recentPosts = person.posts
+
.sort((a, b) => new Date(b.published) - new Date(a.published))
+
.slice(0, 3); // Get top 3 most recent posts
+
+
peopleHTML += `
+
<div class="person-card">
+
<div class="person-name">${person.name}</div>
+
<div class="person-site"><a href="${person.feedUrl}" target="_blank" rel="noopener">${person.site}</a></div>
+
+
<div class="person-stats">
+
<div class="person-stat">
+
<div class="stat-value">${person.postCount}</div>
+
<div class="stat-label">Posts</div>
+
</div>
+
<div class="person-stat">
+
<div class="stat-value">${person.mostRecent ? formatDate(person.mostRecent.published) : 'N/A'}</div>
+
<div class="stat-label">Latest</div>
+
</div>
+
</div>
+
+
${recentPosts.length > 0 ? `
+
<div class="person-recent">
+
<div class="recent-title">RECENT POSTS</div>
+
<div class="recent-posts">
+
${recentPosts.map(post => `
+
<div class="recent-post">
+
<a href="${post.link}" target="_blank">${post.title}</a>
+
<div class="recent-post-date">${formatDate(post.published)}</div>
+
</div>
+
`).join('')}
+
</div>
+
</div>
+
` : ''}
+
</div>
+
`;
+
});
+
+
peopleContainer.innerHTML = peopleHTML;
+
// Initialize tabs
setupTabs();
+
// Make timeline items clickable to scroll to relevant posts or links
document.querySelectorAll('.timeline-year, .timeline-month').forEach(item => {
item.addEventListener('click', () => {
const year = item.getAttribute('data-year');
const month = item.getAttribute('data-month');
+
// Store the selected date globally
+
lastActiveYear = year;
+
if (month !== null && month !== undefined) {
+
lastActiveMonth = month;
+
}
+
+
// Find the first element with this date
let selector = `[data-year="${year}"]`;
if (month !== null && month !== undefined) {
···
// Get the active tab
const activeTab = document.querySelector('.tab-content.active');
+
const activeTabId = activeTab.getAttribute('data-tab');
// Look for the target within the active tab
const targetItem = activeTab.querySelector(selector);
+
// If no matching items in this tab or people tab is active, do nothing
+
if (targetItem && activeTabId !== 'people') {
targetItem.scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+
// Highlight the selected timeline period
+
document.querySelectorAll('.timeline-year.active, .timeline-month.active').forEach(el => {
+
el.classList.remove('active');
+
});
+
+
// Set active classes
+
const yearEl = document.querySelector(`.timeline-year[data-year="${year}"]`);
+
const monthEl = month !== null && month !== undefined ?
+
document.querySelector(`.timeline-month[data-year="${year}"][data-month="${month}"]`) : null;
+
+
if (yearEl) yearEl.classList.add('active');
+
if (monthEl) monthEl.classList.add('active');
}
});
});