2024-10-16 19:37:45 -04:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2024-11-05 19:03:04 -05:00
|
|
|
<title>{{ site_title }}</title>
|
2024-10-16 19:37:45 -04:00
|
|
|
<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">
|
2024-11-05 19:25:52 -05:00
|
|
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
2024-10-16 19:37:45 -04:00
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
2024-10-20 20:22:31 -04:00
|
|
|
min-height: 100vh;
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
2024-10-20 11:19:44 -04:00
|
|
|
|
2024-10-16 19:37:45 -04:00
|
|
|
.main-content {
|
|
|
|
flex-grow: 1;
|
|
|
|
padding: 20px;
|
2024-10-20 20:22:31 -04:00
|
|
|
overflow-y: auto;
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
|
|
|
.grid-container {
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(10px, 1fr));
|
|
|
|
grid-auto-rows: 10px;
|
|
|
|
grid-auto-flow: dense;
|
|
|
|
gap: 15px;
|
2024-10-20 14:34:53 -04:00
|
|
|
max-width: 100%;
|
|
|
|
box-sizing: border-box;
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
|
|
|
.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;
|
2024-10-20 14:34:53 -04:00
|
|
|
max-width: 100%;
|
|
|
|
box-sizing: border-box;
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
.polaroid img {
|
|
|
|
max-width: 100%;
|
|
|
|
height: auto;
|
|
|
|
display: block;
|
2024-10-20 17:02:15 -04:00
|
|
|
flex-grow: 1;
|
|
|
|
object-fit: contain;
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
.date-overlay {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 4rem;
|
|
|
|
font-size: 1rem;
|
2024-10-20 15:53:35 -04:00
|
|
|
right: 1rem;
|
|
|
|
text-align: right;
|
|
|
|
color: #ec5a11;
|
2024-10-16 19:37:45 -04:00
|
|
|
font-family: "Noto Sans Mono", monospace;
|
|
|
|
font-size: 0.7rem;
|
|
|
|
opacity: 0;
|
2024-10-20 15:53:35 -04:00
|
|
|
text-shadow: 0 0 2px #ec5a11;
|
2024-10-16 19:37:45 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-10-20 11:19:44 -04:00
|
|
|
.sidebar {
|
2024-10-20 20:22:31 -04:00
|
|
|
width: 20rem;
|
2024-10-20 11:19:44 -04:00
|
|
|
background-color: #f0f0f0;
|
|
|
|
padding: 20px;
|
|
|
|
box-sizing: border-box;
|
|
|
|
position: sticky;
|
|
|
|
top: 0;
|
|
|
|
height: 100vh;
|
2024-10-20 20:22:31 -04:00
|
|
|
overflow-y: auto;
|
2024-10-20 11:19:44 -04:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: space-between;
|
|
|
|
}
|
|
|
|
.sidebar-title {
|
2024-10-20 14:34:53 -04:00
|
|
|
font-size: 1rem;
|
2024-10-20 11:19:44 -04:00
|
|
|
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;
|
2024-10-20 15:53:35 -04:00
|
|
|
color: {{ accent_color }};
|
2024-10-20 11:19:44 -04:00
|
|
|
}
|
2024-10-20 17:03:51 -04:00
|
|
|
.nav-toggle {
|
|
|
|
display: none;
|
|
|
|
cursor: pointer;
|
|
|
|
font-size: 1.5rem;
|
|
|
|
text-align: right;
|
|
|
|
}
|
2024-10-20 14:34:53 -04:00
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
2024-10-20 16:22:45 -04:00
|
|
|
|
|
|
|
|
2024-10-20 14:34:53 -04:00
|
|
|
.main-content {
|
|
|
|
padding: 10px;
|
|
|
|
}
|
|
|
|
.grid-container {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 20px;
|
|
|
|
}
|
|
|
|
.polaroid {
|
|
|
|
width: 100%;
|
|
|
|
max-width: 100vw;
|
|
|
|
height: auto;
|
|
|
|
}
|
|
|
|
}
|
2024-10-20 16:22:45 -04:00
|
|
|
|
|
|
|
@media (max-width: 1024px) {
|
|
|
|
body {
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
.sidebar {
|
|
|
|
width: 100%;
|
|
|
|
height: auto;
|
|
|
|
position: static;
|
|
|
|
padding: 10px;
|
2024-10-20 20:22:31 -04:00
|
|
|
overflow-y: visible;
|
2024-10-20 17:03:51 -04:00
|
|
|
}
|
|
|
|
.sidebar-nav ul {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
.nav-toggle {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
.sidebar-nav.active ul {
|
|
|
|
display: block;
|
|
|
|
}
|
2024-10-20 16:22:45 -04:00
|
|
|
}
|
2024-11-05 19:03:04 -05:00
|
|
|
|
|
|
|
.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;
|
|
|
|
}
|
2024-10-16 19:37:45 -04:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
2024-11-05 19:03:04 -05:00
|
|
|
<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>
|
2024-10-20 11:19:44 -04:00
|
|
|
</div>
|
2024-11-05 19:03:04 -05:00
|
|
|
</header>
|
2024-10-16 19:37:45 -04:00
|
|
|
|
|
|
|
<div class="main-content">
|
|
|
|
<div class="grid-container" id="polaroid-grid"></div>
|
|
|
|
<div id="loading">Loading more images...</div>
|
|
|
|
</div>
|
|
|
|
|
2024-11-05 19:03:04 -05:00
|
|
|
<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 }}">
|
2024-10-16 19:37:45 -04:00
|
|
|
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;
|
2024-10-20 14:34:53 -04:00
|
|
|
img.setAttribute('data-original-width', polaroid.width);
|
|
|
|
img.setAttribute('data-original-height', polaroid.height);
|
2024-10-20 17:02:15 -04:00
|
|
|
img.setAttribute('data-base-src', polaroid.imgSrc);
|
|
|
|
img.src = polaroid.imgSrc;
|
|
|
|
|
2024-10-20 17:38:45 -04:00
|
|
|
//img.onload = () => loadOptimalThumbnail(img); // Load optimal thumbnail after initial load
|
2024-10-16 19:37:45 -04:00
|
|
|
|
|
|
|
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}`;
|
2024-10-20 14:34:53 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-10-20 17:02:15 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-10-20 14:34:53 -04:00
|
|
|
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 });
|
2024-10-20 17:38:45 -04:00
|
|
|
//loadOptimalThumbnail(img);
|
2024-10-20 14:34:53 -04:00
|
|
|
});
|
2024-10-16 19:37:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-10-20 17:02:15 -04:00
|
|
|
// loadOptimalThumbnail is now called in the img.onload event
|
2024-10-16 19:37:45 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
2024-10-20 17:02:15 -04:00
|
|
|
window.addEventListener('resize', handleResize);
|
2024-10-20 14:34:53 -04:00
|
|
|
|
2024-10-16 19:37:45 -04:00
|
|
|
loadImages(); // Initial load
|
2024-10-20 17:03:51 -04:00
|
|
|
|
|
|
|
function setupNavToggle() {
|
|
|
|
const navToggle = document.querySelector('.nav-toggle');
|
|
|
|
const sidebarNav = document.querySelector('.sidebar-nav');
|
|
|
|
|
|
|
|
navToggle.addEventListener('click', () => {
|
|
|
|
sidebarNav.classList.toggle('active');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setupNavToggle();
|
2024-11-05 19:03:04 -05:00
|
|
|
|
|
|
|
// 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';
|
|
|
|
}
|
|
|
|
}
|
2024-10-16 19:37:45 -04:00
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|