Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Local Image Viewer</title> | |
<!-- Local Tailwind CSS --> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script> | |
tailwind.config = { | |
theme: { | |
extend: { | |
colors: { | |
primary: '#3B82F6', | |
} | |
} | |
} | |
} | |
</script> | |
<!-- Local Font Awesome --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.dropzone { | |
border: 3px dashed #9CA3AF; | |
transition: all 0.3s ease; | |
} | |
.dropzone.active { | |
border-color: #3B82F6; | |
background-color: rgba(59, 130, 246, 0.1); | |
} | |
.image-container { | |
transition: transform 0.3s ease; | |
} | |
.image-container:hover { | |
transform: scale(1.01); | |
} | |
.nav-btn { | |
transition: all 0.2s ease; | |
} | |
.nav-btn:hover { | |
transform: scale(1.1); | |
} | |
.nav-btn:active { | |
transform: scale(0.95); | |
} | |
.file-item:hover { | |
background-color: rgba(59, 130, 246, 0.1); | |
} | |
.file-item.active { | |
background-color: rgba(59, 130, 246, 0.2); | |
border-left: 3px solid #3B82F6; | |
} | |
@keyframes fadeIn { | |
from { | |
opacity: 0; | |
} | |
to { | |
opacity: 1; | |
} | |
} | |
.fade-in { | |
animation: fadeIn 0.3s ease-in-out; | |
} | |
#fullscreenContainer { | |
display: none; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.9); | |
z-index: 1000; | |
justify-content: center; | |
align-items: center; | |
flex-direction: column; | |
} | |
#fullscreenImage { | |
max-width: 90%; | |
max-height: 90%; | |
object-fit: contain; | |
} | |
#fullscreenControls { | |
position: absolute; | |
bottom: 20px; | |
display: flex; | |
gap: 10px; | |
} | |
.sort-option:hover { | |
background-color: rgba(59, 130, 246, 0.1); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<div class="max-w-6xl mx-auto"> | |
<h1 class="text-3xl font-bold text-center text-gray-800 mb-2">Local Image Viewer</h1> | |
<p class="text-center text-gray-600 mb-8">View your local WebP, PNG, JPEG, AVIF, and HEIC files with ease | |
</p> | |
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> | |
<!-- Dropzone area --> | |
<div id="dropzone" class="dropzone p-12 text-center cursor-pointer" role="region" | |
aria-label="File drop zone"> | |
<div class="flex flex-col items-center justify-center"> | |
<i class="fas fa-images text-5xl text-gray-400 mb-4" aria-hidden="true"></i> | |
<h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop Images Here</h3> | |
<p class="text-gray-500 mb-4">or</p> | |
<button id="browseBtn" | |
class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition" | |
aria-label="Browse files"> | |
Browse Files | |
</button> | |
<input type="file" id="fileInput" class="hidden" | |
accept=".webp,.png,.jpg,.jpeg,.avif,.heic,.heif" multiple> | |
</div> | |
</div> | |
<!-- Main viewer area (hidden initially) --> | |
<div id="viewerArea" class="hidden"> | |
<div class="flex flex-col md:flex-row"> | |
<!-- Sidebar with file list --> | |
<div class="w-full md:w-1/4 bg-gray-50 border-r border-gray-200 max-h-[70vh] overflow-y-auto"> | |
<div class="p-4 border-b border-gray-200 flex justify-between items-center"> | |
<h3 class="font-medium text-gray-700">Files (<span id="fileCount">0</span>)</h3> | |
<div class="relative"> | |
<button id="sortBtn" class="text-gray-600 hover:text-gray-800" | |
aria-label="Sort options" aria-haspopup="true" aria-expanded="false"> | |
<i class="fas fa-sort" aria-hidden="true"></i> | |
</button> | |
<div id="sortDropdown" | |
class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10 py-1"> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="name-asc" role="menuitem">Name (A-Z)</div> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="name-desc" role="menuitem">Name (Z-A)</div> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="size-asc" role="menuitem">Size (Small to Large)</div> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="size-desc" role="menuitem">Size (Large to Small)</div> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="date-asc" role="menuitem">Date (Oldest First)</div> | |
<div class="sort-option px-4 py-2 text-sm text-gray-700 cursor-pointer" | |
data-sort="date-desc" role="menuitem">Date (Newest First)</div> | |
</div> | |
</div> | |
</div> | |
<ul id="fileList" class="divide-y divide-gray-200" role="list"> | |
<!-- Files will be listed here --> | |
</ul> | |
</div> | |
<!-- Main image display --> | |
<div class="w-full md:w-3/4 p-4 flex flex-col items-center justify-center"> | |
<div class="relative w-full max-w-3xl"> | |
<!-- Navigation buttons --> | |
<button id="prevBtn" | |
class="nav-btn absolute left-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white text-gray-800 p-3 rounded-full shadow-md ml-4 z-10" | |
aria-label="Previous image"> | |
<i class="fas fa-chevron-left text-xl" aria-hidden="true"></i> | |
</button> | |
<button id="nextBtn" | |
class="nav-btn absolute right-0 top-1/2 -translate-y-1/2 bg-white/80 hover:bg-white text-gray-800 p-3 rounded-full shadow-md mr-4 z-10" | |
aria-label="Next image"> | |
<i class="fas fa-chevron-right text-xl" aria-hidden="true"></i> | |
</button> | |
<!-- Image display area --> | |
<div class="image-container bg-gray-100 rounded-lg overflow-hidden flex items-center justify-center" | |
style="min-height: 400px;"> | |
<div id="imageDisplay" class="p-4 w-full h-full flex items-center justify-center"> | |
<p class="text-gray-500">Select an image to view</p> | |
</div> | |
</div> | |
<!-- Image info --> | |
<div class="mt-4 bg-gray-50 rounded-lg p-3"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<h4 id="fileName" class="font-medium text-gray-800 truncate">No image | |
selected</h4> | |
<p id="fileInfo" class="text-sm text-gray-500">-</p> | |
</div> | |
<div class="flex space-x-2"> | |
<button id="zoomInBtn" | |
class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" | |
aria-label="Zoom in"> | |
<i class="fas fa-search-plus" aria-hidden="true"></i> | |
</button> | |
<button id="zoomOutBtn" | |
class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" | |
aria-label="Zoom out"> | |
<i class="fas fa-search-minus" aria-hidden="true"></i> | |
</button> | |
<button id="resetZoomBtn" | |
class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" | |
aria-label="Reset zoom"> | |
<i class="fas fa-expand" aria-hidden="true"></i> | |
</button> | |
<button id="fullscreenBtn" | |
class="nav-btn bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded" | |
aria-label="Fullscreen"> | |
<i class="fas fa-expand-arrows-alt" aria-hidden="true"></i> | |
</button> | |
</div> | |
</div> | |
<div class="mt-2"> | |
<div class="w-full bg-gray-200 rounded-full h-2"> | |
<div id="progressBar" class="bg-blue-500 h-2 rounded-full" | |
style="width: 0%"></div> | |
</div> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span id="currentIndex">0</span> | |
<span id="totalImages">0</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="text-center text-gray-500 text-sm mt-8"> | |
<p>Use arrow keys to navigate between images</p> | |
<p class="mt-1">Supported formats: WebP, PNG, JPEG, AVIF, HEIC</p> | |
</div> | |
</div> | |
</div> | |
<!-- Fullscreen container --> | |
<div id="fullscreenContainer" role="dialog" aria-modal="true" aria-label="Fullscreen image viewer"> | |
<img id="fullscreenImage" src="" alt="Fullscreen Image"> | |
<div id="fullscreenControls"> | |
<button id="fsPrevBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" | |
aria-label="Previous image"> | |
<i class="fas fa-chevron-left text-xl" aria-hidden="true"></i> | |
</button> | |
<button id="fsCloseBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" | |
aria-label="Close fullscreen"> | |
<i class="fas fa-times text-xl" aria-hidden="true"></i> | |
</button> | |
<button id="fsNextBtn" class="nav-btn bg-white/20 hover:bg-white/40 text-white p-3 rounded-full" | |
aria-label="Next image"> | |
<i class="fas fa-chevron-right text-xl" aria-hidden="true"></i> | |
</button> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function () { | |
// DOM elements | |
const dropzone = document.getElementById('dropzone'); | |
const browseBtn = document.getElementById('browseBtn'); | |
const fileInput = document.getElementById('fileInput'); | |
const viewerArea = document.getElementById('viewerArea'); | |
const fileList = document.getElementById('fileList'); | |
const imageDisplay = document.getElementById('imageDisplay'); | |
const fileName = document.getElementById('fileName'); | |
const fileInfo = document.getElementById('fileInfo'); | |
const fileCount = document.getElementById('fileCount'); | |
const prevBtn = document.getElementById('prevBtn'); | |
const nextBtn = document.getElementById('nextBtn'); | |
const zoomInBtn = document.getElementById('zoomInBtn'); | |
const zoomOutBtn = document.getElementById('zoomOutBtn'); | |
const resetZoomBtn = document.getElementById('resetZoomBtn'); | |
const fullscreenBtn = document.getElementById('fullscreenBtn'); | |
const progressBar = document.getElementById('progressBar'); | |
const currentIndex = document.getElementById('currentIndex'); | |
const totalImages = document.getElementById('totalImages'); | |
const sortBtn = document.getElementById('sortBtn'); | |
const sortDropdown = document.getElementById('sortDropdown'); | |
const fullscreenContainer = document.getElementById('fullscreenContainer'); | |
const fullscreenImage = document.getElementById('fullscreenImage'); | |
const fsPrevBtn = document.getElementById('fsPrevBtn'); | |
const fsNextBtn = document.getElementById('fsNextBtn'); | |
const fsCloseBtn = document.getElementById('fsCloseBtn'); | |
// State variables | |
let files = []; | |
let currentFileIndex = -1; | |
let zoomLevel = 1; | |
const maxZoom = 3; | |
const minZoom = 0.5; | |
const zoomStep = 0.1; | |
let currentSortMethod = 'name-asc'; | |
// Event listeners for dropzone | |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
dropzone.addEventListener(eventName, preventDefaults, false); | |
}); | |
function preventDefaults(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropzone.addEventListener(eventName, highlight, false); | |
}); | |
['dragleave', 'drop'].forEach(eventName => { | |
dropzone.addEventListener(eventName, unhighlight, false); | |
}); | |
function highlight() { | |
dropzone.classList.add('active'); | |
} | |
function unhighlight() { | |
dropzone.classList.remove('active'); | |
} | |
dropzone.addEventListener('drop', handleDrop, false); | |
function handleDrop(e) { | |
const dt = e.dataTransfer; | |
const droppedFiles = dt.files; | |
handleFiles(droppedFiles); | |
} | |
// Browse button click | |
browseBtn.addEventListener('click', () => { | |
fileInput.click(); | |
}); | |
// File input change | |
fileInput.addEventListener('change', () => { | |
if (fileInput.files.length > 0) { | |
handleFiles(fileInput.files); | |
} | |
}); | |
// Navigation buttons | |
prevBtn.addEventListener('click', showPreviousImage); | |
nextBtn.addEventListener('click', showNextImage); | |
// Zoom buttons | |
zoomInBtn.addEventListener('click', zoomIn); | |
zoomOutBtn.addEventListener('click', zoomOut); | |
resetZoomBtn.addEventListener('click', resetZoom); | |
fullscreenBtn.addEventListener('click', openFullscreen); | |
// Sort functionality | |
sortBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
const isExpanded = sortDropdown.classList.toggle('hidden'); | |
sortBtn.setAttribute('aria-expanded', !isExpanded); | |
}); | |
document.querySelectorAll('.sort-option').forEach(option => { | |
option.addEventListener('click', (e) => { | |
currentSortMethod = e.target.dataset.sort; | |
sortFiles(); | |
updateFileList(); | |
showImage(currentFileIndex); | |
sortDropdown.classList.add('hidden'); | |
sortBtn.setAttribute('aria-expanded', 'false'); | |
}); | |
}); | |
// Close dropdown when clicking outside | |
document.addEventListener('click', () => { | |
sortDropdown.classList.add('hidden'); | |
sortBtn.setAttribute('aria-expanded', 'false'); | |
}); | |
// Fullscreen functionality | |
fsPrevBtn.addEventListener('click', () => { | |
showPreviousImage(); | |
updateFullscreenImage(); | |
}); | |
fsNextBtn.addEventListener('click', () => { | |
showNextImage(); | |
updateFullscreenImage(); | |
}); | |
fsCloseBtn.addEventListener('click', closeFullscreen); | |
// Keyboard navigation | |
document.addEventListener('keydown', (e) => { | |
if (files.length === 0) return; | |
switch (e.key) { | |
case 'ArrowLeft': | |
if (fullscreenContainer.style.display === 'flex') { | |
showPreviousImage(); | |
updateFullscreenImage(); | |
} else { | |
showPreviousImage(); | |
} | |
break; | |
case 'ArrowRight': | |
if (fullscreenContainer.style.display === 'flex') { | |
showNextImage(); | |
updateFullscreenImage(); | |
} else { | |
showNextImage(); | |
} | |
break; | |
case '+': | |
case '=': | |
zoomIn(); | |
break; | |
case '-': | |
zoomOut(); | |
break; | |
case '0': | |
resetZoom(); | |
break; | |
case 'Escape': | |
if (fullscreenContainer.style.display === 'flex') { | |
closeFullscreen(); | |
} | |
break; | |
case 'f': | |
case 'F': | |
if (imageDisplay.querySelector('img')) { | |
if (fullscreenContainer.style.display === 'flex') { | |
closeFullscreen(); | |
} else { | |
openFullscreen(); | |
} | |
} | |
break; | |
} | |
}); | |
// Handle dropped or selected files | |
function handleFiles(newFiles) { | |
// Filter for supported image types | |
const supportedTypes = [ | |
'image/webp', | |
'image/png', | |
'image/jpeg', | |
'image/avif', | |
'image/heic', | |
'image/heif' | |
]; | |
const imageFiles = Array.from(newFiles).filter(file => { | |
// Check MIME type first | |
if (supportedTypes.includes(file.type)) return true; | |
// Fallback for file extensions (some browsers might not recognize HEIC/AVIF MIME types) | |
const extension = file.name.split('.').pop().toLowerCase(); | |
return ['webp', 'png', 'jpg', 'jpeg', 'avif', 'heic', 'heif'].includes(extension); | |
}); | |
if (imageFiles.length === 0) { | |
alert('No supported image files found. Please upload WebP, PNG, JPEG, AVIF, or HEIC files.'); | |
return; | |
} | |
files = imageFiles; | |
currentFileIndex = 0; | |
zoomLevel = 1; | |
// Sort files | |
sortFiles(); | |
// Update UI | |
updateFileList(); | |
showImage(currentFileIndex); | |
viewerArea.classList.remove('hidden'); | |
// Scroll to top | |
window.scrollTo(0, 0); | |
} | |
// Sort files based on current sort method | |
function sortFiles() { | |
switch (currentSortMethod) { | |
case 'name-asc': | |
files.sort((a, b) => a.name.localeCompare(b.name)); | |
break; | |
case 'name-desc': | |
files.sort((a, b) => b.name.localeCompare(a.name)); | |
break; | |
case 'size-asc': | |
files.sort((a, b) => a.size - b.size); | |
break; | |
case 'size-desc': | |
files.sort((a, b) => b.size - a.size); | |
break; | |
case 'date-asc': | |
files.sort((a, b) => a.lastModified - b.lastModified); | |
break; | |
case 'date-desc': | |
files.sort((a, b) => b.lastModified - a.lastModified); | |
break; | |
} | |
// Update current file index to maintain selection | |
if (currentFileIndex >= 0 && files.length > 0) { | |
currentFileIndex = 0; | |
} | |
} | |
// Update the file list sidebar | |
function updateFileList() { | |
fileList.innerHTML = ''; | |
fileCount.textContent = files.length; | |
totalImages.textContent = files.length; | |
files.forEach((file, index) => { | |
const listItem = document.createElement('li'); | |
listItem.className = `file-item cursor-pointer ${index === currentFileIndex ? 'active' : ''}`; | |
listItem.setAttribute('role', 'listitem'); | |
listItem.innerHTML = ` | |
<div class="flex items-center p-3"> | |
<div class="flex-shrink-0 h-10 w-10 rounded bg-gray-200 overflow-hidden"> | |
<img src="#" alt="Thumbnail" class="h-full w-full object-cover thumbnail" data-index="${index}"> | |
</div> | |
<div class="ml-3 overflow-hidden"> | |
<p class="text-sm font-medium text-gray-900 truncate">${file.name}</p> | |
<p class="text-sm text-gray-500">${formatFileSize(file.size)}</p> | |
</div> | |
</div> | |
`; | |
listItem.addEventListener('click', () => { | |
showImage(index); | |
}); | |
// Keyboard navigation for file list items | |
listItem.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' || e.key === ' ') { | |
e.preventDefault(); | |
showImage(index); | |
} | |
}); | |
listItem.setAttribute('tabindex', '0'); | |
fileList.appendChild(listItem); | |
// Load thumbnail | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
const thumbnails = document.querySelectorAll(`.thumbnail[data-index="${index}"]`); | |
thumbnails.forEach(thumb => { | |
thumb.src = e.target.result; | |
}); | |
}; | |
reader.readAsDataURL(file); | |
}); | |
} | |
// Show image at specified index | |
function showImage(index) { | |
if (index < 0 || index >= files.length) return; | |
currentFileIndex = index; | |
const file = files[index]; | |
// Update active item in file list | |
document.querySelectorAll('.file-item').forEach((item, i) => { | |
if (i === index) { | |
item.classList.add('active'); | |
item.setAttribute('aria-selected', 'true'); | |
} else { | |
item.classList.remove('active'); | |
item.setAttribute('aria-selected', 'false'); | |
} | |
}); | |
// Update progress | |
currentIndex.textContent = index + 1; | |
progressBar.style.width = `${((index + 1) / files.length) * 100}%`; | |
// Display the image | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
imageDisplay.innerHTML = ''; | |
const img = document.createElement('img'); | |
img.src = e.target.result; | |
img.className = 'max-w-full max-h-[70vh] object-contain fade-in'; | |
img.style.transform = `scale(${zoomLevel})`; | |
img.style.transformOrigin = 'center center'; | |
img.style.transition = 'transform 0.2s ease'; | |
img.setAttribute('alt', `Preview of ${file.name}`); | |
// Add drag to pan functionality | |
let isDragging = false; | |
let startX, startY, translateX = 0, translateY = 0; | |
img.addEventListener('mousedown', (e) => { | |
if (zoomLevel <= 1) return; | |
isDragging = true; | |
startX = e.clientX - translateX; | |
startY = e.clientY - translateY; | |
img.style.cursor = 'grabbing'; | |
}); | |
document.addEventListener('mousemove', (e) => { | |
if (!isDragging) return; | |
translateX = e.clientX - startX; | |
translateY = e.clientY - startY; | |
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`; | |
}); | |
document.addEventListener('mouseup', () => { | |
isDragging = false; | |
img.style.cursor = 'grab'; | |
}); | |
img.addEventListener('mouseenter', () => { | |
if (zoomLevel > 1) { | |
img.style.cursor = 'grab'; | |
} | |
}); | |
img.addEventListener('mouseleave', () => { | |
img.style.cursor = 'default'; | |
}); | |
imageDisplay.appendChild(img); | |
// Update file info | |
fileName.textContent = file.name; | |
fileInfo.textContent = `${getFileType(file)} • ${formatFileSize(file.size)}`; | |
// Load image dimensions after the image is loaded | |
img.onload = () => { | |
fileInfo.textContent = `${img.naturalWidth}×${img.naturalHeight} • ${getFileType(file)} • ${formatFileSize(file.size)}`; | |
}; | |
}; | |
reader.readAsDataURL(file); | |
// Enable/disable navigation buttons | |
prevBtn.disabled = index === 0; | |
nextBtn.disabled = index === files.length - 1; | |
} | |
// Helper to get file type | |
function getFileType(file) { | |
if (file.type) { | |
const type = file.type.split('/')[1]; | |
if (type) return type.toUpperCase(); | |
} | |
// Fallback for file extension | |
const extension = file.name.split('.').pop().toLowerCase(); | |
switch (extension) { | |
case 'jpg': | |
case 'jpeg': return 'JPEG'; | |
case 'png': return 'PNG'; | |
case 'webp': return 'WEBP'; | |
case 'avif': return 'AVIF'; | |
case 'heic': | |
case 'heif': return 'HEIC'; | |
default: return extension.toUpperCase(); | |
} | |
} | |
// Navigation functions | |
function showPreviousImage() { | |
if (currentFileIndex > 0) { | |
showImage(currentFileIndex - 1); | |
} | |
} | |
function showNextImage() { | |
if (currentFileIndex < files.length - 1) { | |
showImage(currentFileIndex + 1); | |
} | |
} | |
// Zoom functions | |
function zoomIn() { | |
if (zoomLevel < maxZoom) { | |
zoomLevel += zoomStep; | |
applyZoom(); | |
} | |
} | |
function zoomOut() { | |
if (zoomLevel > minZoom) { | |
zoomLevel -= zoomStep; | |
applyZoom(); | |
} | |
} | |
function resetZoom() { | |
zoomLevel = 1; | |
applyZoom(); | |
} | |
function applyZoom() { | |
const img = imageDisplay.querySelector('img'); | |
if (img) { | |
img.style.transform = `scale(${zoomLevel})`; | |
// Reset pan position when zooming | |
if (zoomLevel <= 1) { | |
img.style.transform = `scale(${zoomLevel}) translate(0, 0)`; | |
} | |
} | |
} | |
// Fullscreen functions | |
function openFullscreen() { | |
const img = imageDisplay.querySelector('img'); | |
if (!img) return; | |
fullscreenImage.src = img.src; | |
fullscreenContainer.style.display = 'flex'; | |
document.body.style.overflow = 'hidden'; | |
fullscreenContainer.setAttribute('aria-hidden', 'false'); | |
} | |
function closeFullscreen() { | |
fullscreenContainer.style.display = 'none'; | |
document.body.style.overflow = ''; | |
fullscreenContainer.setAttribute('aria-hidden', 'true'); | |
} | |
function updateFullscreenImage() { | |
const img = imageDisplay.querySelector('img'); | |
if (img) { | |
fullscreenImage.src = img.src; | |
} | |
} | |
// Helper function to format file size | |
function formatFileSize(bytes) { | |
if (bytes === 0) return '0 Bytes'; | |
const k = 1024; | |
const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
} | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=jsfs11/local-image-viewer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |