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 playerTimeContainer = document.getElementById('playerTimeContainer');
|
||||||
const remainingTimeContainer = document.getElementById('remainingTimeContainer');
|
const remainingTimeContainer = document.getElementById('remainingTimeContainer');
|
||||||
const playerRemainingTime = document.getElementById('playerRemainingTime');
|
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
|
// Add sound toggle button
|
||||||
const createSoundToggleButton = () => {
|
const createSoundToggleButton = () => {
|
||||||
@@ -379,6 +387,7 @@ setupButton.addEventListener('click', () => {
|
|||||||
|
|
||||||
playerModal.classList.add('active');
|
playerModal.classList.add('active');
|
||||||
deletePlayerButton.style.display = 'block';
|
deletePlayerButton.style.display = 'block';
|
||||||
|
cleanupCameraData();
|
||||||
|
|
||||||
// Play modal open sound
|
// Play modal open sound
|
||||||
audioManager.play('modalOpen');
|
audioManager.play('modalOpen');
|
||||||
@@ -401,6 +410,7 @@ addPlayerButton.addEventListener('click', () => {
|
|||||||
|
|
||||||
playerModal.classList.add('active');
|
playerModal.classList.add('active');
|
||||||
deletePlayerButton.style.display = 'none';
|
deletePlayerButton.style.display = 'none';
|
||||||
|
cleanupCameraData();
|
||||||
|
|
||||||
// Play modal open sound
|
// Play modal open sound
|
||||||
audioManager.play('modalOpen');
|
audioManager.play('modalOpen');
|
||||||
@@ -464,7 +474,78 @@ function parseTimeString(timeString) {
|
|||||||
return (minutes * 60) + seconds;
|
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) => {
|
playerForm.addEventListener('submit', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -484,11 +565,11 @@ playerForm.addEventListener('submit', (e) => {
|
|||||||
remainingTimeValue = parseTimeString(remainingTimeString);
|
remainingTimeValue = parseTimeString(remainingTimeString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get image if uploaded
|
// Check for captured image from camera
|
||||||
const imageFile = document.getElementById('playerImage').files[0];
|
|
||||||
let playerImageData = null;
|
let playerImageData = null;
|
||||||
|
const capturedImage = playerImage.dataset.capturedImage;
|
||||||
|
|
||||||
// Function to proceed with saving player
|
// Define the savePlayer function
|
||||||
const savePlayer = () => {
|
const savePlayer = () => {
|
||||||
const isNewPlayer = document.getElementById('modalTitle').textContent === 'Add New Player';
|
const isNewPlayer = document.getElementById('modalTitle').textContent === 'Add New Player';
|
||||||
|
|
||||||
@@ -531,12 +612,19 @@ playerForm.addEventListener('submit', (e) => {
|
|||||||
saveData();
|
saveData();
|
||||||
playerModal.classList.remove('active');
|
playerModal.classList.remove('active');
|
||||||
document.getElementById('playerImage').value = '';
|
document.getElementById('playerImage').value = '';
|
||||||
|
playerImage.dataset.capturedImage = ''; // Clear captured image data
|
||||||
|
|
||||||
// Also play modal close sound
|
// Also play modal close sound
|
||||||
audioManager.play('modalClose');
|
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) {
|
if (imageFile) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
@@ -551,6 +639,7 @@ playerForm.addEventListener('submit', (e) => {
|
|||||||
}
|
}
|
||||||
savePlayer();
|
savePlayer();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cancel button
|
// Cancel button
|
||||||
@@ -558,6 +647,7 @@ cancelButton.addEventListener('click', () => {
|
|||||||
audioManager.play('buttonClick');
|
audioManager.play('buttonClick');
|
||||||
playerModal.classList.remove('active');
|
playerModal.classList.remove('active');
|
||||||
document.getElementById('playerImage').value = '';
|
document.getElementById('playerImage').value = '';
|
||||||
|
cleanupCameraData();
|
||||||
audioManager.play('modalClose');
|
audioManager.play('modalClose');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -579,6 +669,7 @@ deletePlayerButton.addEventListener('click', () => {
|
|||||||
renderPlayers();
|
renderPlayers();
|
||||||
saveData();
|
saveData();
|
||||||
playerModal.classList.remove('active');
|
playerModal.classList.remove('active');
|
||||||
|
cleanupCameraData();
|
||||||
|
|
||||||
// Play player deleted sound
|
// Play player deleted sound
|
||||||
audioManager.play('playerDeleted');
|
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
|
// Initialize the app
|
||||||
loadData();
|
loadData();
|
||||||
22
index.html
22
index.html
@@ -46,7 +46,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="playerImage">Image</label>
|
<label for="playerImage">Image</label>
|
||||||
|
<div class="image-input-container">
|
||||||
<input type="file" id="playerImage" accept="image/*">
|
<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;">
|
<div id="imagePreview" class="player-image" style="margin-top: 0.5rem;">
|
||||||
<i class="fas fa-user"></i>
|
<i class="fas fa-user"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,6 +88,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="audio.js"></script>
|
||||||
<script type="module" src="apps.js"></script>
|
<script type="module" src="apps.js"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -92,5 +108,11 @@
|
|||||||
.catch((err) => console.log("Service Worker Failed", err));
|
.catch((err) => console.log("Service Worker Failed", err));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<footer class="app-footer">
|
||||||
|
<div class="author-info">
|
||||||
|
<p>Vibe coded by Martin</p>
|
||||||
|
<p>Version 0.0.1</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"screenshots": [
|
"screenshots": [
|
||||||
{
|
{
|
||||||
"src": "/screenshots/screenshot1.png",
|
"src": "/screenshots/screenshot1.png",
|
||||||
"sizes": "1302x1122",
|
"sizes": "2604x2269",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"form_factor": "wide"
|
"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 {
|
.carousel-container {
|
||||||
margin-top: 70px;
|
margin-top: 70px;
|
||||||
|
margin-bottom: 60px; /* Add some space for the footer */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -84,13 +85,20 @@ body {
|
|||||||
height: calc(100vh - 70px);
|
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 {
|
.player-card {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1rem;
|
padding: 2rem; /* Increased from 1rem for more spacing */
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,15 +151,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.player-image {
|
.player-image {
|
||||||
width: 120px;
|
width: 240px; /* Doubled from 120px */
|
||||||
height: 120px;
|
height: 240px; /* Doubled from 120px */
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 2rem; /* Increased from 1rem */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Added shadow for better visual presence */
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-image img {
|
.player-image img {
|
||||||
@@ -161,14 +170,15 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.player-image i {
|
.player-image i {
|
||||||
font-size: 3rem;
|
font-size: 6rem; /* Doubled from 3rem */
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-name {
|
.player-name {
|
||||||
font-size: 1.5rem;
|
font-size: 3rem; /* Doubled from 1.5rem */
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1rem; /* Increased from 0.5rem */
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
@@ -259,3 +269,102 @@ body {
|
|||||||
color: white;
|
color: white;
|
||||||
width: 100%;
|
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