yefeme 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/_includes/head.html +4 -3
- data/_includes/light-box.html +61 -4
- data/assets/lightbox.js +307 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '080bfd44d21f6f9ffe2dc4f171bb50fce8ce032d3feed441444c2c30ecb77bbe'
|
|
4
|
+
data.tar.gz: 48af270410eec82cf3953c2640978f7bcf47cb4c97a09f435d7d179d3c857dde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4af6f45d6b415ee0a22b58c268573d3d9cca92b9008e6d26b26105e566af40bfb8fa3f2bc0e5e5763d029473fae8a685a802c630350cd9a4aa2dabc679c634d8
|
|
7
|
+
data.tar.gz: 491e1b847b25567d437ef9b7a479c0979b553c57aecffb562e88b08e57975effafb09424bed2fe2c31dff048ed72e343316fa8f8e93804a32c7805f41ba089c0
|
data/README.md
CHANGED
|
@@ -65,6 +65,7 @@ Each document in `_shoots` should use the `shoot` layout and provide a title, da
|
|
|
65
65
|
layout: shoot
|
|
66
66
|
title: Marseille
|
|
67
67
|
date: 2025-11-25
|
|
68
|
+
cover: image-id-two
|
|
68
69
|
images:
|
|
69
70
|
- id: image-id-one
|
|
70
71
|
width: 1366
|
|
@@ -77,7 +78,9 @@ images:
|
|
|
77
78
|
---
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
The
|
|
81
|
+
The optional `cover` field selects the Cloudflare image ID used for the shoot's link preview (`og:image`). It defaults to the first image and uses the configured `xl` variant.
|
|
82
|
+
|
|
83
|
+
The theme decodes each BlurHash in the browser and displays it as an immediate placeholder behind the full thumbnail. Selecting a thumbnail opens the shoot in a full-screen lightbox with button, keyboard, and horizontal swipe navigation. Tap the backdrop or drag vertically to close; double-tap or pinch an image to zoom, then drag to pan. Scalar image IDs remain supported for existing sites, but do not provide intrinsic sizing or placeholder information.
|
|
81
84
|
|
|
82
85
|
## Contributing
|
|
83
86
|
|
|
@@ -87,6 +90,10 @@ Bug reports and pull requests are welcome. This project is intended to be a safe
|
|
|
87
90
|
|
|
88
91
|
To set up your environment to develop this theme, run `bundle install`.
|
|
89
92
|
|
|
93
|
+
Check cover metadata with `bundle exec ruby test_cover.rb`.
|
|
94
|
+
|
|
95
|
+
Check lightbox gesture handling with `node --test test_lightbox.js`.
|
|
96
|
+
|
|
90
97
|
Your theme is setup just like a normal Jekyll site! To test your theme, run `bundle exec jekyll serve` and open your browser at `http://localhost:4000`. This starts a Jekyll server using your theme. Add pages, documents, data, etc. like normal to test your theme's contents. As you make modifications to your theme and to your content, your site will regenerate and you should see the changes in the browser after a refresh, just like normal.
|
|
91
98
|
|
|
92
99
|
Gem releases are built from a clean checkout and include the tracked theme layouts, includes, Sass, and browser assets selected by `yefeme.gemspec`. The release workflow verifies the package and rejects site content from `_posts` or `_shoots`.
|
data/_includes/head.html
CHANGED
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
<meta name="twitter:site" content="@{{ site.twitter_username }}">
|
|
25
25
|
{% endif %}
|
|
26
26
|
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
{% assign cover = page.cover | default: page.images.first.id | default: page.images.first %}
|
|
28
|
+
{% if cover and site.cf_images_account_hash and site.cf_images_variants.xl %}
|
|
29
|
+
<meta property="og:image" content="https://imagedelivery.net/{{ site.cf_images_account_hash | escape }}/{{ cover | escape }}/{{ site.cf_images_variants.xl | escape }}">
|
|
30
|
+
{% endif %}
|
|
30
31
|
|
|
31
32
|
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
|
|
32
33
|
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
|
data/_includes/light-box.html
CHANGED
|
@@ -37,10 +37,17 @@
|
|
|
37
37
|
justify-content: center;
|
|
38
38
|
padding: 0;
|
|
39
39
|
position: relative;
|
|
40
|
+
transition: opacity 0.25s, visibility 0.25s;
|
|
40
41
|
width: 2rem;
|
|
41
42
|
z-index: 2;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
.modal.zoomed button {
|
|
46
|
+
opacity: 0;
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
visibility: hidden;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
button svg {
|
|
45
52
|
display: block;
|
|
46
53
|
fill: none;
|
|
@@ -91,8 +98,15 @@
|
|
|
91
98
|
display: flex;
|
|
92
99
|
flex: 0 0 100%;
|
|
93
100
|
justify-content: center;
|
|
101
|
+
opacity: var(--drag-opacity, 1);
|
|
94
102
|
position: relative;
|
|
95
103
|
scroll-snap-align: center;
|
|
104
|
+
transition: opacity 0.33s ease;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.slide.zooming,
|
|
108
|
+
.slide.zooming img {
|
|
109
|
+
touch-action: none;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
.slide img {
|
|
@@ -106,20 +120,53 @@
|
|
|
106
120
|
opacity: var(--resolution-opacity, 1);
|
|
107
121
|
position: absolute;
|
|
108
122
|
top: 50%;
|
|
109
|
-
touch-action:
|
|
110
|
-
transform:
|
|
111
|
-
|
|
123
|
+
touch-action: pan-x;
|
|
124
|
+
transform:
|
|
125
|
+
translate(-50%, calc(-50% - 1.5rem))
|
|
126
|
+
translateY(var(--drag-y, 0px));
|
|
127
|
+
transition: opacity 0.3s ease, transform 0.33s ease;
|
|
112
128
|
user-select: none;
|
|
113
129
|
width: auto;
|
|
114
130
|
-webkit-user-drag: none;
|
|
115
131
|
}
|
|
116
132
|
|
|
133
|
+
.slide.dragging,
|
|
134
|
+
.slide.dragging img {
|
|
135
|
+
transition: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.slide.settling {
|
|
139
|
+
transition: opacity 0.33s ease-out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.slide.settling img {
|
|
143
|
+
transition: opacity 0.3s ease, transform 0.33s ease-out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.slide.zooming img {
|
|
147
|
+
transform:
|
|
148
|
+
translate(-50%, calc(-50% - 1.5rem))
|
|
149
|
+
translate(var(--pan-x, 0px), var(--pan-y, 0px))
|
|
150
|
+
scale(var(--scale, 1));
|
|
151
|
+
transition: none;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.slide.zooming.zooming-paused img {
|
|
155
|
+
transition: opacity 0.3s ease, transform 0.33s ease;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.slide.zooming.panning img {
|
|
159
|
+
transition: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
117
162
|
.slide img.high-resolution {
|
|
118
163
|
--resolution-opacity: 0;
|
|
164
|
+
pointer-events: none;
|
|
119
165
|
}
|
|
120
166
|
|
|
121
167
|
.slide img.high-resolution.loaded {
|
|
122
168
|
--resolution-opacity: 1;
|
|
169
|
+
pointer-events: auto;
|
|
123
170
|
}
|
|
124
171
|
|
|
125
172
|
.status {
|
|
@@ -143,7 +190,16 @@
|
|
|
143
190
|
max-height: 98dvh;
|
|
144
191
|
max-width: calc(98vw - 6rem);
|
|
145
192
|
max-width: calc(98dvw - 6rem);
|
|
146
|
-
transform:
|
|
193
|
+
transform:
|
|
194
|
+
translate(-50%, -50%)
|
|
195
|
+
translateY(var(--drag-y, 0px));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.slide.zooming img {
|
|
199
|
+
transform:
|
|
200
|
+
translate(-50%, -50%)
|
|
201
|
+
translate(var(--pan-x, 0px), var(--pan-y, 0px))
|
|
202
|
+
scale(var(--scale, 1));
|
|
147
203
|
}
|
|
148
204
|
}
|
|
149
205
|
|
|
@@ -164,6 +220,7 @@
|
|
|
164
220
|
|
|
165
221
|
@media (prefers-reduced-motion: reduce) {
|
|
166
222
|
.modal,
|
|
223
|
+
.slide,
|
|
167
224
|
.slide img {
|
|
168
225
|
transition: none;
|
|
169
226
|
}
|
data/assets/lightbox.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
+
var DOUBLE_TAP_MAX_MS = 300;
|
|
5
|
+
var DOUBLE_TAP_MAX_DISTANCE = 30;
|
|
6
|
+
var DOUBLE_TAP_ZOOM_SCALE = 2;
|
|
7
|
+
var TAP_MAX_DISTANCE = 10;
|
|
8
|
+
|
|
4
9
|
if (!window.customElements || window.customElements.get("shoot-lightbox")) return;
|
|
5
10
|
|
|
6
11
|
function getImage(photo) {
|
|
7
12
|
return photo.querySelector("img");
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
function distanceBetween(first, second) {
|
|
16
|
+
return Math.hypot(second.clientX - first.clientX, second.clientY - first.clientY);
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
class ShootLightbox extends HTMLElement {
|
|
11
20
|
constructor() {
|
|
12
21
|
super();
|
|
@@ -25,6 +34,14 @@
|
|
|
25
34
|
if (!this.photos.length) return;
|
|
26
35
|
|
|
27
36
|
this.currentPhoto = 0;
|
|
37
|
+
this.maxTouches = 0;
|
|
38
|
+
this.touchStarts = [];
|
|
39
|
+
this.zoomScale = 1;
|
|
40
|
+
this.panX = 0;
|
|
41
|
+
this.panY = 0;
|
|
42
|
+
this.startPan = { x: null, y: null };
|
|
43
|
+
this.lastTap = null;
|
|
44
|
+
|
|
28
45
|
this.modal = this.shadowRoot.querySelector(".modal");
|
|
29
46
|
this.track = this.shadowRoot.querySelector(".track");
|
|
30
47
|
this.previousButton = this.shadowRoot.querySelector(".previous");
|
|
@@ -49,7 +66,7 @@
|
|
|
49
66
|
image.loading = "eager";
|
|
50
67
|
image.decoding = "async";
|
|
51
68
|
image.draggable = false;
|
|
52
|
-
|
|
69
|
+
this.bindImageEvents(image);
|
|
53
70
|
|
|
54
71
|
slide.appendChild(image);
|
|
55
72
|
this.track.appendChild(slide);
|
|
@@ -58,6 +75,14 @@
|
|
|
58
75
|
this.slides = Array.from(this.track.querySelectorAll(".slide"));
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
bindImageEvents(image) {
|
|
79
|
+
image.addEventListener("click", (event) => event.stopPropagation());
|
|
80
|
+
image.addEventListener("touchstart", this.onTouchStart, { passive: false });
|
|
81
|
+
image.addEventListener("touchmove", this.onTouchMove, { passive: false });
|
|
82
|
+
image.addEventListener("touchend", this.onTouchEnd, { passive: false });
|
|
83
|
+
image.addEventListener("touchcancel", this.onTouchCancel, { passive: false });
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
loadHighResolutionImage(slide, photo) {
|
|
62
87
|
if (!slide || !photo.href || slide.dataset.highResolutionRequested) return;
|
|
63
88
|
|
|
@@ -80,7 +105,7 @@
|
|
|
80
105
|
image.loading = "eager";
|
|
81
106
|
image.decoding = "async";
|
|
82
107
|
image.draggable = false;
|
|
83
|
-
|
|
108
|
+
this.bindImageEvents(image);
|
|
84
109
|
|
|
85
110
|
var reveal = () => {
|
|
86
111
|
if (revealed) return;
|
|
@@ -111,6 +136,7 @@
|
|
|
111
136
|
this.previousButton.addEventListener("click", this.previous);
|
|
112
137
|
this.nextButton.addEventListener("click", this.next);
|
|
113
138
|
this.closeButton.addEventListener("click", this.close);
|
|
139
|
+
this.modal.addEventListener("click", this.close);
|
|
114
140
|
this.track.addEventListener("scroll", this.onScroll, { passive: true });
|
|
115
141
|
document.addEventListener("keydown", this.onKeydown);
|
|
116
142
|
window.addEventListener("resize", this.onResize, { passive: true });
|
|
@@ -134,11 +160,20 @@
|
|
|
134
160
|
event.stopPropagation();
|
|
135
161
|
}
|
|
136
162
|
|
|
137
|
-
if (this.modal.classList.contains("hidden")) return;
|
|
163
|
+
if (this.modal.classList.contains("hidden") || this.zoomScale !== 1) return;
|
|
164
|
+
|
|
165
|
+
var slide = this.getCurrentSlide();
|
|
138
166
|
|
|
139
167
|
this.modal.classList.add("hidden");
|
|
140
168
|
this.modal.setAttribute("aria-hidden", "true");
|
|
141
169
|
document.documentElement.classList.remove("lightbox-open");
|
|
170
|
+
this.lastTap = null;
|
|
171
|
+
|
|
172
|
+
if (slide && slide.classList.contains("settling")) {
|
|
173
|
+
this.scheduleGestureReset(slide);
|
|
174
|
+
} else {
|
|
175
|
+
this.resetGestureState(slide);
|
|
176
|
+
}
|
|
142
177
|
|
|
143
178
|
if (this.previouslyFocused) this.previouslyFocused.focus({ preventScroll: true });
|
|
144
179
|
};
|
|
@@ -163,7 +198,18 @@
|
|
|
163
198
|
return this.currentPhoto >= this.photos.length - 1;
|
|
164
199
|
}
|
|
165
200
|
|
|
201
|
+
getCurrentSlide() {
|
|
202
|
+
return this.slides[this.currentPhoto];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getCurrentImage() {
|
|
206
|
+
var slide = this.getCurrentSlide();
|
|
207
|
+
return slide && (slide.querySelector(".high-resolution.loaded") || slide.querySelector("img"));
|
|
208
|
+
}
|
|
209
|
+
|
|
166
210
|
updateCurrentPhoto(index, behavior) {
|
|
211
|
+
if (this.zoomScale !== 1) this.resetZoomState(this.getCurrentSlide());
|
|
212
|
+
|
|
167
213
|
this.currentPhoto = Math.max(0, Math.min(this.photos.length - 1, index));
|
|
168
214
|
this.track.scrollTo({
|
|
169
215
|
left: this.currentPhoto * this.track.clientWidth,
|
|
@@ -180,7 +226,7 @@
|
|
|
180
226
|
}
|
|
181
227
|
|
|
182
228
|
onKeydown = (event) => {
|
|
183
|
-
if (this.modal.classList.contains("hidden")) return;
|
|
229
|
+
if (this.modal.classList.contains("hidden") || this.zoomScale !== 1) return;
|
|
184
230
|
|
|
185
231
|
if (event.key === "ArrowLeft") this.previous(event);
|
|
186
232
|
if (event.key === "ArrowRight") this.next(event);
|
|
@@ -191,10 +237,16 @@
|
|
|
191
237
|
window.clearTimeout(this.scrollSyncTimeout);
|
|
192
238
|
this.scrollSyncTimeout = window.setTimeout(() => {
|
|
193
239
|
var width = this.track.clientWidth || window.innerWidth;
|
|
194
|
-
|
|
240
|
+
var nextPhoto = Math.max(
|
|
195
241
|
0,
|
|
196
242
|
Math.min(this.photos.length - 1, Math.round(this.track.scrollLeft / width))
|
|
197
243
|
);
|
|
244
|
+
|
|
245
|
+
if (nextPhoto !== this.currentPhoto && this.zoomScale !== 1) {
|
|
246
|
+
this.resetZoomState(this.getCurrentSlide());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.currentPhoto = nextPhoto;
|
|
198
250
|
this.updateControls();
|
|
199
251
|
this.loadHighResolutionImage(this.slides[this.currentPhoto], this.photos[this.currentPhoto]);
|
|
200
252
|
}, 100);
|
|
@@ -204,6 +256,256 @@
|
|
|
204
256
|
if (this.modal.classList.contains("hidden")) return;
|
|
205
257
|
this.updateCurrentPhoto(this.currentPhoto, "auto");
|
|
206
258
|
};
|
|
259
|
+
|
|
260
|
+
onTouchStart = (event) => {
|
|
261
|
+
if (this.getCurrentSlide().classList.contains("settling")) this.resetGestureState();
|
|
262
|
+
|
|
263
|
+
this.touchStarts = Array.from(event.targetTouches);
|
|
264
|
+
this.maxTouches = Math.max(this.maxTouches, event.targetTouches.length);
|
|
265
|
+
this.startPan = { x: null, y: null };
|
|
266
|
+
|
|
267
|
+
if (this.touchStarts.length === 1) this.gestureDirection = undefined;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
onTouchMove = (event) => {
|
|
271
|
+
this.maxTouches = Math.max(this.maxTouches, event.targetTouches.length);
|
|
272
|
+
|
|
273
|
+
if (this.maxTouches >= 2) {
|
|
274
|
+
this.pinchToZoom(event);
|
|
275
|
+
} else if (this.zoomScale === 1) {
|
|
276
|
+
this.maybeApplyVerticalDrag(event);
|
|
277
|
+
} else {
|
|
278
|
+
this.panZoomedImage(event);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
onTouchEnd = (event) => {
|
|
283
|
+
if (event.targetTouches.length !== 0) return;
|
|
284
|
+
|
|
285
|
+
var slide = this.getCurrentSlide();
|
|
286
|
+
|
|
287
|
+
if (this.isDoubleTap(event)) {
|
|
288
|
+
if (this.zoomScale === 1) {
|
|
289
|
+
this.doubleTapZoomIn(event);
|
|
290
|
+
} else {
|
|
291
|
+
this.resetZoomState(slide);
|
|
292
|
+
}
|
|
293
|
+
} else if (this.zoomScale === 1) {
|
|
294
|
+
if (this.maxTouches === 1) this.finishVerticalDrag(event);
|
|
295
|
+
if (this.maxTouches >= 2) this.resetZoomState(slide);
|
|
296
|
+
} else if (this.maxTouches === 1) {
|
|
297
|
+
this.finishPanning();
|
|
298
|
+
} else if (this.maxTouches >= 2) {
|
|
299
|
+
this.pauseZooming();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.maxTouches = 0;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
onTouchCancel = () => {
|
|
306
|
+
this.lastTap = null;
|
|
307
|
+
this.maxTouches = 0;
|
|
308
|
+
|
|
309
|
+
if (this.zoomScale === 1) {
|
|
310
|
+
this.resetGestureState();
|
|
311
|
+
} else {
|
|
312
|
+
this.pauseZooming();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
isDoubleTap(event) {
|
|
317
|
+
var touch = event.changedTouches[0];
|
|
318
|
+
var startedAsTap = touch &&
|
|
319
|
+
this.maxTouches === 1 &&
|
|
320
|
+
!this.gestureDirection &&
|
|
321
|
+
this.touchStarts[0] &&
|
|
322
|
+
distanceBetween(this.touchStarts[0], touch) < TAP_MAX_DISTANCE;
|
|
323
|
+
|
|
324
|
+
if (!startedAsTap) {
|
|
325
|
+
this.lastTap = null;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
var isDoubleTap = this.lastTap &&
|
|
330
|
+
event.timeStamp - this.lastTap.time < DOUBLE_TAP_MAX_MS &&
|
|
331
|
+
distanceBetween(this.lastTap.touch, touch) < DOUBLE_TAP_MAX_DISTANCE;
|
|
332
|
+
|
|
333
|
+
this.lastTap = isDoubleTap ? null : { time: event.timeStamp, touch: touch };
|
|
334
|
+
return Boolean(isDoubleTap);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
doubleTapZoomIn(event) {
|
|
338
|
+
this.resetGestureState();
|
|
339
|
+
|
|
340
|
+
var slide = this.getCurrentSlide();
|
|
341
|
+
var image = this.getCurrentImage();
|
|
342
|
+
var rect = image.getBoundingClientRect();
|
|
343
|
+
|
|
344
|
+
this.zoomScale = DOUBLE_TAP_ZOOM_SCALE;
|
|
345
|
+
this.panX = (1 - DOUBLE_TAP_ZOOM_SCALE) *
|
|
346
|
+
((event.changedTouches[0].clientX - rect.left) - image.offsetWidth / 2);
|
|
347
|
+
this.panY = (1 - DOUBLE_TAP_ZOOM_SCALE) *
|
|
348
|
+
((event.changedTouches[0].clientY - rect.top) - image.offsetHeight / 2);
|
|
349
|
+
|
|
350
|
+
slide.classList.add("zooming", "zooming-paused");
|
|
351
|
+
this.modal.classList.add("zoomed");
|
|
352
|
+
this.applyZoomStyles(slide);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
pinchToZoom(event) {
|
|
356
|
+
var slide = this.getCurrentSlide();
|
|
357
|
+
if (slide.classList.contains("dragging") || event.targetTouches.length < 2) return;
|
|
358
|
+
|
|
359
|
+
var image = this.getCurrentImage();
|
|
360
|
+
var rect = image.getBoundingClientRect();
|
|
361
|
+
var firstTouch = event.targetTouches[0];
|
|
362
|
+
var secondTouch = event.targetTouches[1];
|
|
363
|
+
var distance = distanceBetween(firstTouch, secondTouch);
|
|
364
|
+
var previousScale = this.zoomScale;
|
|
365
|
+
|
|
366
|
+
event.preventDefault();
|
|
367
|
+
slide.classList.add("zooming");
|
|
368
|
+
slide.classList.remove("zooming-paused");
|
|
369
|
+
this.modal.classList.add("zoomed");
|
|
370
|
+
|
|
371
|
+
if (this.startDistance == null) {
|
|
372
|
+
this.startDistance = distanceBetween(this.touchStarts[0], this.touchStarts[1]);
|
|
373
|
+
this.startScale = this.zoomScale;
|
|
374
|
+
this.zoomAnchorPhotoX = ((firstTouch.clientX + secondTouch.clientX) / 2 - rect.left) / previousScale;
|
|
375
|
+
this.zoomAnchorPhotoY = ((firstTouch.clientY + secondTouch.clientY) / 2 - rect.top) / previousScale;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.zoomScale = Math.max(1, this.startScale * (distance / this.startDistance));
|
|
379
|
+
|
|
380
|
+
var midpointX = (firstTouch.clientX + secondTouch.clientX) / 2;
|
|
381
|
+
var midpointY = (firstTouch.clientY + secondTouch.clientY) / 2;
|
|
382
|
+
|
|
383
|
+
this.panX += (midpointX - rect.left) - this.zoomScale * this.zoomAnchorPhotoX +
|
|
384
|
+
(image.offsetWidth / 2) * (this.zoomScale - previousScale);
|
|
385
|
+
this.panY += (midpointY - rect.top) - this.zoomScale * this.zoomAnchorPhotoY +
|
|
386
|
+
(image.offsetHeight / 2) * (this.zoomScale - previousScale);
|
|
387
|
+
|
|
388
|
+
this.applyZoomStyles(slide);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
panZoomedImage(event) {
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
|
|
394
|
+
var slide = this.getCurrentSlide();
|
|
395
|
+
var image = this.getCurrentImage();
|
|
396
|
+
var rect = image.getBoundingClientRect();
|
|
397
|
+
var touch = event.targetTouches[0];
|
|
398
|
+
|
|
399
|
+
if (this.startPan.x == null || this.startPan.y == null) {
|
|
400
|
+
slide.classList.add("panning");
|
|
401
|
+
this.startPan = {
|
|
402
|
+
x: (touch.clientX - rect.left) / this.zoomScale,
|
|
403
|
+
y: (touch.clientY - rect.top) / this.zoomScale
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.panX += (touch.clientX - rect.left) - this.startPan.x * this.zoomScale;
|
|
408
|
+
this.panY += (touch.clientY - rect.top) - this.startPan.y * this.zoomScale;
|
|
409
|
+
this.applyZoomStyles(slide);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
applyZoomStyles(slide) {
|
|
413
|
+
slide.style.setProperty("--scale", this.zoomScale);
|
|
414
|
+
slide.style.setProperty("--pan-x", this.panX + "px");
|
|
415
|
+
slide.style.setProperty("--pan-y", this.panY + "px");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
maybeApplyVerticalDrag(event) {
|
|
419
|
+
var touch = event.targetTouches[0];
|
|
420
|
+
if (!touch || !this.touchStarts[0]) return;
|
|
421
|
+
|
|
422
|
+
if (!this.gestureDirection && distanceBetween(this.touchStarts[0], touch) >= TAP_MAX_DISTANCE) {
|
|
423
|
+
this.gestureDirection = Math.abs(touch.clientX - this.touchStarts[0].clientX) >
|
|
424
|
+
Math.abs(touch.clientY - this.touchStarts[0].clientY) ? "horizontal" : "vertical";
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (this.gestureDirection !== "vertical") return;
|
|
428
|
+
|
|
429
|
+
event.preventDefault();
|
|
430
|
+
this.track.style.scrollSnapType = "none";
|
|
431
|
+
this.track.style.overflowX = "hidden";
|
|
432
|
+
this.applyVerticalDrag(touch.clientY - this.touchStarts[0].clientY);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
applyVerticalDrag(distance) {
|
|
436
|
+
var slide = this.getCurrentSlide();
|
|
437
|
+
var maxDrag = window.innerHeight * 0.5;
|
|
438
|
+
var opacity = Math.max(0, Math.min(1, 1 - Math.abs(distance) / maxDrag));
|
|
439
|
+
|
|
440
|
+
slide.classList.add("dragging");
|
|
441
|
+
slide.style.setProperty("--drag-y", distance + "px");
|
|
442
|
+
slide.style.setProperty("--drag-opacity", opacity);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
finishVerticalDrag(event) {
|
|
446
|
+
if (this.gestureDirection !== "vertical") return;
|
|
447
|
+
|
|
448
|
+
var slide = this.getCurrentSlide();
|
|
449
|
+
var distance = event.changedTouches[0].clientY - this.touchStarts[0].clientY;
|
|
450
|
+
var threshold = Math.min(window.innerHeight * 0.3, 150);
|
|
451
|
+
|
|
452
|
+
slide.classList.add("settling");
|
|
453
|
+
|
|
454
|
+
if (Math.abs(distance) < threshold) {
|
|
455
|
+
slide.style.setProperty("--drag-y", "0px");
|
|
456
|
+
slide.style.setProperty("--drag-opacity", "1");
|
|
457
|
+
this.scheduleGestureReset(slide);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
slide.style.setProperty("--drag-y", (distance > 0 ? distance + 200 : distance - 200) + "px");
|
|
462
|
+
slide.style.setProperty("--drag-opacity", "0");
|
|
463
|
+
this.close();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
finishPanning() {
|
|
467
|
+
this.startPan = { x: null, y: null };
|
|
468
|
+
this.getCurrentSlide().classList.remove("panning");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
pauseZooming() {
|
|
472
|
+
this.startDistance = null;
|
|
473
|
+
this.getCurrentSlide().classList.add("zooming-paused");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
resetZoomState(slide) {
|
|
477
|
+
if (!slide) return;
|
|
478
|
+
|
|
479
|
+
this.resetGestureState(slide);
|
|
480
|
+
slide.classList.remove("zooming", "zooming-paused", "panning");
|
|
481
|
+
slide.style.removeProperty("--scale");
|
|
482
|
+
slide.style.removeProperty("--pan-x");
|
|
483
|
+
slide.style.removeProperty("--pan-y");
|
|
484
|
+
this.modal.classList.remove("zoomed");
|
|
485
|
+
this.zoomScale = 1;
|
|
486
|
+
this.panX = 0;
|
|
487
|
+
this.panY = 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
scheduleGestureReset(slide) {
|
|
491
|
+
window.clearTimeout(this.gestureResetTimeout);
|
|
492
|
+
this.gestureResetTimeout = window.setTimeout(() => this.resetGestureState(slide), 350);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
resetGestureState = (slide) => {
|
|
496
|
+
slide = slide || this.getCurrentSlide();
|
|
497
|
+
if (!slide) return;
|
|
498
|
+
|
|
499
|
+
window.clearTimeout(this.gestureResetTimeout);
|
|
500
|
+
slide.classList.remove("dragging", "settling");
|
|
501
|
+
slide.style.removeProperty("--drag-y");
|
|
502
|
+
slide.style.removeProperty("--drag-opacity");
|
|
503
|
+
this.track.style.scrollSnapType = "";
|
|
504
|
+
this.track.style.overflowX = "";
|
|
505
|
+
this.maxTouches = 0;
|
|
506
|
+
this.startDistance = null;
|
|
507
|
+
this.gestureDirection = undefined;
|
|
508
|
+
};
|
|
207
509
|
}
|
|
208
510
|
|
|
209
511
|
window.customElements.define("shoot-lightbox", ShootLightbox);
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yefeme
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yefim Vedernikoff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|