the home site for me: also iteration 3 or 4 of my site

feat: add a lightbox implementation

dunkirk.sh 83a8f6cf 8b44fc5b

verified
+215 -8
+109
sass/css/_lightbox.scss
···
··· 1 + #lightbox { 2 + display: none; 3 + position: fixed; 4 + top: 0; 5 + left: 0; 6 + width: 100%; 7 + height: 100%; 8 + background-color: rgba(0, 0, 0, 0.8); 9 + z-index: 9999; 10 + justify-content: center; 11 + align-items: center; 12 + } 13 + 14 + .lightbox-content { 15 + position: relative; 16 + max-width: 90%; 17 + max-height: 90%; 18 + display: flex; 19 + flex-direction: column; 20 + align-items: center; 21 + justify-content: center; 22 + } 23 + 24 + #lightbox-img { 25 + max-width: 100%; 26 + max-height: 80vh; 27 + object-fit: contain; 28 + border: none; 29 + padding: 0; 30 + margin: 0; 31 + background: transparent; 32 + border-radius: 0; 33 + } 34 + 35 + .lightbox-controls { 36 + display: flex; 37 + gap: 2rem; 38 + margin-top: 1rem; 39 + align-items: center; 40 + } 41 + 42 + .lightbox-close { 43 + position: fixed; 44 + top: 20px; 45 + right: 20px; 46 + font-size: 40px; 47 + color: var(--text); 48 + background: transparent !important; 49 + border: none; 50 + cursor: pointer; 51 + padding: 0; 52 + line-height: 1; 53 + -webkit-tap-highlight-color: transparent; 54 + transition: color 120ms ease, transform 300ms ease; 55 + } 56 + 57 + .lightbox-close:hover { 58 + background: transparent !important; 59 + color: var(--accent); 60 + background-color: transparent !important; 61 + transform: rotate(90deg); 62 + } 63 + 64 + .lightbox-close:focus { 65 + background: transparent !important; 66 + background-color: transparent !important; 67 + } 68 + 69 + .lightbox-prev, 70 + .lightbox-next { 71 + font-size: 30px; 72 + color: var(--text); 73 + background: transparent !important; 74 + border: none; 75 + cursor: pointer; 76 + padding: 0.5rem 1rem; 77 + user-select: none; 78 + -webkit-tap-highlight-color: transparent; 79 + transition: color 120ms ease; 80 + } 81 + 82 + .lightbox-prev:hover, 83 + .lightbox-next:hover { 84 + background: transparent !important; 85 + color: var(--accent); 86 + background-color: transparent !important; 87 + } 88 + 89 + .lightbox-prev:focus, 90 + .lightbox-next:focus { 91 + background: transparent !important; 92 + background-color: transparent !important; 93 + } 94 + 95 + @media only screen and (max-width: 720px) { 96 + .lightbox-close { 97 + top: 10px; 98 + right: 10px; 99 + } 100 + } 101 + 102 + .img-container { 103 + cursor: pointer; 104 + transition: opacity 120ms ease; 105 + } 106 + 107 + .img-container:hover { 108 + opacity: 0.9; 109 + }
+1
sass/css/main.scss
··· 7 @use "copy-button"; 8 @use "view-transitions"; 9 @use "emoji-inline";
··· 7 @use "copy-button"; 8 @use "view-transitions"; 9 @use "emoji-inline"; 10 + @use "lightbox";
+3 -5
sass/css/mods.css
··· 110 } 111 112 .center .img-container { 113 - display: block; 114 margin: 1rem auto; 115 } 116 ··· 342 gap: 1rem; 343 max-width: 100%; 344 justify-content: center; 345 } 346 347 .img-group .img-container { 348 - flex: 1; 349 - min-width: 0; 350 background-color: var(--accent); 351 border-bottom: 4px solid var(--bg-light); 352 border-radius: 7px 7px 10px 10px; 353 padding: 0.35rem; 354 margin: 1rem 0; 355 } 356 357 .img-group img { 358 - width: 100%; 359 height: auto; 360 - object-fit: contain; 361 border-radius: 0.35rem; 362 } 363
··· 110 } 111 112 .center .img-container { 113 margin: 1rem auto; 114 } 115 ··· 341 gap: 1rem; 342 max-width: 100%; 343 justify-content: center; 344 + align-items: flex-start; 345 } 346 347 .img-group .img-container { 348 background-color: var(--accent); 349 border-bottom: 4px solid var(--bg-light); 350 border-radius: 7px 7px 10px 10px; 351 padding: 0.35rem; 352 margin: 1rem 0; 353 + line-height: 0; 354 } 355 356 .img-group img { 357 + max-width: 100%; 358 height: auto; 359 border-radius: 0.35rem; 360 } 361
+92
static/lightbox.js
···
··· 1 + let currentLightboxImages = []; 2 + let currentLightboxIndex = 0; 3 + 4 + function openLightbox(src) { 5 + currentLightboxImages = [src]; 6 + currentLightboxIndex = 0; 7 + showLightbox(); 8 + } 9 + 10 + function openLightboxGroup(element) { 11 + const group = element.closest('.img-group'); 12 + const images = Array.from(group.querySelectorAll('img')).map(img => img.src); 13 + const clickedImg = element.querySelector('img'); 14 + 15 + currentLightboxImages = images; 16 + currentLightboxIndex = images.indexOf(clickedImg.src); 17 + showLightbox(); 18 + } 19 + 20 + function showLightbox() { 21 + let lightbox = document.getElementById('lightbox'); 22 + 23 + if (!lightbox) { 24 + lightbox = document.createElement('div'); 25 + lightbox.id = 'lightbox'; 26 + lightbox.innerHTML = ` 27 + <div class="lightbox-content"> 28 + <button class="lightbox-close" onclick="closeLightbox()">&times;</button> 29 + <img id="lightbox-img" src="" alt=""> 30 + <div class="lightbox-controls"> 31 + <button class="lightbox-prev" onclick="prevImage()">←</button> 32 + <button class="lightbox-next" onclick="nextImage()">→</button> 33 + </div> 34 + </div> 35 + `; 36 + document.body.appendChild(lightbox); 37 + 38 + lightbox.addEventListener('click', (e) => { 39 + if (e.target === lightbox) closeLightbox(); 40 + }); 41 + 42 + document.addEventListener('keydown', handleKeyPress); 43 + } 44 + 45 + updateLightboxImage(); 46 + lightbox.style.display = 'flex'; 47 + document.body.style.overflow = 'hidden'; 48 + } 49 + 50 + function closeLightbox() { 51 + const lightbox = document.getElementById('lightbox'); 52 + if (lightbox) { 53 + lightbox.style.display = 'none'; 54 + document.body.style.overflow = ''; 55 + } 56 + } 57 + 58 + function updateLightboxImage() { 59 + const img = document.getElementById('lightbox-img'); 60 + const controls = document.querySelector('.lightbox-controls'); 61 + 62 + img.src = currentLightboxImages[currentLightboxIndex]; 63 + 64 + if (currentLightboxImages.length === 1) { 65 + controls.style.display = 'none'; 66 + } else { 67 + controls.style.display = 'flex'; 68 + } 69 + } 70 + 71 + function prevImage() { 72 + currentLightboxIndex = (currentLightboxIndex - 1 + currentLightboxImages.length) % currentLightboxImages.length; 73 + updateLightboxImage(); 74 + } 75 + 76 + function nextImage() { 77 + currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxImages.length; 78 + updateLightboxImage(); 79 + } 80 + 81 + function handleKeyPress(e) { 82 + const lightbox = document.getElementById('lightbox'); 83 + if (!lightbox || lightbox.style.display !== 'flex') return; 84 + 85 + if (e.key === 'Escape') { 86 + closeLightbox(); 87 + } else if (e.key === 'ArrowLeft') { 88 + prevImage(); 89 + } else if (e.key === 'ArrowRight') { 90 + nextImage(); 91 + } 92 + }
+7
templates/head.html
··· 93 defer 94 ></script> 95 96 <script type="speculationrules"> 97 { 98 "prerender": [
··· 93 defer 94 ></script> 95 96 + {% set lightboxJsHash = get_hash(path="lightbox.js", sha_type=256, 97 + base64=true) %} 98 + <script 99 + src="{{ get_url(path='lightbox.js?' ~ lightboxJsHash, trailing_slash=false) | safe }}" 100 + defer 101 + ></script> 102 + 103 <script type="speculationrules"> 104 { 105 "prerender": [
+1 -1
templates/shortcodes/img.html
··· 1 <figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}> 2 - <div class="img-container"> 3 <img src="{{id}}" {% if alt %}alt="{{alt}}" {% endif %} /> 4 </div> 5 {% if caption %}
··· 1 <figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}> 2 + <div class="img-container" onclick="openLightbox('{{id}}')"> 3 <img src="{{id}}" {% if alt %}alt="{{alt}}" {% endif %} /> 4 </div> 5 {% if caption %}
+2 -2
templates/shortcodes/imgs.html
··· 1 <figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}> 2 - <div class="img-group"> 3 {% set images = id | split(pat=",") %} 4 {% set alts = alt | default(value="") | split(pat=",") %} 5 {% for image in images %} 6 - <div class="img-container"> 7 <img src="{{image | trim}}" {% if alts[loop.index0] %}alt="{{alts[loop.index0] | trim}}" {% endif %} /> 8 </div> 9 {% endfor %}
··· 1 <figure {% if class %}class="{{class}}" {% else %}class="center" {% endif %}> 2 + <div class="img-group" data-images="{{id}}" data-alts="{{alt | default(value='')}}"> 3 {% set images = id | split(pat=",") %} 4 {% set alts = alt | default(value="") | split(pat=",") %} 5 {% for image in images %} 6 + <div class="img-container" onclick="openLightboxGroup(this)"> 7 <img src="{{image | trim}}" {% if alts[loop.index0] %}alt="{{alts[loop.index0] | trim}}" {% endif %} /> 8 </div> 9 {% endfor %}