Files
kennethreitz fd970be0ba Upgrade to Responder 3.12; thumbnails, smart 404s, and a faster site
- Responder 3.10-3.12: auto ETags, request size limits, timeouts,
  Prometheus metrics at /metrics; essay release count is now seven
- Gallery thumbnails via /thumb route (24MB pages drop to ~400KB)
- Dark-mode flash eliminated; theme applies before first paint
- 404 pages suggest the closest-matching content
- Long-lived caching for static assets; lazy-loaded content images
- Skip link and keyboard-accessible nav dropdowns
- Per-essay OG image in Article structured data
- Homepage: trim the quest log clause

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:40:27 -04:00

677 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block extra_head %}
<style>
/* Directory listing styles */
.directory-list {
list-style: none;
padding: 0;
margin: 0;
}
.directory-item {
position: relative;
padding: 0.25rem 0;
border-bottom: 1px solid #f9f9f9;
line-height: 1.3;
}
.directory-item:last-child {
border-bottom: none;
}
.directory-icon {
width: 18px;
height: 18px;
position: absolute;
left: -1.5rem;
top: 0.3rem;
}
.directory-content {
display: flex;
align-items: center;
gap: 0.75rem;
justify-content: flex-start;
}
.directory-link {
color: #333;
text-decoration: none;
font-size: 1rem;
text-align: left;
flex: 1;
}
.directory-link:hover {
color: #666;
text-decoration: underline;
}
.directory-date {
color: #999;
font-size: 0.75rem;
margin-left: auto;
white-space: nowrap;
}
@media (max-width: 760px) {
.directory-item {
padding: 0.2rem 0;
}
.directory-icon {
width: 16px;
height: 16px;
left: -1.25rem;
top: 0.25rem;
}
.directory-link {
font-size: 0.9rem;
}
.directory-date {
font-size: 0.7rem;
}
}
/* Directory navigation styles */
.directory-navigation {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #f0f0f0;
}
.parent-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: #666;
text-decoration: none;
font-size: 0.9rem;
transition: color 0.2s ease;
}
.parent-link:hover {
color: #333;
text-decoration: underline;
}
.parent-icon {
width: 16px;
height: 16px;
}
/* Image Gallery Styles */
.image-gallery {
margin: 2rem 0;
padding: 0 2rem;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-top: 1.5rem;
}
.gallery-item {
position: relative;
aspect-ratio: 1;
overflow: hidden;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.gallery-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.gallery-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.2s ease;
}
.gallery-item:hover .gallery-thumbnail {
transform: scale(1.05);
}
/* Lightbox Styles */
.lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.lightbox.active {
opacity: 1;
visibility: visible;
}
.lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
display: flex;
flex-direction: column;
align-items: center;
}
.lightbox-image {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
border-radius: 4px;
}
.lightbox-title {
color: white;
margin-top: 1rem;
text-align: center;
font-size: 1.1rem;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
}
.lightbox-exif {
color: #aaa;
margin-top: 0.5rem;
text-align: center;
font-size: 0.9rem;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
line-height: 1.6;
}
.lightbox-exif .exif-row {
margin: 0.25rem 0;
}
.lightbox-exif .exif-label {
font-variant: small-caps;
color: #888;
margin-right: 0.5rem;
}
.lightbox-close {
position: absolute;
top: 20px;
right: 20px;
color: white;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
z-index: 1001;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0.5);
transition: background 0.2s ease;
}
.lightbox-close:hover {
background: rgba(0, 0, 0, 0.8);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
color: white;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
z-index: 1001;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0.5);
transition: background 0.2s ease;
}
.lightbox-nav:hover {
background: rgba(0, 0, 0, 0.8);
}
.lightbox-prev {
left: 20px;
}
.lightbox-next {
right: 20px;
}
@media (max-width: 1024px) {
.gallery-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 760px) {
.image-gallery {
padding: 0 1rem;
}
.gallery-grid {
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
}
.lightbox-close {
top: 10px;
right: 10px;
width: 35px;
height: 35px;
font-size: 1.5rem;
}
.lightbox-nav {
width: 45px;
height: 45px;
font-size: 1.5rem;
}
.lightbox-prev {
left: 10px;
}
.lightbox-next {
right: 10px;
}
}
/* Light mode overrides */
body.light-mode .directory-link {
color: #333 !important;
}
body.light-mode .directory-link:hover {
color: #666 !important;
}
body.light-mode .directory-date {
color: #999 !important;
}
body.light-mode .directory-item {
border-bottom-color: #f9f9f9 !important;
}
body.light-mode .directory-navigation {
border-top-color: #f0f0f0 !important;
}
body.light-mode .parent-link {
color: #666 !important;
}
body.light-mode .parent-link:hover {
color: #333 !important;
}
/* Dark mode styles */
body.dark-mode .directory-link {
color: #ccc;
}
body.dark-mode .directory-link:hover {
color: #fff;
}
body.dark-mode .directory-date {
color: #666;
}
body.dark-mode .directory-item {
border-bottom-color: #2a2a2a;
}
body.dark-mode .directory-navigation {
border-top-color: #2a2a2a;
}
body.dark-mode .parent-link {
color: #999;
}
body.dark-mode .parent-link:hover {
color: #ccc;
}
@media (prefers-color-scheme: dark) {
.directory-link {
color: #ccc;
}
.directory-link:hover {
color: #fff;
}
.directory-date {
color: #666;
}
.directory-item {
border-bottom-color: #2a2a2a;
}
.directory-navigation {
border-top-color: #2a2a2a;
}
.parent-link {
color: #999;
}
.parent-link:hover {
color: #ccc;
}
}
/* Directory title icon */
.directory-title-icon {
width: 32px;
height: 32px;
margin-right: 0.5rem;
vertical-align: middle;
display: inline-block;
}
@media (max-width: 760px) {
.directory-title-icon {
width: 24px;
height: 24px;
margin-right: 0.4rem;
}
}
</style>
{% endblock %}
{% block content %}
{% if current_path and current_path != '' %}
<h1>
{% if folder_icon %}
<img src="{{ folder_icon }}" alt="Folder icon" class="directory-title-icon">
{% endif %}
{{ title }}
</h1>
{% endif %}
{% if index_content and content_position == 'top' %}
<section class="directory-intro">
{{ index_content.content | safe }}
</section>
{% endif %}
{% if is_image_gallery and image_items %}
<section class="image-gallery">
<h3>Gallery</h3>
<div class="gallery-grid">
{% for item in image_items %}
<div class="gallery-item">
{% if item.static_path.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')) %}
<img src="/thumb{{ item.static_path }}?w=640"
srcset="/thumb{{ item.static_path }}?w=320 320w, /thumb{{ item.static_path }}?w=640 640w, /thumb{{ item.static_path }}?w=1280 1280w"
sizes="(max-width: 760px) 50vw, 320px"
loading="lazy" decoding="async"
alt="{{ item.display_name }}" class="gallery-thumbnail"
data-full="{{ item.static_path }}"
data-title="{{ item.display_name }}"
data-exif="{{ item.exif | tojson | forceescape }}">
{% else %}
<img src="{{ item.static_path }}" loading="lazy" decoding="async"
alt="{{ item.display_name }}" class="gallery-thumbnail"
data-full="{{ item.static_path }}"
data-title="{{ item.display_name }}"
data-exif="{{ item.exif | tojson | forceescape }}">
{% endif %}
</div>
{% endfor %}
</div>
</section>
{% if items|length > image_items|length %}
<section class="directory-listing">
<h3>Other Files</h3>
<ul class="directory-list">
{% for item in items %}
{% if not item.is_image %}
<li class="directory-item">
{% if item.unique_icon %}
<img src="{{ item.unique_icon }}" alt="Icon for {{ item.display_name }}" class="directory-icon">
{% endif %}
<div class="directory-content">
<a href="{{ item.url_path }}" class="directory-link">
{{ item.display_name }}{% if item.is_dir %}/{% endif %}
</a>
{% if not item.is_dir and item.file_date %}
<span class="directory-date">{{ item.file_date }}</span>
{% endif %}
</div>
</li>
{% endif %}
{% endfor %}
</ul>
</section>
{% endif %}
{% elif items %}
<section class="directory-listing">
<h3>Contents</h3>
<ul class="directory-list">
{% for item in items %}
<li class="directory-item">
{% if item.unique_icon %}
<img src="{{ item.unique_icon }}" alt="Icon for {{ item.display_name }}" class="directory-icon">
{% endif %}
<div class="directory-content">
<a href="{{ item.url_path }}" class="directory-link">
{{ item.display_name }}{% if item.is_dir %}/{% endif %}
</a>
{% if not item.is_dir and item.file_date %}
<span class="directory-date">{{ item.file_date }}</span>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
</section>
{% else %}
<section class="empty-directory">
<p><em>This directory is empty.</em></p>
</section>
{% endif %}
{% if index_content and content_position == 'bottom' %}
<section class="directory-description">
<h3>About This Directory</h3>
{{ index_content.content | safe }}
</section>
{% endif %}
{% if parent_directory %}
<section class="directory-navigation">
<a href="{{ parent_directory.url }}" class="parent-link">
{% if parent_directory.icon %}
<img src="{{ parent_directory.icon }}" alt="Parent directory icon" class="parent-icon">
{% endif %}
← Back to {{ parent_directory.display_name }}
</a>
</section>
{% endif %}
<!-- Lightbox HTML -->
<div id="lightbox" class="lightbox">
<div class="lightbox-close" onclick="closeLightbox()">&times;</div>
<div class="lightbox-nav lightbox-prev" onclick="changeImage(-1)">&lt;</div>
<div class="lightbox-nav lightbox-next" onclick="changeImage(1)">&gt;</div>
<div class="lightbox-content">
<img id="lightbox-image" class="lightbox-image" src="" alt="">
<div id="lightbox-title" class="lightbox-title"></div>
<div id="lightbox-exif" class="lightbox-exif"></div>
</div>
</div>
<script>
let currentImageIndex = 0;
let images = [];
document.addEventListener('DOMContentLoaded', function() {
// Initialize gallery if images exist
const galleryItems = document.querySelectorAll('.gallery-thumbnail');
if (galleryItems.length > 0) {
// Build images array
images = Array.from(galleryItems).map(img => {
let exif = {};
try {
exif = JSON.parse(img.dataset.exif || '{}');
} catch (e) {
console.error('Error parsing EXIF data:', e);
}
return {
src: img.dataset.full,
title: img.dataset.title,
alt: img.alt,
exif: exif
};
});
// Add click handlers
galleryItems.forEach((img, index) => {
img.addEventListener('click', () => openLightbox(index));
});
}
// Close lightbox on background click
document.getElementById('lightbox').addEventListener('click', function(e) {
if (e.target === this) {
closeLightbox();
}
});
// Keyboard navigation
document.addEventListener('keydown', function(e) {
const lightbox = document.getElementById('lightbox');
if (lightbox.classList.contains('active')) {
switch(e.key) {
case 'Escape':
closeLightbox();
break;
case 'ArrowLeft':
changeImage(-1);
break;
case 'ArrowRight':
changeImage(1);
break;
}
}
});
});
function openLightbox(index) {
currentImageIndex = index;
updateLightboxImage();
document.getElementById('lightbox').classList.add('active');
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
function closeLightbox() {
document.getElementById('lightbox').classList.remove('active');
document.body.style.overflow = ''; // Restore scrolling
}
function changeImage(direction) {
currentImageIndex += direction;
if (currentImageIndex >= images.length) {
currentImageIndex = 0;
} else if (currentImageIndex < 0) {
currentImageIndex = images.length - 1;
}
updateLightboxImage();
}
function updateLightboxImage() {
const image = images[currentImageIndex];
const lightboxImage = document.getElementById('lightbox-image');
const lightboxTitle = document.getElementById('lightbox-title');
const lightboxExif = document.getElementById('lightbox-exif');
lightboxImage.src = image.src;
lightboxImage.alt = image.alt;
lightboxTitle.textContent = image.title;
// Build EXIF display
const exif = image.exif || {};
let exifHtml = '';
// Camera info
if (exif.camera_make || exif.camera_model) {
const camera = [exif.camera_make, exif.camera_model].filter(Boolean).join(' ');
exifHtml += `<div class="exif-row"><span class="exif-label">Camera:</span>${camera}</div>`;
}
// Lens
if (exif.lens) {
exifHtml += `<div class="exif-row"><span class="exif-label">Lens:</span>${exif.lens}</div>`;
}
// Settings (on one line if all present)
const settings = [];
if (exif.focal_length) settings.push(exif.focal_length);
if (exif.aperture) settings.push(exif.aperture);
if (exif.shutter_speed) settings.push(exif.shutter_speed);
if (exif.iso) settings.push(exif.iso);
if (settings.length > 0) {
exifHtml += `<div class="exif-row"><span class="exif-label">Settings:</span>${settings.join(' • ')}</div>`;
}
// Date taken
if (exif.date_original || exif.date_taken) {
const date = exif.date_original || exif.date_taken;
exifHtml += `<div class="exif-row"><span class="exif-label">Taken:</span>${date}</div>`;
}
// Dimensions
if (exif.width && exif.height) {
exifHtml += `<div class="exif-row"><span class="exif-label">Size:</span>${exif.width} × ${exif.height}</div>`;
}
lightboxExif.innerHTML = exifHtml;
// Update navigation visibility
const prevBtn = document.querySelector('.lightbox-prev');
const nextBtn = document.querySelector('.lightbox-next');
if (images.length <= 1) {
prevBtn.style.display = 'none';
nextBtn.style.display = 'none';
} else {
prevBtn.style.display = 'flex';
nextBtn.style.display = 'flex';
}
}
</script>
{% endblock %}