Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes"> | |
| <title>{{ image_title }}</title> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> | |
| <style> | |
| :root { | |
| --bg-dark: #0d0d0d; | |
| --toolbar-bg: #1a1a1a; | |
| --accent: #3b82f6; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| background: var(--bg-dark); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| color: #fff; | |
| } | |
| .toolbar { | |
| height: 48px; | |
| background: var(--toolbar-bg); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 16px; | |
| border-bottom: 1px solid rgba(255,255,255,0.1); | |
| flex-shrink: 0; | |
| } | |
| .toolbar-title { | |
| font-size: 14px; | |
| font-weight: 500; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| max-width: 50%; | |
| } | |
| .toolbar-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .toolbar-btn { | |
| background: transparent; | |
| border: none; | |
| color: #999; | |
| padding: 8px 12px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| transition: all 0.15s ease; | |
| text-decoration: none; | |
| } | |
| .toolbar-btn:hover { | |
| background: rgba(255,255,255,0.08); | |
| color: #fff; | |
| } | |
| .image-container { | |
| flex: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| overflow: auto; | |
| } | |
| .image-container img { | |
| max-width: 100%; | |
| max-height: 100%; | |
| object-fit: contain; | |
| border-radius: 4px; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.5); | |
| transition: transform 0.3s ease; | |
| } | |
| .image-container img.zoomed { | |
| max-width: none; | |
| max-height: none; | |
| cursor: zoom-out; | |
| } | |
| .zoom-controls { | |
| position: fixed; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: var(--toolbar-bg); | |
| border-radius: 12px; | |
| padding: 8px; | |
| display: flex; | |
| gap: 4px; | |
| border: 1px solid rgba(255,255,255,0.1); | |
| } | |
| .zoom-btn { | |
| width: 40px; | |
| height: 40px; | |
| border: none; | |
| background: transparent; | |
| color: #999; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 18px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.15s ease; | |
| } | |
| .zoom-btn:hover { | |
| background: rgba(255,255,255,0.1); | |
| color: #fff; | |
| } | |
| .zoom-level { | |
| min-width: 60px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 13px; | |
| color: #999; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="toolbar"> | |
| <a href="javascript:history.back()" class="toolbar-btn"> | |
| <i class="bi bi-arrow-left"></i> | |
| Back | |
| </a> | |
| <div class="toolbar-title">{{ image_title }}</div> | |
| <div class="toolbar-actions"> | |
| <a href="{{ image_url }}" download class="toolbar-btn"> | |
| <i class="bi bi-download"></i> | |
| </a> | |
| <button class="toolbar-btn" onclick="toggleFullscreen()"> | |
| <i class="bi bi-arrows-fullscreen"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="image-container" id="imageContainer"> | |
| <img src="{{ image_url }}" alt="{{ image_title }}" id="mainImage" onclick="toggleZoom()"> | |
| </div> | |
| <div class="zoom-controls"> | |
| <button class="zoom-btn" onclick="zoomOut()"><i class="bi bi-dash"></i></button> | |
| <span class="zoom-level" id="zoomLevel">100%</span> | |
| <button class="zoom-btn" onclick="zoomIn()"><i class="bi bi-plus"></i></button> | |
| <button class="zoom-btn" onclick="resetZoom()"><i class="bi bi-arrows-angle-contract"></i></button> | |
| </div> | |
| <script> | |
| const img = document.getElementById('mainImage'); | |
| const zoomLevelEl = document.getElementById('zoomLevel'); | |
| let currentZoom = 1; | |
| function updateZoom() { | |
| img.style.transform = `scale(${currentZoom})`; | |
| zoomLevelEl.textContent = Math.round(currentZoom * 100) + '%'; | |
| if (currentZoom > 1) { | |
| img.classList.add('zoomed'); | |
| } else { | |
| img.classList.remove('zoomed'); | |
| } | |
| } | |
| function zoomIn() { | |
| currentZoom = Math.min(currentZoom * 1.25, 5); | |
| updateZoom(); | |
| } | |
| function zoomOut() { | |
| currentZoom = Math.max(currentZoom / 1.25, 0.25); | |
| updateZoom(); | |
| } | |
| function resetZoom() { | |
| currentZoom = 1; | |
| updateZoom(); | |
| } | |
| function toggleZoom() { | |
| if (currentZoom > 1) { | |
| resetZoom(); | |
| } else { | |
| currentZoom = 2; | |
| updateZoom(); | |
| } | |
| } | |
| function toggleFullscreen() { | |
| if (!document.fullscreenElement) { | |
| document.documentElement.requestFullscreen(); | |
| } else { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === '+' || e.key === '=') zoomIn(); | |
| else if (e.key === '-') zoomOut(); | |
| else if (e.key === '0') resetZoom(); | |
| else if (e.key === 'Escape') history.back(); | |
| }); | |
| // Pinch to zoom | |
| let initialDistance = 0; | |
| let initialZoom = 1; | |
| document.addEventListener('touchstart', (e) => { | |
| if (e.touches.length === 2) { | |
| initialDistance = Math.hypot( | |
| e.touches[0].clientX - e.touches[1].clientX, | |
| e.touches[0].clientY - e.touches[1].clientY | |
| ); | |
| initialZoom = currentZoom; | |
| } | |
| }, { passive: true }); | |
| document.addEventListener('touchmove', (e) => { | |
| if (e.touches.length === 2) { | |
| const distance = Math.hypot( | |
| e.touches[0].clientX - e.touches[1].clientX, | |
| e.touches[0].clientY - e.touches[1].clientY | |
| ); | |
| const ratio = distance / initialDistance; | |
| currentZoom = Math.min(Math.max(initialZoom * ratio, 0.25), 5); | |
| updateZoom(); | |
| } | |
| }, { passive: true }); | |
| </script> | |
| </body> | |
| </html> | |