bigger picture, footer
This commit is contained in:
128
apps.js
128
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,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();
|
||||
22
index.html
22
index.html
@@ -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>
|
||||
@@ -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 |
123
styles.css
123
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 {
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user