Files
kennethreitz.org/tuftecms/templates/directory.html
kennethreitz a7b18f0d84 Rename Browse to italic etc., fix directory page missing title
- Browse nav item renamed to etc. with italic styling
- Directory template now uses the actual title variable instead of
  raw path segment, fixing missing titles on gallery pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:57:21 -04:00

665 lines
15 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">
<img src="{{ item.static_path }}" alt="{{ item.display_name }}" class="gallery-thumbnail"
data-full="{{ item.static_path }}"
data-title="{{ item.display_name }}"
data-exif="{{ item.exif | tojson | forceescape }}">
</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 %}