Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Photo Album β Video Slideshow</title>
<style>
* {
box-sizing: border-box;
user-select: none; /* avoid accidental selection while clicking */
}
body {
background: #1a1a2e;
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: system-ui, 'Segoe UI', 'Roboto', sans-serif;
padding: 20px;
}
.album-container {
background: #0f0f1a;
border-radius: 40px;
padding: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);
max-width: 1000px;
width: 100%;
}
.viewer {
background: #000;
border-radius: 28px;
overflow: hidden;
aspect-ratio: 16 / 9; /* consistent frame, change to auto if needed */
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
}
.viewer img {
width: 100%;
height: 100%;
object-fit: contain;
background: #111;
display: block;
transition: opacity 0.2s ease;
}
.controls {
margin-top: 24px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
align-items: center;
}
button {
background: #2d2d44;
border: none;
color: white;
font-size: 1.5rem;
font-weight: bold;
padding: 10px 24px;
border-radius: 60px;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 8px;
}
button:active {
transform: scale(0.96);
}
button:hover {
background: #3d3d60;
}
.play-pause {
background: #0f7b6e;
min-width: 110px;
justify-content: center;
}
.play-pause:hover {
background: #139e8c;
}
.reset-btn {
background: #4a2e6b;
}
.counter {
background: #000000aa;
backdrop-filter: blur(4px);
padding: 6px 14px;
border-radius: 60px;
font-weight: 500;
font-size: 1rem;
color: #f0f0f0;
letter-spacing: 0.5px;
}
.slider-container {
flex: 1;
min-width: 150px;
display: flex;
align-items: center;
gap: 12px;
background: #1e1e2a;
padding: 5px 15px;
border-radius: 60px;
}
input[type="range"] {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: #4a4a6a;
border-radius: 5px;
outline: none;
}
input[type="range"]:focus {
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: #6c9eff;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 4px white;
}
.status {
font-size: 0.8rem;
color: #aaa;
text-align: center;
margin-top: 18px;
background: #0b0b12;
display: inline-block;
width: auto;
padding: 5px 18px;
border-radius: 60px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
margin-top: 16px;
gap: 12px;
}
@media (max-width: 640px) {
button { padding: 6px 16px; font-size: 1.2rem; }
.controls { gap: 10px; }
.slider-container { min-width: 120px; }
}
.warning {
background: #ffb34720;
border-left: 4px solid #ffb347;
padding: 8px 12px;
font-size: 0.8rem;
border-radius: 20px;
color: #ddd;
}
</style>
</head>
<body>
<div class="album-container">
<div class="viewer">
<img id="slideshow-img" alt="album slide" src="">
</div>
<div class="controls">
<button id="prevBtn" title="Previous">β Prev</button>
<button id="playPauseBtn" class="play-pause">βΆ Play</button>
<button id="nextBtn" title="Next">Next βΆ</button>
<div class="slider-container">
<span style="font-size:0.9rem;">π·</span>
<input type="range" id="slideSlider" min="0" max="0" value="0" step="1">
<span id="slideCounter" class="counter">0 / 0</span>
</div>
</div>
<div class="footer">
<div class="status" id="statusMsg">β³ Loading images...</div>
<div class="warning">β οΈ Make sure your image files are named like <strong>10.png, 11.png ... 50.png</strong> in same folder as this HTML file.</div>
</div>
</div>
<script>
// --------------------------------------------------------------
// CORRECTED SEQUENCE (duplicates removed: only one 42 and one 45)
// Missing numbers automatically skipped later (no 16,17,24,35,43,44,47)
// --------------------------------------------------------------
const RAW_SEQUENCE = [
10, 11, 12, 13, 14, 15,
18, 19, 20, 21, 22, 23,
25, 26, 27, 28, 29, 30,
31, 32, 33, 34,
36, 37, 38, 39, 40, 41, 42,
45, 46,
48, 49, 50
];
// Extension β you can change to .jpg or .jpeg if needed (try both)
const EXTENSION = "png"; // change to "jpg" if your files are .jpg
// If some images are mixed, you could add fallback, but we keep simple.
let imageList = []; // stores actual reachable image paths
let currentIndex = 0;
let timer = null;
let isPlaying = false;
let slideDuration = 2000; // 2 seconds per image (video feel)
// DOM elements
const imgElement = document.getElementById("slideshow-img");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const playPauseBtn = document.getElementById("playPauseBtn");
const slider = document.getElementById("slideSlider");
const counterSpan = document.getElementById("slideCounter");
const statusSpan = document.getElementById("statusMsg");
// --------------------------------------------------------------
// Check which images actually exist (skip missing files)
// --------------------------------------------------------------
async function buildValidImageList() {
const valid = [];
for (let num of RAW_SEQUENCE) {
const candidatePath = `${num}.${EXTENSION}`;
const exists = await imageExists(candidatePath);
if (exists) {
valid.push(candidatePath);
} else {
console.warn(`Missing: ${candidatePath} β skipping`);
statusSpan.innerHTML = `β οΈ Missing ${candidatePath}, skipped. Try changing extension in script.`;
// but we keep showing warning briefly, we'll update later
}
}
if (valid.length === 0) {
statusSpan.innerHTML = `β No images found! Check file names (${RAW_SEQUENCE[0]}.${EXTENSION} etc) or change EXTENSION in code.`;
return [];
}
statusSpan.innerHTML = `β
Loaded ${valid.length} images (video mode ready)`;
return valid;
}
function imageExists(url) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = url;
});
}
// --------------------------------------------------------------
// Slideshow controls & rendering
// --------------------------------------------------------------
function updateDisplay() {
if (!imageList.length) {
imgElement.alt = "No images available";
imgElement.src = "";
counterSpan.innerText = `0 / 0`;
slider.max = 0;
slider.value = 0;
return;
}
const path = imageList[currentIndex];
imgElement.src = path;
imgElement.alt = `slide ${currentIndex+1}`;
counterSpan.innerText = `${currentIndex+1} / ${imageList.length}`;
slider.max = imageList.length - 1;
slider.value = currentIndex;
// optional: preload next image for smoother experience
if (currentIndex + 1 < imageList.length) {
const preloadImg = new Image();
preloadImg.src = imageList[currentIndex + 1];
}
}
function nextSlide() {
if (!imageList.length) return;
if (currentIndex + 1 < imageList.length) {
currentIndex++;
updateDisplay();
} else {
// loop back to start (optional: you can also stop playing)
if (isPlaying) {
// if it's end and playing, loop to beginning seamlessly
currentIndex = 0;
updateDisplay();
} else {
// if not playing, just stay at last, but we allow wrap manually?
// but for video album feeling: we can loop anyway
currentIndex = 0;
updateDisplay();
// pause if desired? Let's keep consistent: when next at end, jump to start
// But to avoid confusion, we also reset timer if playing
if (isPlaying) resetTimer();
}
}
if (isPlaying) resetTimer();
}
function prevSlide() {
if (!imageList.length) return;
if (currentIndex - 1 >= 0) {
currentIndex--;
} else {
currentIndex = imageList.length - 1; // wrap around
}
updateDisplay();
if (isPlaying) resetTimer();
}
function startPlayback() {
if (timer) clearInterval(timer);
if (!imageList.length) return;
timer = setInterval(() => {
// move to next, with wrap
if (currentIndex + 1 < imageList.length) {
currentIndex++;
updateDisplay();
} else {
// loop: go back to first (video album infinite replay)
currentIndex = 0;
updateDisplay();
}
}, slideDuration);
isPlaying = true;
playPauseBtn.innerHTML = "βΈ Pause";
}
function stopPlayback() {
if (timer) {
clearInterval(timer);
timer = null;
}
isPlaying = false;
playPauseBtn.innerHTML = "βΆ Play";
}
function resetTimer() {
if (isPlaying) {
// restart interval to keep consistent timing
if (timer) clearInterval(timer);
timer = setInterval(() => {
if (currentIndex + 1 < imageList.length) {
currentIndex++;
updateDisplay();
} else {
currentIndex = 0;
updateDisplay();
}
}, slideDuration);
}
}
function togglePlayPause() {
if (!imageList.length) return;
if (isPlaying) {
stopPlayback();
} else {
startPlayback();
}
}
function onSliderChange() {
if (!imageList.length) return;
const newIdx = parseInt(slider.value, 10);
if (!isNaN(newIdx) && newIdx !== currentIndex && newIdx >= 0 && newIdx < imageList.length) {
currentIndex = newIdx;
updateDisplay();
if (isPlaying) resetTimer();
}
}
// --------------------------------------------------------------
// initialization
// --------------------------------------------------------------
async function init() {
imageList = await buildValidImageList();
if (imageList.length === 0) {
// show dummy placeholder
imgElement.alt = "No valid images found";
imgElement.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300' viewBox='0 0 400 300'%3E%3Crect width='400' height='300' fill='%23222'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23aaa' font-size='16'%3Eβ οΈ No images%3C/text%3E%3C/svg%3E";
counterSpan.innerText = "0 / 0";
slider.disabled = true;
playPauseBtn.disabled = true;
prevBtn.disabled = true;
nextBtn.disabled = true;
return;
}
slider.disabled = false;
playPauseBtn.disabled = false;
prevBtn.disabled = false;
nextBtn.disabled = false;
currentIndex = 0;
updateDisplay();
// auto-start playing (like a video album)
startPlayback();
}
// attach event listeners
prevBtn.addEventListener("click", () => { prevSlide(); });
nextBtn.addEventListener("click", () => { nextSlide(); });
playPauseBtn.addEventListener("click", togglePlayPause);
slider.addEventListener("input", onSliderChange);
// start everything
init();
</script>
</body>
</html>35
1
14KB
23KB
196.0ms
272.0ms
800.0ms