<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ site_title }}</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet"> <link rel="icon" href="data:;base64,iVBORw0KGgo="> <style> body { margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f0f0f0; display: flex; flex-direction: row; min-height: 100vh; } .main-content { flex-grow: 1; padding: 20px; overflow-y: auto; } .grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(10px, 1fr)); grid-auto-rows: 10px; grid-auto-flow: dense; gap: 15px; max-width: 100%; box-sizing: border-box; } .polaroid { position: relative; background-color: #e8e8ea; box-shadow: 0 0 2px 0 #f4f4f6 inset, 1px 5px 5px 0px #99999955; border-radius: 2px; padding: 10px; display: flex; flex-direction: column; align-items: center; transition: background-color 0.3s ease; max-width: 100%; box-sizing: border-box; } .polaroid img { max-width: 100%; height: auto; display: block; flex-grow: 1; object-fit: contain; } .date-overlay { position: absolute; bottom: 4rem; font-size: 1rem; right: 1rem; text-align: right; color: #ec5a11; font-family: "Noto Sans Mono", monospace; font-size: 0.7rem; opacity: 0; text-shadow: 0 0 2px #ec5a11; transition: opacity 0.3s ease; } .polaroid:hover .date-overlay { opacity: 0.8; } .polaroid .caption { margin-top: 10px; display: block; flex-grow: 0; flex-shrink: 1; flex-basis: auto; align-self: flex-end; order: 0; text-align: right; line-height: 70%; font-size: 0.75rem; } .noto-sans-mono-font { font-family: "Noto Sans Mono", monospace; font-optical-sizing: auto; font-weight: 400; font-style: normal; font-variation-settings: "wdth" 100; } .sidebar { width: 20rem; background-color: #f0f0f0; padding: 20px; box-sizing: border-box; position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; justify-content: space-between; } .sidebar-title { font-size: 1rem; text-align: right; } .sidebar-nav { font-size: 1rem; flex-grow: 1; justify-content: center; text-align: right; } .sidebar-nav ul { list-style-type: none; padding: 0; } .sidebar-nav a { text-decoration: none; color: {{ accent_color }}; } .nav-toggle { display: none; cursor: pointer; font-size: 1.5rem; text-align: right; } @media (max-width: 768px) { .main-content { padding: 10px; } .grid-container { display: flex; flex-direction: column; gap: 20px; } .polaroid { width: 100%; max-width: 100vw; height: auto; } } @media (max-width: 1024px) { body { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; padding: 10px; overflow-y: visible; } .sidebar-nav ul { display: none; } .nav-toggle { display: block; } .sidebar-nav.active ul { display: block; } } .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); backdrop-filter: blur(5px); } .modal-content { background-color: #f0f0f0; margin: 10% auto; padding: 20px; width: 80%; max-width: 600px; border-radius: 8px; position: relative; display: flex; flex-direction: column; gap: 1rem; } .modal-header { display: flex; align-items: center; gap: 1rem; } .profile-image { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; } .profile-info { flex-grow: 1; } .profile-name { font-size: 1.5rem; margin: 0; } .profile-location { color: #666; margin: 0; } .modal-bio { line-height: 1.6; } .close { position: absolute; right: 20px; top: 20px; font-size: 1.5rem; cursor: pointer; color: #666; } .close:hover { color: #000; } </style> </head> <body> <header> <div class="sidebar"> <div class="sidebar-nav noto-sans-mono-font"> <div class="nav-toggle">☰</div> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="#" id="aboutLink">About</a></li> <li><hr></li> <li>Powered by <a href="https://dws.rip">DWS</a></li> </ul> </nav> </div> <div class="sidebar-title noto-sans-mono-font"> <h1>{{ site_title }}</h1> </div> </div> </header> <div class="main-content"> <div class="grid-container" id="polaroid-grid"></div> <div id="loading">Loading more images...</div> </div> <div id="aboutModal" class="modal"> <div class="modal-content"> <span class="close">×</span> <div class="modal-header"> <img src="{{ about.profile_image }}" alt="{{ about.name }}" class="profile-image"> <div class="profile-info"> <h2 class="profile-name">{{ about.name }}</h2> <p class="profile-location">{{ about.location }}</p> </div> </div> <div class="modal-bio" id="bio-content"></div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" nonce="{{ nonce }}"></script> <script nonce="{{ nonce }}"> const gridContainer = document.getElementById('polaroid-grid'); const loadingIndicator = document.getElementById('loading'); const baseSize = 110; // Size of one grid cell in pixels let page = 1; let isLoading = false; let hasMore = true; function createPolaroid(polaroid) { const polaroidElement = document.createElement('div'); polaroidElement.className = 'polaroid'; if (polaroid.height > polaroid.width) { polaroidElement.classList.add('vertical'); } polaroidElement.style.backgroundColor = `${polaroid.highlightColor}33`; const img = document.createElement('img'); img.alt = polaroid.caption; img.setAttribute('data-original-width', polaroid.width); img.setAttribute('data-original-height', polaroid.height); img.setAttribute('data-base-src', polaroid.imgSrc); img.src = polaroid.imgSrc; //img.onload = () => loadOptimalThumbnail(img); // Load optimal thumbnail after initial load const dateOverlay = document.createElement('div'); dateOverlay.className = 'date-overlay'; dateOverlay.textContent = polaroid.date; const caption = document.createElement('div'); caption.className = 'caption noto-sans-mono-font'; caption.textContent = polaroid.technicalInfo; polaroidElement.appendChild(img); polaroidElement.appendChild(dateOverlay); polaroidElement.appendChild(caption); return polaroidElement; } function calculateGridSpan(dimension) { return Math.ceil(dimension / baseSize); } function positionPolaroid(polaroidElement, polaroid) { const width = calculateGridSpan(polaroid.width + 20); // Add 20px for padding const height = calculateGridSpan(polaroid.height + 40) + 1; // Add 40px for padding and caption polaroidElement.style.gridColumnEnd = `span ${width}`; polaroidElement.style.gridRowEnd = `span ${height}`; } function getOptimalThumbnailSize(width, height) { const sizes = [256, 512, 768, 1024, 1536, 2048]; const maxDimension = Math.max(width, height); return sizes.find(size => size >= maxDimension) || sizes[sizes.length - 1]; } function loadOptimalThumbnail(img) { // Use a small delay to ensure the image has rendered setTimeout(() => { const containerWidth = img.offsetWidth; const containerHeight = img.offsetHeight; const devicePixelRatio = window.devicePixelRatio || 1; // If dimensions are still 0, use the original image dimensions const width = containerWidth || parseInt(img.getAttribute('data-original-width')); const height = containerHeight || parseInt(img.getAttribute('data-original-height')); const optimalSize = getOptimalThumbnailSize( width * devicePixelRatio, height * devicePixelRatio ); const newSrc = img.getAttribute('data-base-src').replace('1536_', `${optimalSize}_`); if (newSrc !== img.src) { const tempImg = new Image(); tempImg.onload = function() { img.src = newSrc; img.style.opacity = 1; }; tempImg.src = newSrc; img.style.opacity = 0.5; } }, 100); // 100ms delay } function handleResize() { const polaroids = document.querySelectorAll('.polaroid'); polaroids.forEach(polaroid => { const img = polaroid.querySelector('img'); const width = parseInt(img.getAttribute('data-original-width')); const height = parseInt(img.getAttribute('data-original-height')); positionPolaroid(polaroid, { width, height }); //loadOptimalThumbnail(img); }); } async function loadImages() { if (isLoading || !hasMore) return; isLoading = true; loadingIndicator.style.display = 'block'; try { const response = await fetch(`/api/images?page=${page}`); const data = await response.json(); data.images.forEach(polaroid => { const polaroidElement = createPolaroid(polaroid); positionPolaroid(polaroidElement, polaroid); gridContainer.appendChild(polaroidElement); // loadOptimalThumbnail is now called in the img.onload event }); hasMore = data.hasMore; page++; } catch (error) { console.error('Error loading images:', error); } finally { isLoading = false; loadingIndicator.style.display = 'none'; } } function handleScroll() { if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) { loadImages(); } } window.addEventListener('scroll', handleScroll); window.addEventListener('resize', handleResize); loadImages(); // Initial load function setupNavToggle() { const navToggle = document.querySelector('.nav-toggle'); const sidebarNav = document.querySelector('.sidebar-nav'); navToggle.addEventListener('click', () => { sidebarNav.classList.toggle('active'); }); } setupNavToggle(); // Modal functionality const modal = document.getElementById('aboutModal'); const aboutLink = document.getElementById('aboutLink'); const closeBtn = document.querySelector('.close'); const bioContent = document.getElementById('bio-content'); // Render markdown content bioContent.innerHTML = marked.parse(`{{ about.bio | safe }}`); aboutLink.onclick = function() { modal.style.display = 'block'; document.body.style.overflow = 'hidden'; } closeBtn.onclick = function() { modal.style.display = 'none'; document.body.style.overflow = 'auto'; } window.onclick = function(event) { if (event.target == modal) { modal.style.display = 'none'; document.body.style.overflow = 'auto'; } } </script> </body> </html>