diff --git a/assets/css/main.css b/assets/css/main.css index 3ebe07092..9650b4ff7 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -386,7 +386,7 @@ html { top: 0.5rem !important; } -/* Copy button icons - light mode */ +/* Copy button icons */ .highlight button .icon-tabler.icon-tabler-copy { stroke: var(--color-gray-400); transition: stroke 0.2s ease; @@ -400,7 +400,7 @@ html { stroke: var(--color-green-600); } -/* Checkmark draw-in animation */ +/* Checkmark draw-in animation - stroke length approximation for smooth animation */ .highlight .icon-tabler.icon-tabler-check path { stroke-dasharray: 50; stroke-dashoffset: 50; @@ -834,16 +834,44 @@ mark[data-pagefind-highlight], /* Image Lightbox */ .prose img[role="button"] { - transition: opacity 0.2s ease; + @apply transition-opacity duration-200 hover:opacity-90; } -.prose img[role="button"]:hover { - opacity: 0.9; +#image-lightbox { + @apply transition-opacity duration-200; +} + +#image-lightbox img, +#image-lightbox iframe { + @apply rounded-lg shadow-lg; } #image-lightbox img { - border-radius: 0.5rem; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + animation: lightbox-zoom 0.2s ease-out; +} + +#image-lightbox iframe { + animation: lightbox-fade 0.2s ease-out; +} + +@keyframes lightbox-zoom { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes lightbox-fade { + from { + opacity: 0; + } + to { + opacity: 1; + } } /* Tabsets */ diff --git a/assets/js/image-lightbox.js b/assets/js/image-lightbox.js index 1206b3fbb..4b20a82cf 100644 --- a/assets/js/image-lightbox.js +++ b/assets/js/image-lightbox.js @@ -8,33 +8,136 @@ lightbox.id = 'image-lightbox'; lightbox.className = 'fixed inset-0 z-50 hidden items-center justify-center bg-blue-100/80 transition-opacity'; lightbox.innerHTML = ` - - + `; document.body.appendChild(lightbox); const lightboxImg = document.getElementById('lightbox-image'); + const lightboxPdf = document.getElementById('lightbox-pdf'); const closeBtn = document.getElementById('lightbox-close'); + const loadingIndicator = document.getElementById('lightbox-loading'); + const errorIndicator = document.getElementById('lightbox-error'); + const announcement = document.getElementById('lightbox-announcement'); + let isTransitioning = false; + let lastFocusedElement = null; + let pdfLoadTimeout = null; - // Find all images in prose content - const proseImages = document.querySelectorAll('.prose img:not(a img)'); + // Constants + const TRANSITION_DURATION = 200; + const FOCUS_DELAY = 100; + const PDF_TIMEOUT = 10000; + + // Helper to hide all indicators + function hideIndicators() { + errorIndicator.classList.add('hidden'); + loadingIndicator.classList.add('hidden'); + } + + // Find all images in prose content and cheatsheet thumbnails (excluding linked images) + const proseImages = document.querySelectorAll('.prose img:not(a img), .cheatsheet-thumbnail img:not(a img)'); proseImages.forEach(img => { + // Skip images with no-lightbox class + if (img.classList.contains('no-lightbox')) { + return; + } + // Make images clickable img.style.cursor = 'pointer'; img.setAttribute('role', 'button'); img.setAttribute('tabindex', '0'); img.addEventListener('click', function() { - lightboxImg.src = this.src; - lightboxImg.alt = this.alt || ''; + // Prevent rapid clicks during transition + if (isTransitioning) return; + + // Save current focus + lastFocusedElement = document.activeElement; + + // Check if this is a cheatsheet thumbnail with a PDF + const pdfUrl = this.dataset.pdfUrl; + + // Clear any existing timeout + if (pdfLoadTimeout) { + clearTimeout(pdfLoadTimeout); + pdfLoadTimeout = null; + } + + // Hide error/loading indicators + hideIndicators(); + + if (pdfUrl) { + // Show loading indicator + loadingIndicator.classList.remove('hidden'); + + // Show PDF in iframe + lightboxPdf.src = pdfUrl; + lightboxPdf.classList.remove('hidden'); + lightboxImg.classList.add('hidden'); + + // Handle PDF load + lightboxPdf.onload = function() { + loadingIndicator.classList.add('hidden'); + announcement.textContent = 'PDF loaded'; + }; + + // Handle PDF error + lightboxPdf.onerror = function() { + loadingIndicator.classList.add('hidden'); + errorIndicator.classList.remove('hidden'); + announcement.textContent = 'Failed to load PDF'; + }; + + // Timeout for slow loads + pdfLoadTimeout = setTimeout(function() { + if (!loadingIndicator.classList.contains('hidden')) { + hideIndicators(); + errorIndicator.classList.remove('hidden'); + announcement.textContent = 'PDF load timeout'; + } + pdfLoadTimeout = null; + }, PDF_TIMEOUT); + } else { + // Show image + lightboxImg.src = this.src; + lightboxImg.alt = this.alt || ''; + lightboxImg.classList.remove('hidden'); + lightboxPdf.classList.add('hidden'); + announcement.textContent = 'Image opened in lightbox'; + } + lightbox.classList.remove('hidden'); lightbox.classList.add('flex'); document.body.style.overflow = 'hidden'; + + // Focus close button for accessibility + setTimeout(function() { + closeBtn.focus(); + }, FOCUS_DELAY); }); // Keyboard support @@ -48,9 +151,55 @@ // Close lightbox function closeLightbox() { - lightbox.classList.add('hidden'); - lightbox.classList.remove('flex'); - document.body.style.overflow = ''; + if (isTransitioning) return; + isTransitioning = true; + + announcement.textContent = 'Lightbox closed'; + lightbox.style.opacity = '0'; + + // Clear pending timeout + if (pdfLoadTimeout) { + clearTimeout(pdfLoadTimeout); + pdfLoadTimeout = null; + } + + setTimeout(function() { + lightbox.classList.add('hidden'); + lightbox.classList.remove('flex'); + lightbox.style.opacity = ''; + document.body.style.overflow = ''; + + // Clear sources and stop any loading + lightboxImg.src = ''; + + // Stop PDF loading (cross-browser) + try { + if (lightboxPdf.contentWindow) { + if (lightboxPdf.contentWindow.stop) { + lightboxPdf.contentWindow.stop(); + } else if (lightboxPdf.contentWindow.document && lightboxPdf.contentWindow.document.execCommand) { + lightboxPdf.contentWindow.document.execCommand('Stop'); + } + } + } catch (e) { + // Cross-origin iframe access might throw, ignore + } + lightboxPdf.src = ''; + + // Hide indicators + hideIndicators(); + + // Reset image transform + lightboxImg.style.transform = ''; + + // Restore focus + if (lastFocusedElement) { + lastFocusedElement.focus(); + lastFocusedElement = null; + } + + isTransitioning = false; + }, TRANSITION_DURATION); } closeBtn.addEventListener('click', closeLightbox); @@ -62,10 +211,63 @@ } }); - // Close on Escape key + // Close on Escape key and handle focus trap document.addEventListener('keydown', function(e) { - if (e.key === 'Escape' && !lightbox.classList.contains('hidden')) { + if (lightbox.classList.contains('hidden')) return; + + if (e.key === 'Escape') { closeLightbox(); } + + // Focus trap - keep focus within lightbox + if (e.key === 'Tab') { + const focusableElements = lightbox.querySelectorAll('button:not([disabled]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + const firstFocusable = focusableElements[0]; + const lastFocusable = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey) { + // Shift + Tab + if (document.activeElement === firstFocusable) { + e.preventDefault(); + lastFocusable.focus(); + } + } else { + // Tab + if (document.activeElement === lastFocusable) { + e.preventDefault(); + firstFocusable.focus(); + } + } + } + }); + + // Enable panning for images on mobile + let panning = false; + let pointX = 0; + let pointY = 0; + let start = { x: 0, y: 0 }; + + lightboxImg.addEventListener('touchstart', function(e) { + if (e.touches.length === 1) { + start = { x: e.touches[0].clientX - pointX, y: e.touches[0].clientY - pointY }; + panning = true; + } + }); + + lightboxImg.addEventListener('touchmove', function(e) { + if (panning && e.touches.length === 1) { + e.preventDefault(); + pointX = e.touches[0].clientX - start.x; + pointY = e.touches[0].clientY - start.y; + this.style.transform = `translate(${pointX}px, ${pointY}px)`; + } + }); + + lightboxImg.addEventListener('touchend', function() { + panning = false; + // Reset position on release for simplicity + pointX = 0; + pointY = 0; + this.style.transform = ''; }); })(); diff --git a/content/resources/cheatsheets/data-import/_index.md b/content/resources/cheatsheets/data-import/_index.md index 6450f1ac3..dfeabc4b9 100644 --- a/content/resources/cheatsheets/data-import/_index.md +++ b/content/resources/cheatsheets/data-import/_index.md @@ -3,6 +3,7 @@ title: Importing data with the tidyverse image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Learn about readr, readxl, and haven. download_url: data-import.pdf people: diff --git a/content/resources/cheatsheets/data-transformation/_index.md b/content/resources/cheatsheets/data-transformation/_index.md index b87efdf6d..52fabf1b5 100644 --- a/content/resources/cheatsheets/data-transformation/_index.md +++ b/content/resources/cheatsheets/data-transformation/_index.md @@ -3,6 +3,7 @@ title: Data transformation with dplyr image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for data transformation with dplyr. download_url: data-transformation.pdf thumbnails: diff --git a/content/resources/cheatsheets/data-visualization/_index.md b/content/resources/cheatsheets/data-visualization/_index.md index 06ea90f2c..5067b821e 100644 --- a/content/resources/cheatsheets/data-visualization/_index.md +++ b/content/resources/cheatsheets/data-visualization/_index.md @@ -3,6 +3,7 @@ title: Data visualization with ggplot2 image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for data visualization with ggplot2. download_url: data-visualization.pdf thumbnails: diff --git a/content/resources/cheatsheets/factors/_index.md b/content/resources/cheatsheets/factors/_index.md index 5afcaa409..e3b8c4b01 100644 --- a/content/resources/cheatsheets/factors/_index.md +++ b/content/resources/cheatsheets/factors/_index.md @@ -3,6 +3,7 @@ title: Factors with forcats image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for factors with forcats. download_url: factors.pdf thumbnails: diff --git a/content/resources/cheatsheets/great-tables/_index.md b/content/resources/cheatsheets/great-tables/_index.md index 7f79a698d..5e1637a6f 100644 --- a/content/resources/cheatsheets/great-tables/_index.md +++ b/content/resources/cheatsheets/great-tables/_index.md @@ -3,6 +3,7 @@ title: Great Tables image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for great tables. download_url: great-tables.pdf thumbnails: diff --git a/content/resources/cheatsheets/gt/_index.md b/content/resources/cheatsheets/gt/_index.md index 9bbadc9e8..341606e50 100644 --- a/content/resources/cheatsheets/gt/_index.md +++ b/content/resources/cheatsheets/gt/_index.md @@ -3,6 +3,7 @@ title: gt image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for gt. download_url: gt.pdf thumbnails: diff --git a/content/resources/cheatsheets/keras/_index.md b/content/resources/cheatsheets/keras/_index.md index e04f21b2f..7bee61c88 100644 --- a/content/resources/cheatsheets/keras/_index.md +++ b/content/resources/cheatsheets/keras/_index.md @@ -3,6 +3,7 @@ title: Deep Learning with Keras image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for deep learning with keras. download_url: keras.pdf thumbnails: diff --git a/content/resources/cheatsheets/lubridate/_index.md b/content/resources/cheatsheets/lubridate/_index.md index 781424493..8d0865b8c 100644 --- a/content/resources/cheatsheets/lubridate/_index.md +++ b/content/resources/cheatsheets/lubridate/_index.md @@ -3,6 +3,7 @@ title: Dates and times with lubridate image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for dates and times with lubridate. download_url: lubridate.pdf thumbnails: diff --git a/content/resources/cheatsheets/ml-create-models/_index.md b/content/resources/cheatsheets/ml-create-models/_index.md index b05815a76..63e03dc06 100644 --- a/content/resources/cheatsheets/ml-create-models/_index.md +++ b/content/resources/cheatsheets/ml-create-models/_index.md @@ -5,6 +5,7 @@ resource_type: cheatsheet date: '2026-04-09' description: Quick reference guide for create models with parsnip. download_url: ml-create-models.pdf +lightbox: true software: - parsnip languages: diff --git a/content/resources/cheatsheets/ml-preprocessing-data/_index.md b/content/resources/cheatsheets/ml-preprocessing-data/_index.md index 5ab2ff413..9b78a510c 100644 --- a/content/resources/cheatsheets/ml-preprocessing-data/_index.md +++ b/content/resources/cheatsheets/ml-preprocessing-data/_index.md @@ -3,6 +3,7 @@ title: Preprocessing data with recipes image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Get your data ready for modeling using pipable sequences of feature engineering steps with recipes. download_url: ml-preprocessing-data.pdf thumbnails: diff --git a/content/resources/cheatsheets/nlp-with-llms/_index.md b/content/resources/cheatsheets/nlp-with-llms/_index.md index 6af78efe6..cdc8120dc 100644 --- a/content/resources/cheatsheets/nlp-with-llms/_index.md +++ b/content/resources/cheatsheets/nlp-with-llms/_index.md @@ -3,6 +3,7 @@ title: Natural Language Processing using LLMs in R & Python image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for natural language processing using llms in r & python. download_url: nlp-with-llms.pdf diff --git a/content/resources/cheatsheets/package-development/_index.md b/content/resources/cheatsheets/package-development/_index.md index 7cac45e3b..29322ab89 100644 --- a/content/resources/cheatsheets/package-development/_index.md +++ b/content/resources/cheatsheets/package-development/_index.md @@ -3,6 +3,7 @@ title: Package Development image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for package development. download_url: package-development.pdf thumbnails: diff --git a/content/resources/cheatsheets/plotnine/_index.md b/content/resources/cheatsheets/plotnine/_index.md index 409b9e040..df3bb4b4f 100644 --- a/content/resources/cheatsheets/plotnine/_index.md +++ b/content/resources/cheatsheets/plotnine/_index.md @@ -3,6 +3,7 @@ title: Data visualization with Plotnine image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for data visualization with plotnine. download_url: plotnine.pdf thumbnails: diff --git a/content/resources/cheatsheets/plumber/_index.md b/content/resources/cheatsheets/plumber/_index.md index c2bf5ca86..1b9f7b9c1 100644 --- a/content/resources/cheatsheets/plumber/_index.md +++ b/content/resources/cheatsheets/plumber/_index.md @@ -3,6 +3,7 @@ title: REST APIs with plumber image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for rest apis with plumber. download_url: plumber.pdf thumbnails: diff --git a/content/resources/cheatsheets/posit-team/_index.md b/content/resources/cheatsheets/posit-team/_index.md index 16503d970..669e945f8 100644 --- a/content/resources/cheatsheets/posit-team/_index.md +++ b/content/resources/cheatsheets/posit-team/_index.md @@ -3,6 +3,7 @@ title: Posit Team image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for posit team. download_url: posit-team.pdf thumbnails: diff --git a/content/resources/cheatsheets/positron/_index.md b/content/resources/cheatsheets/positron/_index.md index 6648ae5f4..92848f00b 100644 --- a/content/resources/cheatsheets/positron/_index.md +++ b/content/resources/cheatsheets/positron/_index.md @@ -3,6 +3,7 @@ title: Positron image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for positron. download_url: positron.pdf thumbnails: diff --git a/content/resources/cheatsheets/purrr/_index.md b/content/resources/cheatsheets/purrr/_index.md index e4dfef803..303261419 100644 --- a/content/resources/cheatsheets/purrr/_index.md +++ b/content/resources/cheatsheets/purrr/_index.md @@ -3,6 +3,7 @@ title: Apply functions with purrr image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for apply functions with purrr. download_url: purrr.pdf thumbnails: diff --git a/content/resources/cheatsheets/quarto/_index.md b/content/resources/cheatsheets/quarto/_index.md index f5ebdc629..4c8c20d9e 100644 --- a/content/resources/cheatsheets/quarto/_index.md +++ b/content/resources/cheatsheets/quarto/_index.md @@ -3,6 +3,7 @@ title: Publish and Share with Quarto image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for publish and share with quarto. download_url: quarto.pdf thumbnails: diff --git a/content/resources/cheatsheets/reticulate/_index.md b/content/resources/cheatsheets/reticulate/_index.md index 1cba41c3c..c49241f49 100644 --- a/content/resources/cheatsheets/reticulate/_index.md +++ b/content/resources/cheatsheets/reticulate/_index.md @@ -3,6 +3,7 @@ title: Use Python with R with reticulate image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for use python with r with reticulate. download_url: reticulate.pdf thumbnails: diff --git a/content/resources/cheatsheets/rmarkdown/_index.md b/content/resources/cheatsheets/rmarkdown/_index.md index e5899d8a2..a5ea469f1 100644 --- a/content/resources/cheatsheets/rmarkdown/_index.md +++ b/content/resources/cheatsheets/rmarkdown/_index.md @@ -3,6 +3,7 @@ title: rmarkdown image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for rmarkdown. download_url: rmarkdown.pdf thumbnails: diff --git a/content/resources/cheatsheets/rstudio-ide/_index.md b/content/resources/cheatsheets/rstudio-ide/_index.md index f6da92035..b381c02e2 100644 --- a/content/resources/cheatsheets/rstudio-ide/_index.md +++ b/content/resources/cheatsheets/rstudio-ide/_index.md @@ -3,6 +3,7 @@ title: RStudio IDE image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for rstudio ide. download_url: rstudio-ide.pdf thumbnails: diff --git a/content/resources/cheatsheets/shiny-python/_index.md b/content/resources/cheatsheets/shiny-python/_index.md index 1354caa15..821d11fda 100644 --- a/content/resources/cheatsheets/shiny-python/_index.md +++ b/content/resources/cheatsheets/shiny-python/_index.md @@ -3,6 +3,7 @@ title: Shiny for Python image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for shiny for python. download_url: shiny-python.pdf thumbnails: diff --git a/content/resources/cheatsheets/shiny/_index.md b/content/resources/cheatsheets/shiny/_index.md index fda97bb9b..59b6035b7 100644 --- a/content/resources/cheatsheets/shiny/_index.md +++ b/content/resources/cheatsheets/shiny/_index.md @@ -3,6 +3,7 @@ title: Shiny for R image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for shiny for r. download_url: shiny.pdf thumbnails: diff --git a/content/resources/cheatsheets/shinychat/_index.md b/content/resources/cheatsheets/shinychat/_index.md index 6d00bc0b4..8054d3070 100644 --- a/content/resources/cheatsheets/shinychat/_index.md +++ b/content/resources/cheatsheets/shinychat/_index.md @@ -3,6 +3,7 @@ title: AI chatbots with shinychat image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for ai chatbots with shinychat. download_url: shinychat.pdf thumbnails: diff --git a/content/resources/cheatsheets/sparklyr/_index.md b/content/resources/cheatsheets/sparklyr/_index.md index 3cf1b071a..2e066b653 100644 --- a/content/resources/cheatsheets/sparklyr/_index.md +++ b/content/resources/cheatsheets/sparklyr/_index.md @@ -3,6 +3,7 @@ title: Data science in Spark with sparklyr image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for data science in spark with sparklyr. download_url: sparklyr.pdf thumbnails: diff --git a/content/resources/cheatsheets/strings/_index.md b/content/resources/cheatsheets/strings/_index.md index 45e3e20d1..f0b8072ed 100644 --- a/content/resources/cheatsheets/strings/_index.md +++ b/content/resources/cheatsheets/strings/_index.md @@ -3,6 +3,7 @@ title: String manipulation with stringr image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for string manipulation with stringr. download_url: strings.pdf thumbnails: diff --git a/content/resources/cheatsheets/tidyr/_index.md b/content/resources/cheatsheets/tidyr/_index.md index c46871301..ce51705e5 100644 --- a/content/resources/cheatsheets/tidyr/_index.md +++ b/content/resources/cheatsheets/tidyr/_index.md @@ -3,6 +3,7 @@ title: Data tidying with tidyr image: page-1.png resource_type: cheatsheet date: '2026-02-25' +lightbox: true description: Quick reference guide for data tidying with tidyr. download_url: tidyr.pdf thumbnails: diff --git a/layouts/partials/extended_footer.html b/layouts/partials/extended_footer.html index 969f67d89..577a0e793 100644 --- a/layouts/partials/extended_footer.html +++ b/layouts/partials/extended_footer.html @@ -37,7 +37,13 @@ {{ if .Params.asciinema }}{{ partial "asciinema.html" . }}{{ end }} -{{- /* image-lightbox.js disabled */ -}} +{{- if .Params.lightbox }} +{{- $jsLightbox := resources.Get "js/image-lightbox.js" -}} +{{- if not hugo.IsDevelopment }} + {{- $jsLightbox = $jsLightbox | minify | fingerprint -}} +{{ end -}} + +{{ end -}} {{- if (findRE "panel-tabset-tabby" .Content 1) }} {{- $jsTabby := resources.Get "js/tabby.min.js" -}} diff --git a/layouts/resources/term.html b/layouts/resources/term.html index d5544944d..16ee8735e 100644 --- a/layouts/resources/term.html +++ b/layouts/resources/term.html @@ -249,15 +249,27 @@

{{ if eq .Params.resource_type "cheatsheet" }} {{- /* Thumbnails */ -}} {{ with .Params.thumbnails }} + {{ $pdfUrl := "" }} + {{ with $.Params.download_url }} + {{ $pdf := $.Resources.Get . }} + {{ if $pdf }} + {{ $pdfUrl = $pdf.RelPermalink }} + {{ else }} + {{ warnf "PDF not found: %s for page %s" . $.File.Path }} + {{ end }} + {{ end }}
-
- {{ range . }} - {{ $thumbnail := $.Resources.Get . }} +
+ {{ range $index, $element := . }} + {{ $thumbnail := $.Resources.Get $element }} {{ if $thumbnail }}
Page preview + class="h-48 w-auto rounded-lg border border-gray-300" loading="lazy" + {{ if $pdfUrl }}data-pdf-url="{{ $pdfUrl }}#page={{ add $index 1 }}"{{ end }} />
+ {{ else }} + {{ warnf "Thumbnail not found: %s for page %s" $element $.File.Path }} {{ end }} {{ end }}