summaryrefslogtreecommitdiffstats
path: root/assets/js/photo-utils.js
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-22 12:42:56 +0200
committerDanilo M. <danix@danix.xyz>2026-04-22 12:42:56 +0200
commit631547a75142326a7c71bdf123e1475217a5ad73 (patch)
treef3cfef6b3c5b42bf626fc823ddcf63b8dcf4cdbb /assets/js/photo-utils.js
parent77ccbe72fad5a4870185fff374f75471c16a9043 (diff)
downloaddanixxyz-theme-631547a75142326a7c71bdf123e1475217a5ad73.tar.gz
danixxyz-theme-631547a75142326a7c71bdf123e1475217a5ad73.zip
chore: replace with extracted danix.xyz-hacker theme (danix2-hugo-theme)
Diffstat (limited to 'assets/js/photo-utils.js')
-rw-r--r--assets/js/photo-utils.js476
1 files changed, 0 insertions, 476 deletions
diff --git a/assets/js/photo-utils.js b/assets/js/photo-utils.js
deleted file mode 100644
index 32ede76..0000000
--- a/assets/js/photo-utils.js
+++ /dev/null
@@ -1,476 +0,0 @@
-(function() {
- 'use strict';
-
- // Internal state
- let currentPhotoIndex = 0;
- let photosData = [];
- let isOpen = false;
- let exifData = {};
- let options = {
- swipeEnabled: true,
- keyboardEnabled: true,
- extractEXIF: true,
- onOpen: null,
- onClose: null,
- };
-
- // Lightbox DOM elements (created on first init)
- let lightboxModal = null;
- let lightboxImage = null;
- let lightboxCaption = null;
- let lightboxSidebar = null;
-
- /**
- * Initialize lightbox for a photo collection
- * @param {string} containerSelector - CSS selector for photo container
- * @param {Array} photos - Array of photo objects: {src, alt, caption, location}
- * @param {Object} opts - Options: {swipeEnabled, keyboardEnabled, extractEXIF, onOpen, onClose}
- * @returns {Object} - {openLightbox, closeLightbox}
- */
- function initLightbox(containerSelector, photos, opts = {}) {
- photosData = photos;
- options = { ...options, ...opts };
-
- // Merge options with defaults
- const container = document.querySelector(containerSelector);
- if (!container) {
- console.error('Photo container not found:', containerSelector);
- return { openLightbox: () => {}, closeLightbox: () => {} };
- }
-
- // Attach click listeners to all figures in the container
- const figures = container.querySelectorAll('figure[data-photo-index]');
- figures.forEach((figure) => {
- figure.addEventListener('click', () => {
- const index = parseInt(figure.dataset.photoIndex, 10);
- openLightbox(index);
- });
- });
-
- // Return public API
- return {
- openLightbox,
- closeLightbox,
- };
- }
-
- /**
- * Open lightbox to a specific photo
- * @param {number} photoIndex - 0-based index into photosData array
- */
- function openLightbox(photoIndex) {
- if (photoIndex < 0 || photoIndex >= photosData.length) {
- console.warn('Invalid photo index:', photoIndex);
- return;
- }
-
- currentPhotoIndex = photoIndex;
- isOpen = true;
-
- // Create lightbox DOM if it doesn't exist
- if (!lightboxModal) {
- createLightboxDOM();
- }
-
- // Populate lightbox with current photo
- updateLightboxContent();
-
- // Show modal
- lightboxModal.classList.add('visible');
- document.body.style.overflow = 'hidden';
-
- // Extract EXIF if enabled
- if (options.extractEXIF) {
- extractEXIFForCurrentPhoto();
- }
-
- // Attach event listeners
- attachEventListeners();
-
- // Callback
- if (options.onOpen) {
- options.onOpen(photoIndex);
- }
- }
-
- /**
- * Close lightbox
- */
- function closeLightbox() {
- if (!isOpen || !lightboxModal) return;
-
- isOpen = false;
- lightboxModal.classList.remove('visible');
- document.body.style.overflow = '';
-
- // Detach event listeners
- detachEventListeners();
-
- // Callback
- if (options.onClose) {
- options.onClose();
- }
- }
-
- /**
- * Create lightbox HTML structure and inject into DOM
- */
- function createLightboxDOM() {
- lightboxModal = document.createElement('div');
- lightboxModal.className = 'photo-lightbox';
-
- lightboxModal.innerHTML = `
- <div class="photo-lightbox-backdrop"></div>
- <div class="photo-lightbox-container">
- <button class="photo-lightbox-close" aria-label="Close lightbox">×</button>
-
- <button class="photo-lightbox-prev" aria-label="Previous photo">←</button>
-
- <div class="photo-lightbox-content">
- <img class="photo-lightbox-image" src="" alt="">
- <div class="photo-lightbox-caption"></div>
- </div>
-
- <button class="photo-lightbox-next" aria-label="Next photo">→</button>
-
- <div class="photo-lightbox-sidebar">
- <div class="photo-lightbox-sidebar-content"></div>
- </div>
- </div>
- `;
-
- document.body.appendChild(lightboxModal);
-
- // Cache element references
- lightboxImage = lightboxModal.querySelector('.photo-lightbox-image');
- lightboxCaption = lightboxModal.querySelector('.photo-lightbox-caption');
- lightboxSidebar = lightboxModal.querySelector('.photo-lightbox-sidebar-content');
-
- // Attach close button and backdrop click
- lightboxModal.querySelector('.photo-lightbox-close').addEventListener('click', closeLightbox);
- lightboxModal.querySelector('.photo-lightbox-backdrop').addEventListener('click', closeLightbox);
-
- // Attach navigation buttons
- lightboxModal.querySelector('.photo-lightbox-prev').addEventListener('click', () => navigate(-1));
- lightboxModal.querySelector('.photo-lightbox-next').addEventListener('click', () => navigate(1));
- }
-
- /**
- * Update lightbox content with current photo
- */
- function updateLightboxContent() {
- const photo = photosData[currentPhotoIndex];
- if (!photo) return;
-
- lightboxImage.src = photo.src;
- lightboxImage.alt = photo.alt || '';
-
- // Caption
- if (photo.caption) {
- lightboxCaption.textContent = photo.caption;
- lightboxCaption.style.display = 'block';
- } else {
- lightboxCaption.style.display = 'none';
- }
-
- // Update prev/next button states
- const prevBtn = lightboxModal.querySelector('.photo-lightbox-prev');
- const nextBtn = lightboxModal.querySelector('.photo-lightbox-next');
- prevBtn.disabled = currentPhotoIndex === 0;
- nextBtn.disabled = currentPhotoIndex === photosData.length - 1;
- }
-
- /**
- * Navigate to previous or next photo
- * @param {number} direction - -1 for prev, 1 for next
- */
- function navigate(direction) {
- const newIndex = currentPhotoIndex + direction;
- if (newIndex >= 0 && newIndex < photosData.length) {
- currentPhotoIndex = newIndex;
- updateLightboxContent();
-
- // Reset EXIF data and re-extract if enabled
- exifData = {};
- if (options.extractEXIF) {
- extractEXIFForCurrentPhoto();
- }
- }
- }
-
- /**
- * Extract EXIF data from current photo image element
- */
- function extractEXIFForCurrentPhoto() {
- extractEXIF(lightboxImage).then((data) => {
- exifData = data;
- updateSidebarContent();
- });
- }
-
- /**
- * Update sidebar with EXIF and location data
- */
- function updateSidebarContent() {
- const photo = photosData[currentPhotoIndex];
- let sidebarHTML = '';
-
- // Always show location if provided
- if (photo.location) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Location</div>
- <div class="photo-metadata-value">${photo.location}</div>
- </div>
- `;
- }
-
- // Show EXIF fields if available
- if (Object.keys(exifData).length > 0) {
- sidebarHTML += `
- <div class="photo-metadata-divider"></div>
- <div class="photo-metadata-label">Camera</div>
- `;
-
- if (exifData.camera) {
- sidebarHTML += `
- <div class="photo-metadata-value">${exifData.camera}</div>
- `;
- }
- if (exifData.lens) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Lens</div>
- <div class="photo-metadata-value">${exifData.lens}</div>
- </div>
- `;
- }
- if (exifData.focalLength) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Focal Length</div>
- <div class="photo-metadata-value">${exifData.focalLength}</div>
- </div>
- `;
- }
- if (exifData.aperture) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Aperture</div>
- <div class="photo-metadata-value">${exifData.aperture}</div>
- </div>
- `;
- }
- if (exifData.shutterSpeed) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Shutter Speed</div>
- <div class="photo-metadata-value">${exifData.shutterSpeed}</div>
- </div>
- `;
- }
- if (exifData.iso) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">ISO</div>
- <div class="photo-metadata-value">${exifData.iso}</div>
- </div>
- `;
- }
- if (exifData.dateTaken) {
- sidebarHTML += `
- <div class="photo-metadata-field">
- <div class="photo-metadata-label">Date Taken</div>
- <div class="photo-metadata-value">${exifData.dateTaken}</div>
- </div>
- `;
- }
- }
-
- // Show or hide sidebar based on whether there's content
- const sidebarContainer = lightboxModal.querySelector('.photo-lightbox-sidebar');
- if (sidebarHTML) {
- lightboxSidebar.innerHTML = sidebarHTML;
- sidebarContainer.classList.add('visible');
- } else {
- sidebarContainer.classList.remove('visible');
- }
- }
-
- /**
- * Extract EXIF data from an image element using exif-js library
- * @param {HTMLImageElement} imageElement - Image to read EXIF from
- * @returns {Promise} - Resolves to {camera, lens, iso, aperture, shutterSpeed, focalLength, dateTaken}
- */
- function extractEXIF(imageElement) {
- return new Promise((resolve) => {
- // If exif-js is not loaded, return empty object
- if (typeof EXIF === 'undefined') {
- console.warn('exif-js library not loaded');
- resolve({});
- return;
- }
-
- EXIF.getData(imageElement, function() {
- const data = {
- camera: EXIF.getTag(this, 'Model'),
- lens: EXIF.getTag(this, 'LensModel'),
- iso: EXIF.getTag(this, 'ISOSpeedRatings'),
- aperture: formatAperture(EXIF.getTag(this, 'FNumber')),
- shutterSpeed: formatShutterSpeed(EXIF.getTag(this, 'ExposureTime')),
- focalLength: formatFocalLength(EXIF.getTag(this, 'FocalLength')),
- dateTaken: EXIF.getTag(this, 'DateTime'),
- };
-
- // Filter out undefined values
- Object.keys(data).forEach(key => {
- if (data[key] === undefined) {
- delete data[key];
- }
- });
-
- resolve(data);
- });
- });
- }
-
- /**
- * Format aperture value (f-number)
- */
- function formatAperture(value) {
- if (!value) return null;
- if (typeof value === 'object' && value.numerator !== undefined) {
- return `f/${(value.numerator / value.denominator).toFixed(1)}`;
- }
- return `f/${value}`;
- }
-
- /**
- * Format shutter speed (exposure time)
- */
- function formatShutterSpeed(value) {
- if (!value) return null;
- if (typeof value === 'object' && value.numerator !== undefined) {
- const speed = value.numerator / value.denominator;
- if (speed >= 1) {
- return `${speed.toFixed(1)}s`;
- }
- return `1/${Math.round(1 / speed)}`;
- }
- return value;
- }
-
- /**
- * Format focal length
- */
- function formatFocalLength(value) {
- if (!value) return null;
- if (typeof value === 'object' && value.numerator !== undefined) {
- return `${(value.numerator / value.denominator).toFixed(0)}mm`;
- }
- return `${value}mm`;
- }
-
- /**
- * Attach event listeners (swipe, keyboard, etc.)
- */
- function attachEventListeners() {
- if (options.swipeEnabled) {
- attachSwipeListeners();
- }
- if (options.keyboardEnabled) {
- attachKeyboardListeners();
- }
- }
-
- /**
- * Detach event listeners to prevent memory leaks
- */
- function detachEventListeners() {
- if (options.swipeEnabled) {
- detachSwipeListeners();
- }
- if (options.keyboardEnabled) {
- detachKeyboardListeners();
- }
- }
-
- /**
- * Touch swipe event listeners
- */
- let swipeStartX = 0;
- let swipeStartY = 0;
-
- function onTouchStart(e) {
- swipeStartX = e.touches[0].clientX;
- swipeStartY = e.touches[0].clientY;
- }
-
- function onTouchEnd(e) {
- const swipeEndX = e.changedTouches[0].clientX;
- const swipeEndY = e.changedTouches[0].clientY;
- const diffX = swipeEndX - swipeStartX;
- const diffY = swipeEndY - swipeStartY;
-
- // Only consider horizontal swipe if delta-x > delta-y
- if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
- if (diffX > 0) {
- // Swiped right: go to previous
- navigate(-1);
- } else {
- // Swiped left: go to next
- navigate(1);
- }
- }
- }
-
- function attachSwipeListeners() {
- lightboxModal.addEventListener('touchstart', onTouchStart, false);
- lightboxModal.addEventListener('touchend', onTouchEnd, false);
- }
-
- function detachSwipeListeners() {
- lightboxModal.removeEventListener('touchstart', onTouchStart, false);
- lightboxModal.removeEventListener('touchend', onTouchEnd, false);
- }
-
- /**
- * Keyboard event listeners
- */
- function onKeyDown(e) {
- if (!isOpen) return;
-
- switch (e.key) {
- case 'ArrowLeft':
- navigate(-1);
- e.preventDefault();
- break;
- case 'ArrowRight':
- navigate(1);
- e.preventDefault();
- break;
- case 'Escape':
- closeLightbox();
- e.preventDefault();
- break;
- default:
- break;
- }
- }
-
- function attachKeyboardListeners() {
- document.addEventListener('keydown', onKeyDown);
- }
-
- function detachKeyboardListeners() {
- document.removeEventListener('keydown', onKeyDown);
- }
-
- // Expose public API
- window.PhotoUtils = {
- initLightbox,
- extractEXIF,
- openLightbox,
- closeLightbox,
- };
-})();