Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ private function addNoteAsTimelineItem(ParagraphInterface $paragraph, DateTimeIm
$image = $paragraph?->field_paragraph_image?->entity?->field_itk_media_image_upload?->entity?->getFileUri();

return [
'id' => '',
'id' => 'note-' . $paragraph->id(),
'date' => $date->format('Y-m-d'),
'month' => $date->format('F Y'),
'title' => $paragraph->field_title->value,
Expand Down
60 changes: 58 additions & 2 deletions web/themes/custom/hoeringsportal/assets/js/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,75 @@
return;
}

// Track all currently intersecting cards
const visibleCards = new Set();

const observerOptions = {
root: null,
rootMargin: OBSERVER_ROOT_MARGIN,
threshold: 0,
};

state.observer = new IntersectionObserver((entries) => {
// Update the visibility set with changed entries
// Only track cards that are actually visible (have dimensions)
entries.forEach((entry) => {
const { cardId } = entry.target.dataset;
const rect = entry.boundingClientRect;
const isVisible = rect.height > 0 && rect.width > 0;

// Only process events from visible cards (ignore hidden horizontal duplicates)
if (!isVisible) {
return;
}

if (entry.isIntersecting) {
const { cardId } = entry.target.dataset;
updateActiveNavLink(cardId);
visibleCards.add(cardId);
} else {
visibleCards.delete(cardId);
}
});

// Find the card that best overlaps the viewport center
// Prefer the topmost card whose bounds contain the center point
const viewportCenter = window.innerHeight / 2;
let bestCardId = null;
let bestScore = -Infinity;

visibleCards.forEach((cardId) => {
// Find the visible card (not the hidden horizontal view duplicate)
const cards = timeline.querySelectorAll(`[data-card-id="${cardId}"]`);
const card = Array.from(cards).find(
(c) => c.getBoundingClientRect().height > 0,
);
if (card) {
const rect = card.getBoundingClientRect();

// Score based on how well the card covers the viewport center
// Higher score = card contains or is closer to center
let score;
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
// Card contains the center point - highest priority
// Prefer cards where center is further from the edges (more centered)
const distFromTop = viewportCenter - rect.top;
const distFromBottom = rect.bottom - viewportCenter;
score = 1000 + Math.min(distFromTop, distFromBottom);
} else {
// Card doesn't contain center - score by distance
const cardCenter = rect.top + rect.height / 2;
score = -Math.abs(cardCenter - viewportCenter);
}

if (score > bestScore) {
bestScore = score;
bestCardId = cardId;
}
}
});

if (bestCardId) {
updateActiveNavLink(bestCardId);
}
}, observerOptions);

elements.cards.forEach((card) => {
Expand Down
Loading