spectra/templates/index.html

466 lines
14 KiB
HTML
Raw Permalink Normal View History

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">
<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">
<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
}
.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>
<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>
</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>
<div id="aboutModal" class="modal">
<div class="modal-content">
<span class="close">&times;</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();
// 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>