diff --git a/apps.js b/apps.js index a3e1c8e..99987c8 100644 --- a/apps.js +++ b/apps.js @@ -27,6 +27,14 @@ const imagePreview = document.getElementById('imagePreview'); const playerTimeContainer = document.getElementById('playerTimeContainer'); const remainingTimeContainer = document.getElementById('remainingTimeContainer'); const playerRemainingTime = document.getElementById('playerRemainingTime'); +const cameraButton = document.getElementById('cameraButton'); +const cameraContainer = document.getElementById('cameraContainer'); +const cameraView = document.getElementById('cameraView'); +const cameraCanvas = document.getElementById('cameraCanvas'); +const cameraCaptureButton = document.getElementById('cameraCaptureButton'); +const cameraCancelButton = document.getElementById('cameraCancelButton'); + +let stream = null; // Add sound toggle button const createSoundToggleButton = () => { @@ -379,7 +387,8 @@ setupButton.addEventListener('click', () => { playerModal.classList.add('active'); deletePlayerButton.style.display = 'block'; - + cleanupCameraData(); + // Play modal open sound audioManager.play('modalOpen'); }); @@ -401,6 +410,7 @@ addPlayerButton.addEventListener('click', () => { playerModal.classList.add('active'); deletePlayerButton.style.display = 'none'; + cleanupCameraData(); // Play modal open sound audioManager.play('modalOpen'); @@ -464,7 +474,78 @@ function parseTimeString(timeString) { return (minutes * 60) + seconds; } -// Player form submit +// Camera button click handler +cameraButton.addEventListener('click', async (e) => { + e.preventDefault(); + + // Check if the browser supports getUserMedia + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + alert('Your browser does not support camera access or it is not available on this device.'); + return; + } + + try { + // Get permission and access to the camera + stream = await navigator.mediaDevices.getUserMedia({ + video: { + facingMode: 'user', // Default to front camera + width: { ideal: 1280 }, + height: { ideal: 720 } + } + }); + + // Show the camera UI + cameraContainer.classList.add('active'); + + // Attach the stream to the video element + cameraView.srcObject = stream; + } catch (error) { + console.error('Error accessing camera:', error); + alert('Could not access the camera: ' + error.message); + } +}); + +// Camera capture button click handler +cameraCaptureButton.addEventListener('click', () => { + // Set canvas dimensions to match video + cameraCanvas.width = cameraView.videoWidth; + cameraCanvas.height = cameraView.videoHeight; + + // Draw the current video frame to the canvas + const context = cameraCanvas.getContext('2d'); + context.drawImage(cameraView, 0, 0, cameraCanvas.width, cameraCanvas.height); + + // Convert canvas to data URL + const imageDataUrl = cameraCanvas.toDataURL('image/jpeg'); + + // Update the image preview with the captured photo + imagePreview.innerHTML = `Player`; + + // Stop the camera stream and close the camera UI + stopCameraStream(); + cameraContainer.classList.remove('active'); + + // Since we're capturing directly, we don't need to use the file input + // Instead, store the data URL to use when saving the player + playerImage.dataset.capturedImage = imageDataUrl; + playerImage.value = ''; // Clear the file input +}); + +// Camera cancel button handler +cameraCancelButton.addEventListener('click', () => { + stopCameraStream(); + cameraContainer.classList.remove('active'); +}); + +// Function to stop the camera stream +function stopCameraStream() { + if (stream) { + stream.getTracks().forEach(track => track.stop()); + stream = null; + } +} + +// Player form submit - FIXED VERSION playerForm.addEventListener('submit', (e) => { e.preventDefault(); @@ -484,11 +565,11 @@ playerForm.addEventListener('submit', (e) => { remainingTimeValue = parseTimeString(remainingTimeString); } - // Get image if uploaded - const imageFile = document.getElementById('playerImage').files[0]; + // Check for captured image from camera let playerImageData = null; + const capturedImage = playerImage.dataset.capturedImage; - // Function to proceed with saving player + // Define the savePlayer function const savePlayer = () => { const isNewPlayer = document.getElementById('modalTitle').textContent === 'Add New Player'; @@ -531,25 +612,33 @@ playerForm.addEventListener('submit', (e) => { saveData(); playerModal.classList.remove('active'); document.getElementById('playerImage').value = ''; + playerImage.dataset.capturedImage = ''; // Clear captured image data // Also play modal close sound audioManager.play('modalClose'); }; - // Process image if there is one - if (imageFile) { - const reader = new FileReader(); - reader.onload = (event) => { - playerImageData = event.target.result; - savePlayer(); - }; - reader.readAsDataURL(imageFile); - } else { - // Current player's existing image or null - if (document.getElementById('modalTitle').textContent !== 'Add New Player') { - playerImageData = players[currentPlayerIndex].image; - } + // Process image: either from captured image or uploaded file + if (capturedImage) { + playerImageData = capturedImage; savePlayer(); + } else { + // Check for uploaded file + const imageFile = document.getElementById('playerImage').files[0]; + if (imageFile) { + const reader = new FileReader(); + reader.onload = (event) => { + playerImageData = event.target.result; + savePlayer(); + }; + reader.readAsDataURL(imageFile); + } else { + // Current player's existing image or null + if (document.getElementById('modalTitle').textContent !== 'Add New Player') { + playerImageData = players[currentPlayerIndex].image; + } + savePlayer(); + } } }); @@ -558,6 +647,7 @@ cancelButton.addEventListener('click', () => { audioManager.play('buttonClick'); playerModal.classList.remove('active'); document.getElementById('playerImage').value = ''; + cleanupCameraData(); audioManager.play('modalClose'); }); @@ -579,6 +669,7 @@ deletePlayerButton.addEventListener('click', () => { renderPlayers(); saveData(); playerModal.classList.remove('active'); + cleanupCameraData(); // Play player deleted sound audioManager.play('playerDeleted'); @@ -599,5 +690,32 @@ if ('serviceWorker' in navigator) { }); } +// Make sure to handle rotation by adding window event listener for orientation changes +window.addEventListener('orientationchange', () => { + // If camera is active, adjust video dimensions + if (cameraContainer.classList.contains('active') && stream) { + // Give a moment for the orientation to complete + setTimeout(() => { + // This may cause the video to briefly reset but will ensure proper dimensions + cameraView.srcObject = null; + cameraView.srcObject = stream; + }, 300); + } +}); + +// Clean up when the modal is closed +function cleanupCameraData() { + // Clear any captured image data + if (playerImage) { + playerImage.dataset.capturedImage = ''; + } + + // Make sure camera is stopped + stopCameraStream(); + + // Hide camera UI if visible + cameraContainer.classList.remove('active'); +} + // Initialize the app loadData(); \ No newline at end of file diff --git a/index.html b/index.html index f9bbce1..33c2d32 100644 --- a/index.html +++ b/index.html @@ -46,7 +46,12 @@
- +
+ + +
@@ -83,6 +88,17 @@
+
+
+ + +
+
+ + +
+
+ + + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 0d3d771..18f4f65 100644 --- a/manifest.json +++ b/manifest.json @@ -36,7 +36,7 @@ "screenshots": [ { "src": "/screenshots/screenshot1.png", - "sizes": "1302x1122", + "sizes": "2604x2269", "type": "image/png", "form_factor": "wide" }, diff --git a/screenshots/screenshot1.png b/screenshots/screenshot1.png index d140301..4db22a7 100644 Binary files a/screenshots/screenshot1.png and b/screenshots/screenshot1.png differ diff --git a/screenshots/screenshot2.png b/screenshots/screenshot2.png index dbd3c9d..fd590cd 100644 Binary files a/screenshots/screenshot2.png and b/screenshots/screenshot2.png differ diff --git a/styles.css b/styles.css index f61c785..d7e3c1e 100644 --- a/styles.css +++ b/styles.css @@ -72,6 +72,7 @@ body { .carousel-container { margin-top: 70px; + margin-bottom: 60px; /* Add some space for the footer */ width: 100%; overflow: hidden; flex: 1; @@ -84,13 +85,20 @@ body { height: calc(100vh - 70px); } +/* Adjust the preview image in the modal to maintain consistency */ +#imagePreview.player-image { + width: 180px; /* Slightly smaller than the main display but still larger than original 120px */ + height: 180px; + margin: 0.5rem auto; +} + .player-card { min-width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 1rem; + padding: 2rem; /* Increased from 1rem for more spacing */ transition: all 0.3s ease; } @@ -143,15 +151,16 @@ body { } .player-image { - width: 120px; - height: 120px; + width: 240px; /* Doubled from 120px */ + height: 240px; /* Doubled from 120px */ border-radius: 50%; background-color: #ddd; display: flex; align-items: center; justify-content: center; - margin-bottom: 1rem; + margin-bottom: 2rem; /* Increased from 1rem */ overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Added shadow for better visual presence */ } .player-image img { @@ -161,14 +170,15 @@ body { } .player-image i { - font-size: 3rem; + font-size: 6rem; /* Doubled from 3rem */ color: #888; } .player-name { - font-size: 1.5rem; - margin-bottom: 0.5rem; + font-size: 3rem; /* Doubled from 1.5rem */ + margin-bottom: 1rem; /* Increased from 0.5rem */ font-weight: bold; + text-align: center; } .modal { @@ -258,4 +268,103 @@ body { background-color: #e74c3c; color: white; width: 100%; +} + +/* Add these styles to your styles.css file */ + +.image-input-container { + display: flex; + gap: 10px; + align-items: center; + margin-bottom: 10px; +} + +.camera-button { + background-color: #3498db; + color: white; + border: none; + padding: 0.5rem; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; +} + +.camera-button:hover { + background-color: #2980b9; +} + +/* Optional: Hide the default file input appearance and use a custom button */ +input[type="file"] { + max-width: 120px; +} + +.camera-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + z-index: 30; + display: none; + flex-direction: column; +} + +.camera-container.active { + display: flex; +} + +.camera-view { + flex: 1; + position: relative; + overflow: hidden; +} + +.camera-view video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.camera-controls { + display: flex; + justify-content: space-around; + padding: 1rem; + background-color: #222; +} + +.camera-button-large { + width: 60px; + height: 60px; + border-radius: 50%; + background-color: #fff; + border: 3px solid #3498db; + cursor: pointer; +} + +.camera-button-cancel { + background-color: #e74c3c; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; +} + +.app-footer { + background-color: #2c3e50; + color: white; + padding: 1rem; + text-align: center; + font-size: 0.9rem; + margin-top: auto; /* This pushes the footer to the bottom when possible */ +} + +.author-info { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; } \ No newline at end of file