bigger picture, footer

This commit is contained in:
cpu
2025-03-23 20:14:50 +01:00
parent 1cfcd628d4
commit 8a6947f4ea
6 changed files with 277 additions and 28 deletions

128
apps.js
View File

@@ -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,6 +387,7 @@ 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 = `<img src="${imageDataUrl}" alt="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,12 +612,19 @@ 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
// 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) => {
@@ -551,6 +639,7 @@ playerForm.addEventListener('submit', (e) => {
}
savePlayer();
}
}
});
// Cancel button
@@ -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();

View File

@@ -46,7 +46,12 @@
</div>
<div class="form-group">
<label for="playerImage">Image</label>
<div class="image-input-container">
<input type="file" id="playerImage" accept="image/*">
<button type="button" id="cameraButton" class="camera-button">
<i class="fas fa-camera"></i> Take Photo
</button>
</div>
<div id="imagePreview" class="player-image" style="margin-top: 0.5rem;">
<i class="fas fa-user"></i>
</div>
@@ -83,6 +88,17 @@
</div>
</div>
<div id="cameraContainer" class="camera-container">
<div class="camera-view">
<video id="cameraView" autoplay playsinline></video>
<canvas id="cameraCanvas" style="display: none;"></canvas>
</div>
<div class="camera-controls">
<button id="cameraCancelButton" class="camera-button-cancel">Cancel</button>
<button id="cameraCaptureButton" class="camera-button-large"></button>
</div>
</div>
<script type="module" src="audio.js"></script>
<script type="module" src="apps.js"></script>
<script>
@@ -92,5 +108,11 @@
.catch((err) => console.log("Service Worker Failed", err));
}
</script>
<footer class="app-footer">
<div class="author-info">
<p>Vibe coded by Martin</p>
<p>Version 0.0.1</p>
</div>
</footer>
</body>
</html>

View File

@@ -36,7 +36,7 @@
"screenshots": [
{
"src": "/screenshots/screenshot1.png",
"sizes": "1302x1122",
"sizes": "2604x2269",
"type": "image/png",
"form_factor": "wide"
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 416 KiB

View File

@@ -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 {
@@ -259,3 +269,102 @@ body {
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;
}