Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down
232 changes: 217 additions & 15 deletions assets/js/image-lightbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
<button id="lightbox-close" class="absolute top-4 right-4 text-gray-700 text-4xl font-light hover:text-gray-900 transition-colors z-10" aria-label="Close lightbox">
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<img id="lightbox-image" class="max-w-[90vw] max-h-[90vh] object-contain" alt="">
<div role="dialog" aria-modal="true" aria-label="Image lightbox" class="contents">
<button id="lightbox-close" class="absolute top-4 right-4 text-gray-700 text-4xl font-light hover:text-gray-900 transition-colors z-10" aria-label="Close lightbox">
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<div id="lightbox-loading" class="absolute inset-0 flex items-center justify-center text-gray-600 hidden" aria-live="polite">
<div class="text-center">
<svg class="animate-spin h-12 w-12 mx-auto mb-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Loading...</span>
</div>
</div>
<div id="lightbox-error" class="absolute inset-0 flex items-center justify-center text-red-600 hidden" role="alert" aria-live="assertive">
<div class="text-center">
<p class="text-lg font-medium">Failed to load PDF</p>
<p class="text-sm mt-2">The file may not exist or cannot be displayed.</p>
</div>
</div>
<img id="lightbox-image" class="max-w-[95vw] max-h-[95vh] md:max-w-[90vw] md:max-h-[90vh] object-contain touch-pan-x touch-pan-y touch-pinch-zoom" alt="">
<iframe id="lightbox-pdf" class="w-[95vw] h-[95vh] md:w-[90vw] md:h-[90vh] bg-white hidden" frameborder="0" title="PDF viewer"></iframe>
<div id="lightbox-announcement" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>
</div>
`;
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
Expand All @@ -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);
Expand All @@ -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 = '';
});
})();
1 change: 1 addition & 0 deletions content/resources/cheatsheets/data-import/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions content/resources/cheatsheets/factors/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions content/resources/cheatsheets/great-tables/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions content/resources/cheatsheets/gt/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions content/resources/cheatsheets/keras/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading