const themeToggleBtn = document.getElementById('theme-toggle'); const roomConfigForm = document.getElementById('room-config-form'); const statusLine = document.getElementById('config-status'); const scaleSelect = document.getElementById('estimation-scale'); const maxPeopleInput = document.getElementById('max-people'); const previewScale = document.getElementById('preview-scale'); const previewMaxPeople = document.getElementById('preview-max-people'); const previewCards = document.getElementById('preview-cards'); const customCardInput = document.getElementById('custom-card'); const addCardButton = document.getElementById('add-card'); const SCALE_PRESETS = { fibonacci: ['0', '1', '2', '3', '5', '8', '13', '21', '?'], tshirt: ['XS', 'S', 'M', 'L', 'XL', '?'], 'powers-of-two': ['1', '2', '4', '8', '16', '32', '?'], }; let isDarkMode = false; let nextCardID = 1; let currentCards = []; themeToggleBtn.addEventListener('click', () => { isDarkMode = !isDarkMode; if (isDarkMode) { document.documentElement.setAttribute('data-theme', 'dark'); themeToggleBtn.textContent = 'Light Mode'; return; } document.documentElement.removeAttribute('data-theme'); themeToggleBtn.textContent = 'Dark Mode'; }); function createCard(value) { return { id: nextCardID++, value: value.toString() }; } function getCardsForScale(scale) { return (SCALE_PRESETS[scale] || SCALE_PRESETS.fibonacci).map(createCard); } function captureCardPositions() { const positions = new Map(); previewCards.querySelectorAll('.preview-card').forEach((el) => { positions.set(el.dataset.cardId, el.getBoundingClientRect()); }); return positions; } function animateCardReflow(previousPositions) { previewCards.querySelectorAll('.preview-card').forEach((el) => { const oldRect = previousPositions.get(el.dataset.cardId); if (!oldRect) { el.style.opacity = '0'; el.style.transform = 'scale(0.85)'; requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translate(0, 0) scale(1)'; }); return; } const newRect = el.getBoundingClientRect(); const deltaX = oldRect.left - newRect.left; const deltaY = oldRect.top - newRect.top; if (deltaX === 0 && deltaY === 0) { return; } el.style.transform = `translate(${deltaX}px, ${deltaY}px)`; requestAnimationFrame(() => { el.style.transform = 'translate(0, 0)'; }); }); } function renderCards(previousPositions = new Map()) { previewCards.innerHTML = ''; currentCards.forEach((card) => { const cardEl = document.createElement('div'); cardEl.className = 'preview-card'; cardEl.dataset.cardId = String(card.id); cardEl.textContent = card.value; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'preview-card-remove'; removeBtn.textContent = 'X'; removeBtn.setAttribute('aria-label', `Remove card ${card.value}`); removeBtn.addEventListener('click', (event) => { event.stopPropagation(); removeCard(card.id); }); cardEl.appendChild(removeBtn); previewCards.appendChild(cardEl); }); animateCardReflow(previousPositions); } function removeCard(cardID) { const cardEl = previewCards.querySelector(`[data-card-id="${cardID}"]`); if (!cardEl) { return; } cardEl.classList.add('is-removing'); window.setTimeout(() => { const previousPositions = captureCardPositions(); currentCards = currentCards.filter((card) => card.id !== cardID); renderCards(previousPositions); }, 160); } function resetCardsForCurrentScale() { const previousPositions = captureCardPositions(); currentCards = getCardsForScale(scaleSelect.value); renderCards(previousPositions); } function updatePreviewMeta() { previewScale.textContent = `Scale: ${scaleSelect.value}`; previewMaxPeople.textContent = `Max: ${maxPeopleInput.value || 0}`; } addCardButton.addEventListener('click', () => { const value = customCardInput.value.trim(); if (!value) { return; } const previousPositions = captureCardPositions(); currentCards.push(createCard(value.slice(0, 8))); renderCards(previousPositions); customCardInput.value = ''; customCardInput.focus(); }); customCardInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); addCardButton.click(); } }); scaleSelect.addEventListener('change', () => { resetCardsForCurrentScale(); updatePreviewMeta(); statusLine.textContent = 'Card deck reset to selected estimation scale.'; }); maxPeopleInput.addEventListener('input', () => { updatePreviewMeta(); }); roomConfigForm.addEventListener('submit', (event) => { event.preventDefault(); const formData = new FormData(roomConfigForm); const roomName = (formData.get('roomName') || '').toString().trim(); const username = (formData.get('username') || '').toString().trim(); if (!roomName || !username) { statusLine.textContent = 'Please fill in both room name and username.'; return; } statusLine.textContent = `Room "${roomName}" prepared by ${username} with ${currentCards.length} cards.`; }); roomConfigForm.addEventListener('reset', () => { window.setTimeout(() => { updatePreviewMeta(); resetCardsForCurrentScale(); statusLine.textContent = 'Room settings reset to defaults.'; }, 0); }); updatePreviewMeta(); resetCardsForCurrentScale();