/** * M2 Component Library - JavaScript Utilities * * Core functions for interactive components: * - Card flip button (dual-faced cards) * - Collapsible panels * - Card popups * - Modal management */ // ============================================ // CARD FLIP FUNCTIONALITY // ============================================ /** * Flip a dual-faced card image between front and back faces * @param {HTMLElement} button - The flip button element */ function flipCard(button) { const container = button.closest('.card-thumb-container, .card-popup-image'); if (!container) return; const img = container.querySelector('img'); if (!img) return; const cardName = img.dataset.cardName; if (!cardName) return; const faces = cardName.split(' // '); if (faces.length < 2) return; // Determine current face (default to 0 = front) const currentFace = parseInt(img.dataset.currentFace || '0', 10); const nextFace = currentFace === 0 ? 1 : 0; const faceName = faces[nextFace]; // Determine image version based on container const isLarge = container.classList.contains('card-thumb-large') || container.classList.contains('card-popup-image'); const version = isLarge ? 'normal' : 'small'; // Update image source img.src = `https://api.scryfall.com/cards/named?fuzzy=${encodeURIComponent(faceName)}&format=image&version=${version}`; img.alt = `${faceName} image`; img.dataset.currentFace = nextFace.toString(); // Update button aria-label const otherFace = faces[currentFace]; button.setAttribute('aria-label', `Flip to ${otherFace}`); } /** * Reset all card images to show front face * Useful when navigating between pages or clearing selections */ function resetCardFaces() { document.querySelectorAll('img[data-card-name][data-current-face]').forEach(img => { const cardName = img.dataset.cardName; const faces = cardName.split(' // '); if (faces.length > 1) { const frontFace = faces[0]; const container = img.closest('.card-thumb-container, .card-popup-image'); const isLarge = container && (container.classList.contains('card-thumb-large') || container.classList.contains('card-popup-image')); const version = isLarge ? 'normal' : 'small'; img.src = `https://api.scryfall.com/cards/named?fuzzy=${encodeURIComponent(frontFace)}&format=image&version=${version}`; img.alt = `${frontFace} image`; img.dataset.currentFace = '0'; } }); } // ============================================ // COLLAPSIBLE PANEL FUNCTIONALITY // ============================================ /** * Toggle a collapsible panel's expanded/collapsed state * @param {string} panelId - The ID of the panel element */ function togglePanel(panelId) { const panel = document.getElementById(panelId); if (!panel) return; const button = panel.querySelector('.panel-toggle'); const content = panel.querySelector('.panel-collapse-content'); if (!button || !content) return; const isExpanded = button.getAttribute('aria-expanded') === 'true'; // Toggle state button.setAttribute('aria-expanded', (!isExpanded).toString()); content.style.display = isExpanded ? 'none' : 'block'; // Toggle classes panel.classList.toggle('panel-expanded', !isExpanded); panel.classList.toggle('panel-collapsed', isExpanded); } /** * Expand a collapsible panel * @param {string} panelId - The ID of the panel element */ function expandPanel(panelId) { const panel = document.getElementById(panelId); if (!panel) return; const button = panel.querySelector('.panel-toggle'); const content = panel.querySelector('.panel-collapse-content'); if (!button || !content) return; button.setAttribute('aria-expanded', 'true'); content.style.display = 'block'; panel.classList.add('panel-expanded'); panel.classList.remove('panel-collapsed'); } /** * Collapse a collapsible panel * @param {string} panelId - The ID of the panel element */ function collapsePanel(panelId) { const panel = document.getElementById(panelId); if (!panel) return; const button = panel.querySelector('.panel-toggle'); const content = panel.querySelector('.panel-collapse-content'); if (!button || !content) return; button.setAttribute('aria-expanded', 'false'); content.style.display = 'none'; panel.classList.add('panel-collapsed'); panel.classList.remove('panel-expanded'); } // ============================================ // MODAL MANAGEMENT // ============================================ /** * Open a modal by ID * @param {string} modalId - The ID of the modal element */ function openModal(modalId) { const modal = document.getElementById(modalId); if (!modal) return; modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // Focus first focusable element in modal const focusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); if (focusable) { setTimeout(() => focusable.focus(), 100); } } /** * Close a modal by ID or element * @param {string|HTMLElement} modalOrId - Modal element or ID */ function closeModal(modalOrId) { const modal = typeof modalOrId === 'string' ? document.getElementById(modalOrId) : modalOrId; if (!modal) return; modal.remove(); // Restore body scroll if no other modals are open if (!document.querySelector('.modal')) { document.body.style.overflow = ''; } } /** * Close all open modals */ function closeAllModals() { document.querySelectorAll('.modal').forEach(modal => modal.remove()); document.body.style.overflow = ''; } // ============================================ // CARD POPUP FUNCTIONALITY // ============================================ /** * Show card details popup on hover or tap * @param {string} cardName - The card name * @param {Object} options - Popup options * @param {string[]} options.tags - Card tags * @param {string[]} options.highlightTags - Tags to highlight * @param {string} options.role - Card role * @param {string} options.layout - Card layout (for flip button) */ function showCardPopup(cardName, options = {}) { // Remove any existing popup closeCardPopup(); const { tags = [], highlightTags = [], role = '', layout = 'normal' } = options; const isDFC = ['modal_dfc', 'transform', 'double_faced_token', 'reversible_card'].includes(layout); const baseName = cardName.split(' // ')[0]; // Create popup HTML const popup = document.createElement('div'); popup.className = 'card-popup'; popup.setAttribute('role', 'dialog'); popup.setAttribute('aria-label', `${cardName} details`); let tagsHTML = ''; if (tags.length > 0) { tagsHTML = '
'; } let roleHTML = ''; if (role) { roleHTML = `