/** * View Transition Manager for CMS Collection Lists * * Handles smooth view transitions between collection list items and their destination pages. * Automatically assigns unique transition names to images within CMS groups to prevent conflicts * when multiple images share the same view-transition-name attribute value. * * Usage: * - Add r-view-transition-img="group-name" to images in collection lists * - Each group gets unique numbered transition names: group-name-0, group-name-1, etc. * - On click, the selected image gets the target transition name to match destination page */ const TRANSITIONCONFIG = { selectors: { cardImage: "[r-view-transition-img]", // Image from triggering card clickableLink: "a.g_clickable_link", // Clickable link within the card }, transition_names: { targetHero: "main-hero-img", // Name for the destination page's hero image }, }; /** * Handles view transitions for collection list cards * Ensures smooth transitions between card images and destination page hero */ class ViewTransitionManager { constructor() { this.cardImages = null; } /** * Initialize the view transition system */ init() { // Check if View Transition API is supported if (!document.startViewTransition) { return; } this.cacheElements(); if (!this.cardImages?.length) return; this.assignUniqueTransitionNames(); this.attachEventListeners(); } /** * Cache DOM elements for performance */ cacheElements() { this.cardImages = document.querySelectorAll( TRANSITIONCONFIG.selectors.cardImage, ); } /** * Assign unique view-transition-name to each card image using dynamic prefixes */ assignUniqueTransitionNames() { // Group images by their attribute values const imageGroups = new Map(); this.cardImages.forEach((img) => { const groupName = img.getAttribute("r-view-transition-img"); if (!groupName || groupName.trim() === "") { return; } const cleanGroupName = groupName.trim(); if (!imageGroups.has(cleanGroupName)) { imageGroups.set(cleanGroupName, []); } imageGroups.get(cleanGroupName).push(img); }); // Assign unique names within each group imageGroups.forEach((images, groupName) => { images.forEach((img, index) => { const transitionName = `${groupName}-${index}`; img.style.viewTransitionName = transitionName; }); }); } /** * Clear all transition names from card images */ clearAllTransitionNames() { this.cardImages.forEach((img) => { img.style.viewTransitionName = ""; }); } /** * Handle card click with view transition * @param {Event} event - Click event * @param {HTMLElement} cardImage - The clicked card's image element * @param {HTMLElement} clickableElement - The clickable element (link or button) */ handleCardClick(event, cardImage, clickableElement) { event.preventDefault(); // Guard clause: ensure clickable element has href (for links) const href = clickableElement.href; if (!href) return; // Reset all transition names this.clearAllTransitionNames(); // Set the clicked card's transition name to match destination cardImage.style.viewTransitionName = TRANSITIONCONFIG.transition_names.targetHero; // Trigger view transition document.startViewTransition(() => { window.location.href = href; }); } /** * Attach click event listeners to all cards */ attachEventListeners() { this.cardImages.forEach((cardImage) => { // Find the collection item wrapper containing the image const collectionItem = cardImage.closest(".w-dyn-item"); if (!collectionItem) return; // Find the clickable link within the collection item const clickableLink = collectionItem.querySelector( TRANSITIONCONFIG.selectors.clickableLink, ); // Guard clause: ensure clickable link exists if (!clickableLink) return; // Create the click handler const handleClick = (event) => { this.handleCardClick(event, cardImage, clickableLink); }; // Attach click handler to the clickable link clickableLink.addEventListener("click", handleClick); }); } } /** * Initialize view transitions when DOM is ready */ document.addEventListener("DOMContentLoaded", () => { const transitionManager = new ViewTransitionManager(); transitionManager.init(); });