generated from hantim/static-site-template
500 lines
14 KiB
HTML
500 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>Suzuki Violin Class - Practice Videos</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
background-color: #000;
|
|
color: #fff;
|
|
height: 100dvh;
|
|
width: 100vw;
|
|
overflow: hidden;
|
|
position: fixed;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
#app {
|
|
height: 100%;
|
|
width: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
/* For desktop, limit max-width to maintain mobile aspect ratio */
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
#videoContainer {
|
|
height: calc(100% - 80px);
|
|
width: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.video-slide {
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 100%;
|
|
top: 0;
|
|
left: 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.video-slide.active {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.video-slide.prev {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
video {
|
|
height: 100%;
|
|
width: 100%;
|
|
object-fit: contain; /* Changed from cover to contain */
|
|
background-color: #000;
|
|
}
|
|
|
|
.video-title {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 10px;
|
|
right: 10px;
|
|
padding: 10px;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
border-radius: 8px;
|
|
z-index: 10;
|
|
}
|
|
|
|
.loading-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
z-index: 20;
|
|
}
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 50%;
|
|
border-top-color: #fff;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Play button styles */
|
|
.play-button-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: none; /* Hidden by default */
|
|
justify-content: center;
|
|
align-items: center;
|
|
background-color: rgba(0, 0, 0, 0.3);
|
|
z-index: 15;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.play-button {
|
|
width: 80px;
|
|
height: 80px;
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
backdrop-filter: blur(2px);
|
|
transition: transform 0.2s, background-color 0.2s;
|
|
}
|
|
|
|
.play-button:hover {
|
|
transform: scale(1.1);
|
|
background-color: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.play-icon {
|
|
width: 30px;
|
|
height: 30px;
|
|
fill: white;
|
|
margin-left: 0px; /* Slight adjustment for visual centering */
|
|
}
|
|
|
|
.mute-button {
|
|
position: absolute;
|
|
bottom: 100px;
|
|
right: 20px;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 10;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.mute-icon, .unmute-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
fill: white;
|
|
}
|
|
|
|
.unmute-icon {
|
|
display: none;
|
|
}
|
|
|
|
#thumbnailBar {
|
|
height: 80px;
|
|
width: 100%;
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
background-color: #111;
|
|
}
|
|
|
|
.thumbnail {
|
|
height: 60px;
|
|
width: 60px;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
border: 2px solid transparent;
|
|
transition: border-color 0.3s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.thumbnail.active {
|
|
border-color: #0095f6;
|
|
}
|
|
|
|
.thumbnail img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
/* Desktop styles */
|
|
@media (min-width: 768px) {
|
|
body {
|
|
background-color: #000; /* Ensure black background extends fully */
|
|
}
|
|
|
|
#app {
|
|
height: 100vh;
|
|
width: 375px; /* Common mobile width */
|
|
max-height: 90vh;
|
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div id="videoContainer"></div>
|
|
<div id="thumbnailBar"></div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Sample video data
|
|
const videoData = [
|
|
{
|
|
src: '/media/violin-class/02/playing-position.mp4',
|
|
thumbnail: '/media/violin-class/02/playing-position.jpg',
|
|
title: 'Playing position<br />Stay still for 5 counts<br />Resting position feet and resting position violin<br />Take a bow'
|
|
},
|
|
{
|
|
src: '/media/violin-class/02/bowhold-bunny.mp4',
|
|
thumbnail: '/media/violin-class/02/bowhold-bunny.jpg',
|
|
title: 'Bowhold Bunny'
|
|
},
|
|
];
|
|
|
|
const videoContainer = document.getElementById('videoContainer');
|
|
const thumbnailBar = document.getElementById('thumbnailBar');
|
|
|
|
let currentVideoIndex = 0;
|
|
let isMuted = false;
|
|
|
|
// Create video slides
|
|
videoData.forEach((data, index) => {
|
|
// Create video slide
|
|
const videoSlide = document.createElement('div');
|
|
videoSlide.className = `video-slide ${index === 0 ? 'active' : ''}`;
|
|
videoSlide.dataset.index = index;
|
|
|
|
// Create video element
|
|
const video = document.createElement('video');
|
|
video.loop = true;
|
|
video.muted = isMuted;
|
|
video.playsInline = true;
|
|
video.dataset.index = index;
|
|
|
|
// Create source element
|
|
const source = document.createElement('source');
|
|
source.src = data.src;
|
|
source.type = 'video/mp4';
|
|
|
|
// Create title overlay
|
|
const titleOverlay = document.createElement('div');
|
|
titleOverlay.className = 'video-title';
|
|
titleOverlay.innerHTML = data.title;
|
|
|
|
// Create loading overlay
|
|
const loadingOverlay = document.createElement('div');
|
|
loadingOverlay.className = 'loading-overlay';
|
|
loadingOverlay.innerHTML = '<div class="spinner"></div>';
|
|
|
|
// Create play button overlay
|
|
const playButtonOverlay = document.createElement('div');
|
|
playButtonOverlay.className = 'play-button-overlay';
|
|
playButtonOverlay.innerHTML = `
|
|
<div class="play-button">
|
|
<svg class="play-icon" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z"/>
|
|
</svg>
|
|
</div>
|
|
`;
|
|
|
|
// Create mute button
|
|
const muteButton = document.createElement('div');
|
|
muteButton.className = 'mute-button';
|
|
muteButton.innerHTML = `
|
|
<svg class="mute-icon" viewBox="0 0 24 24">
|
|
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
|
</svg>
|
|
<svg class="unmute-icon" viewBox="0 0 24 24">
|
|
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
|
|
</svg>
|
|
`;
|
|
|
|
// Append elements
|
|
video.appendChild(source);
|
|
videoSlide.appendChild(video);
|
|
videoSlide.appendChild(titleOverlay);
|
|
videoSlide.appendChild(loadingOverlay);
|
|
videoSlide.appendChild(playButtonOverlay);
|
|
videoSlide.appendChild(muteButton);
|
|
videoContainer.appendChild(videoSlide);
|
|
|
|
// Add event listeners for play/pause state
|
|
video.addEventListener('pause', () => {
|
|
playButtonOverlay.style.display = 'flex';
|
|
});
|
|
|
|
video.addEventListener('play', () => {
|
|
playButtonOverlay.style.display = 'none';
|
|
});
|
|
|
|
// Play button click handler
|
|
playButtonOverlay.addEventListener('click', () => {
|
|
video.play().catch(e => console.error("Video play failed:", e));
|
|
});
|
|
|
|
video.addEventListener('waiting', () => {
|
|
loadingOverlay.style.display = 'flex';
|
|
});
|
|
|
|
video.addEventListener('canplay', () => {
|
|
loadingOverlay.style.display = 'none';
|
|
if (video.paused) {
|
|
playButtonOverlay.style.display = 'flex';
|
|
} else {
|
|
playButtonOverlay.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Create thumbnail
|
|
const thumbnail = document.createElement('div');
|
|
thumbnail.className = `thumbnail ${index === 0 ? 'active' : ''}`;
|
|
thumbnail.dataset.index = index;
|
|
|
|
const thumbnailImg = document.createElement('img');
|
|
thumbnailImg.src = data.thumbnail;
|
|
thumbnailImg.alt = `Thumbnail for video ${index + 1}`;
|
|
|
|
thumbnail.appendChild(thumbnailImg);
|
|
thumbnailBar.appendChild(thumbnail);
|
|
|
|
|
|
});
|
|
|
|
// Handle thumbnail clicks
|
|
thumbnailBar.addEventListener('click', (e) => {
|
|
const thumbnail = e.target.closest('.thumbnail');
|
|
if (thumbnail) {
|
|
const newIndex = parseInt(thumbnail.dataset.index);
|
|
if (newIndex !== currentVideoIndex) {
|
|
changeVideo(currentVideoIndex, newIndex);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle video tap to pause/play
|
|
videoContainer.addEventListener('click', (e) => {
|
|
if (e.target.tagName === 'VIDEO') {
|
|
const video = e.target;
|
|
if (video.paused) {
|
|
video.play().catch(e => console.error("Video play failed:", e));
|
|
} else {
|
|
video.pause();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle mute button clicks
|
|
videoContainer.addEventListener('click', (e) => {
|
|
const muteButton = e.target.closest('.mute-button');
|
|
if (muteButton) {
|
|
e.stopPropagation();
|
|
toggleMute();
|
|
}
|
|
});
|
|
|
|
// Handle swipe
|
|
let touchStartX = 0;
|
|
let touchEndX = 0;
|
|
|
|
videoContainer.addEventListener('touchstart', (e) => {
|
|
touchStartX = e.changedTouches[0].screenX;
|
|
}, { passive: true });
|
|
|
|
videoContainer.addEventListener('touchend', (e) => {
|
|
touchEndX = e.changedTouches[0].screenX;
|
|
handleSwipe();
|
|
}, { passive: true });
|
|
|
|
// Add keyboard navigation for desktop
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'ArrowLeft') {
|
|
// Previous video
|
|
if (currentVideoIndex > 0) {
|
|
changeVideo(currentVideoIndex, currentVideoIndex - 1);
|
|
}
|
|
} else if (e.key === 'ArrowRight') {
|
|
// Next video
|
|
if (currentVideoIndex < videoData.length - 1) {
|
|
changeVideo(currentVideoIndex, currentVideoIndex + 1);
|
|
}
|
|
} else if (e.key === ' ' || e.key === 'Spacebar') {
|
|
// Toggle play/pause
|
|
const currentVideo = document.querySelector(`.video-slide[data-index="${currentVideoIndex}"] video`);
|
|
if (currentVideo.paused) {
|
|
currentVideo.play().catch(e => console.error("Video play failed:", e));
|
|
} else {
|
|
currentVideo.pause();
|
|
}
|
|
e.preventDefault();
|
|
} else if (e.key === 'm') {
|
|
// Toggle mute
|
|
toggleMute();
|
|
}
|
|
});
|
|
|
|
function handleSwipe() {
|
|
const swipeThreshold = 50;
|
|
if (touchEndX + swipeThreshold < touchStartX) {
|
|
// Swipe left - next video
|
|
if (currentVideoIndex < videoData.length - 1) {
|
|
changeVideo(currentVideoIndex, currentVideoIndex + 1);
|
|
}
|
|
} else if (touchEndX > touchStartX + swipeThreshold) {
|
|
// Swipe right - previous video
|
|
if (currentVideoIndex > 0) {
|
|
changeVideo(currentVideoIndex, currentVideoIndex - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function changeVideo(oldIndex, newIndex) {
|
|
// Update video slides
|
|
const oldSlide = document.querySelector(`.video-slide[data-index="${oldIndex}"]`);
|
|
const newSlide = document.querySelector(`.video-slide[data-index="${newIndex}"]`);
|
|
const oldVideo = oldSlide.querySelector('video');
|
|
const newVideo = newSlide.querySelector('video');
|
|
|
|
// Pause current video
|
|
oldVideo.pause();
|
|
|
|
// Position slides for animation
|
|
if (newIndex > oldIndex) {
|
|
oldSlide.classList.remove('active');
|
|
oldSlide.classList.add('prev');
|
|
newSlide.classList.add('active');
|
|
} else {
|
|
oldSlide.classList.remove('active');
|
|
newSlide.classList.remove('prev');
|
|
newSlide.classList.add('active');
|
|
}
|
|
|
|
// Play new video
|
|
newVideo.muted = isMuted;
|
|
newVideo.play().catch(e => {
|
|
console.error("Video play failed:", e);
|
|
// Show play button on play failure
|
|
const playButtonOverlay = newSlide.querySelector('.play-button-overlay');
|
|
playButtonOverlay.style.display = 'flex';
|
|
});
|
|
|
|
// Update thumbnails
|
|
document.querySelector(`.thumbnail[data-index="${oldIndex}"]`).classList.remove('active');
|
|
document.querySelector(`.thumbnail[data-index="${newIndex}"]`).classList.add('active');
|
|
|
|
// Update current index
|
|
currentVideoIndex = newIndex;
|
|
}
|
|
|
|
function toggleMute() {
|
|
isMuted = !isMuted;
|
|
|
|
// Update all videos
|
|
document.querySelectorAll('video').forEach(video => {
|
|
video.muted = isMuted;
|
|
});
|
|
|
|
// Update mute button icons
|
|
document.querySelectorAll('.mute-icon').forEach(icon => {
|
|
icon.style.display = isMuted ? 'none' : 'block';
|
|
});
|
|
|
|
document.querySelectorAll('.unmute-icon').forEach(icon => {
|
|
icon.style.display = isMuted ? 'block' : 'none';
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|