Skip to content

Commit a5c7960

Browse files
wesmclaude
andcommitted
feat: Add image lightbox for documentation screenshots
Add clickable lightbox functionality to make documentation screenshots viewable in full resolution. Works on all screen sizes with responsive styling and touch gestures for mobile. Features: - Click any screenshot to view full resolution in modal overlay - Desktop: hover effect shows images are clickable - Mobile: touch-optimized close button and swipe-down gesture - Close with X button, Escape key, or clicking outside image - Smooth fade animations and image captions - Works with Material for MkDocs instant navigation Also updated CLAUDE.md to memorialize reproducibility principles: AI assistants must never modify environments with pip/uv pip install. All dependencies go through pyproject.toml and uv sync. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f495963 commit a5c7960

File tree

4 files changed

+231
-1
lines changed

4 files changed

+231
-1
lines changed

CLAUDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ moneyflow is a terminal-based UI for power users to manage personal finance tran
88

99
### Using uv (REQUIRED)
1010

11-
**IMPORTANT**: This project uses **uv** exclusively for all development workflows. Always use `uv run` for executing scripts and `uv pip` for package management. Never use pip, pipenv, poetry, or other package managers.
11+
**IMPORTANT**: This project uses **uv** exclusively for all development workflows. Always use `uv run` for executing scripts. Never use pip, pipenv, poetry, or other package managers.
12+
13+
**CRITICAL FOR AI ASSISTANTS (Claude Code, etc.)**:
14+
-**NEVER run `pip install` or `uv pip install` to modify the user's environment**
15+
-**NEVER run `uv tool install` for project dependencies**
16+
- ✅ All dependencies MUST be declared in `pyproject.toml` and installed via `uv sync`
17+
- ✅ Use `uv run <command>` to run tools in the project's virtual environment
18+
- 💡 This ensures **reproducibility** - anyone can clone the repo and run `uv sync` to get the exact same environment
1219

1320
```bash
1421
# Install uv if not already installed

docs/javascripts/lightbox.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* Simple image lightbox for documentation screenshots
3+
* Works on all screen sizes (desktop, tablet, mobile)
4+
*/
5+
6+
(function() {
7+
'use strict';
8+
9+
// Create lightbox elements
10+
function createLightbox() {
11+
const overlay = document.createElement('div');
12+
overlay.id = 'lightbox-overlay';
13+
overlay.innerHTML = `
14+
<div class="lightbox-content">
15+
<img id="lightbox-image" src="" alt="">
16+
<button id="lightbox-close" aria-label="Close lightbox">&times;</button>
17+
<div class="lightbox-caption"></div>
18+
</div>
19+
`;
20+
document.body.appendChild(overlay);
21+
22+
// Close on overlay click (not on image or caption)
23+
overlay.addEventListener('click', (e) => {
24+
if (e.target === overlay || e.target.id === 'lightbox-close') {
25+
closeLightbox();
26+
}
27+
});
28+
29+
// Close on Escape key
30+
document.addEventListener('keydown', (e) => {
31+
if (e.key === 'Escape' && overlay.classList.contains('active')) {
32+
closeLightbox();
33+
}
34+
});
35+
36+
// Support touch gestures for mobile
37+
let touchStartY = 0;
38+
overlay.addEventListener('touchstart', (e) => {
39+
touchStartY = e.touches[0].clientY;
40+
});
41+
42+
overlay.addEventListener('touchend', (e) => {
43+
const touchEndY = e.changedTouches[0].clientY;
44+
const swipeDistance = touchStartY - touchEndY;
45+
46+
// Swipe down to close (must swipe at least 100px)
47+
if (swipeDistance < -100) {
48+
closeLightbox();
49+
}
50+
});
51+
52+
return overlay;
53+
}
54+
55+
// Open lightbox with image
56+
function openLightbox(imgSrc, altText) {
57+
const overlay = document.getElementById('lightbox-overlay') || createLightbox();
58+
const lightboxImg = document.getElementById('lightbox-image');
59+
const caption = overlay.querySelector('.lightbox-caption');
60+
61+
lightboxImg.src = imgSrc;
62+
lightboxImg.alt = altText;
63+
caption.textContent = altText;
64+
65+
overlay.classList.add('active');
66+
document.body.style.overflow = 'hidden'; // Prevent scrolling
67+
}
68+
69+
// Close lightbox
70+
function closeLightbox() {
71+
const overlay = document.getElementById('lightbox-overlay');
72+
if (overlay) {
73+
overlay.classList.remove('active');
74+
document.body.style.overflow = ''; // Restore scrolling
75+
}
76+
}
77+
78+
// Make images clickable
79+
function initLightbox() {
80+
// Target all content images (screenshots)
81+
const images = document.querySelectorAll('.md-content img');
82+
83+
images.forEach(img => {
84+
// Skip if already processed
85+
if (img.classList.contains('lightbox-enabled')) return;
86+
87+
// Add class and click handler
88+
img.classList.add('lightbox-enabled');
89+
img.title = img.alt + ' (click to enlarge)';
90+
91+
img.addEventListener('click', (e) => {
92+
e.preventDefault();
93+
openLightbox(img.src, img.alt);
94+
});
95+
});
96+
}
97+
98+
// Initialize on page load
99+
document.addEventListener('DOMContentLoaded', initLightbox);
100+
101+
// Re-initialize on Material for MkDocs instant navigation
102+
if (typeof document$ !== 'undefined') {
103+
document$.subscribe(() => {
104+
initLightbox();
105+
});
106+
}
107+
})();

docs/stylesheets/extra.css

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,119 @@
162162
display: none;
163163
}
164164

165+
/* Lightbox styles - all screen sizes */
166+
/* Make images appear clickable */
167+
.md-content img.lightbox-enabled {
168+
cursor: pointer;
169+
transition: transform 0.2s ease, box-shadow 0.2s ease;
170+
}
171+
172+
/* Desktop hover effect */
173+
@media (min-width: 76.1875em) {
174+
.md-content img.lightbox-enabled:hover {
175+
transform: scale(1.02);
176+
box-shadow: 0 6px 16px rgba(0,0,0,0.2);
177+
}
178+
}
179+
180+
/* Lightbox overlay */
181+
#lightbox-overlay {
182+
display: none;
183+
position: fixed;
184+
top: 0;
185+
left: 0;
186+
width: 100%;
187+
height: 100%;
188+
background-color: rgba(0, 0, 0, 0.95);
189+
z-index: 10000;
190+
opacity: 0;
191+
transition: opacity 0.3s ease;
192+
}
193+
194+
#lightbox-overlay.active {
195+
display: flex;
196+
align-items: center;
197+
justify-content: center;
198+
opacity: 1;
199+
}
200+
201+
/* Lightbox content container */
202+
.lightbox-content {
203+
position: relative;
204+
max-width: 95%;
205+
max-height: 95%;
206+
display: flex;
207+
flex-direction: column;
208+
align-items: center;
209+
}
210+
211+
/* Lightbox image */
212+
#lightbox-image {
213+
max-width: 100%;
214+
max-height: 85vh;
215+
object-fit: contain;
216+
border-radius: 4px;
217+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
218+
}
219+
220+
/* Close button */
221+
#lightbox-close {
222+
position: absolute;
223+
top: -40px;
224+
right: 0;
225+
background: none;
226+
border: none;
227+
color: white;
228+
font-size: 40px;
229+
cursor: pointer;
230+
padding: 0;
231+
width: 40px;
232+
height: 40px;
233+
line-height: 1;
234+
opacity: 0.8;
235+
transition: opacity 0.2s ease;
236+
}
237+
238+
#lightbox-close:hover,
239+
#lightbox-close:active {
240+
opacity: 1;
241+
}
242+
243+
/* Caption */
244+
.lightbox-caption {
245+
color: white;
246+
text-align: center;
247+
margin-top: 1rem;
248+
font-size: 0.9rem;
249+
max-width: 90%;
250+
}
251+
252+
/* Mobile-specific adjustments */
253+
@media (max-width: 76.1875em) {
254+
#lightbox-overlay {
255+
background-color: rgba(0, 0, 0, 0.98);
256+
}
257+
258+
#lightbox-image {
259+
max-height: 90vh;
260+
}
261+
262+
#lightbox-close {
263+
top: 10px;
264+
right: 10px;
265+
font-size: 50px;
266+
width: 50px;
267+
height: 50px;
268+
background-color: rgba(0, 0, 0, 0.5);
269+
border-radius: 50%;
270+
}
271+
272+
.lightbox-caption {
273+
font-size: 0.8rem;
274+
padding: 0 1rem;
275+
}
276+
}
277+
165278
/* Mobile optimizations */
166279
@media (max-width: 76.1875em) {
167280
/* Smaller hero title on mobile */

mkdocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ extra_css:
5454
- stylesheets/extra.css
5555
- stylesheets/terminal.css
5656

57+
extra_javascript:
58+
- javascripts/lightbox.js
59+
5760
extra:
5861
social:
5962
- icon: fontawesome/brands/github

0 commit comments

Comments
 (0)