Compare commits
2 Commits
1ee9c788d8
...
3c7cb11f02
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c7cb11f02 | |||
| 696a94288d |
@ -4,9 +4,11 @@
|
|||||||
.gallery-nav:active,
|
.gallery-nav:active,
|
||||||
#gallery-modal-content,
|
#gallery-modal-content,
|
||||||
#gallery-close,
|
#gallery-close,
|
||||||
#gallery-download {
|
#gallery-download,
|
||||||
-webkit-tap-highlight-color: transparent; /* Disable blue highlight on Android/iOS/Chrome/Safari */
|
#gallery-info,
|
||||||
outline: none; /* Disable outline on click/focus */
|
#gallery-play {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property --play-progress {
|
@property --play-progress {
|
||||||
@ -15,160 +17,196 @@
|
|||||||
initial-value: 0deg;
|
initial-value: 0deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gallery Modal Styles */
|
|
||||||
#gallery-modal {
|
#gallery-modal {
|
||||||
display: none; /* Hidden by default */
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
top: 0;
|
inset: 0;
|
||||||
left: 0;
|
background-color: rgba(0, 0, 0, 0.82);
|
||||||
width: 100%;
|
background-image:
|
||||||
height: 100vh;
|
radial-gradient(circle at 50% 15%, rgba(42, 42, 42, 0.52), rgba(8, 8, 8, 0.95) 56%, #000 100%);
|
||||||
background-color: rgba(0,0,0,1);
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-stage {
|
||||||
|
position: absolute;
|
||||||
|
inset: 82px 22px 96px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-modal-content {
|
#gallery-modal-content {
|
||||||
max-width: 95%;
|
position: absolute;
|
||||||
max-height: 95vh;
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: min(96vw, calc(100vw - 80px));
|
||||||
|
max-height: calc(100vh - 190px);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: block;
|
display: block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 26px 60px rgba(0, 0, 0, 0.58);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Close Button */
|
.gallery-top-bar {
|
||||||
#gallery-close {
|
position: absolute;
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 120;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gallery-close:hover {
|
|
||||||
background: rgba(40, 40, 40, 0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loader for gallery */
|
|
||||||
#loader-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
right: 0;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 9999; /* Ensure it's on top */
|
gap: 12px;
|
||||||
pointer-events: none; /* Allow interacting with close button while loader shows */
|
padding: 14px 18px;
|
||||||
|
z-index: 130;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-loader {
|
.gallery-actions-left,
|
||||||
border: 8px solid #f3f3f3;
|
.gallery-actions-right {
|
||||||
border-top: 8px solid #525252;
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-control {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 48px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 30px;
|
border: 1px solid rgba(255, 255, 255, 0.27);
|
||||||
height: 30px;
|
background: rgba(0, 0, 0, 0.56);
|
||||||
animation: spin 3s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keyframes for spinner animation */
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-nav {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 3rem;
|
|
||||||
color: white;
|
|
||||||
background: rgba(0,0,0,0.1);
|
|
||||||
border: none;
|
|
||||||
padding: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
|
||||||
transition: opacity 0.4s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-prev { left: 20px; }
|
|
||||||
.gallery-next { right: 20px; }
|
|
||||||
|
|
||||||
.gallery-nav.nav-hidden { opacity: 0; }
|
|
||||||
|
|
||||||
#gallery-info {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
left: 20px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: grid;
|
display: inline-flex;
|
||||||
place-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 120;
|
z-index: 132;
|
||||||
font-size: 1rem;
|
font-size: 1.45rem;
|
||||||
}
|
font-weight: 500;
|
||||||
|
|
||||||
#gallery-download {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
left: 64px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 120;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
text-decoration: none;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-download:hover {
|
.gallery-control:hover,
|
||||||
background: rgba(40, 40, 40, 0.85);
|
.gallery-control:focus-visible {
|
||||||
|
background: rgba(38, 38, 38, 0.88);
|
||||||
|
border-color: rgba(255, 255, 255, 0.65);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-info.loading {
|
#gallery-info.loading {
|
||||||
opacity: 0.65;
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-info {
|
||||||
|
font-size: 1.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-download,
|
||||||
|
#gallery-close {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader-container {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 3500;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-loader {
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.22);
|
||||||
|
border-top: 4px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
animation: gallery-spin 0.9s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gallery-spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
min-width: 56px;
|
||||||
|
min-height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0, 0, 0, 0.54);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 120;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
transition: opacity 0.25s ease, transform 0.25s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav:hover,
|
||||||
|
.gallery-nav:focus-visible {
|
||||||
|
background: rgba(30, 30, 30, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-prev {
|
||||||
|
left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-next {
|
||||||
|
right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-next.is-close {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #fff;
|
||||||
|
color: #101010;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav.nav-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50%) scale(0.97);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-exif {
|
#gallery-exif {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 60px;
|
top: 68px;
|
||||||
left: 20px;
|
left: 18px;
|
||||||
background: rgba(0, 0, 0, 0.82);
|
background: rgba(0, 0, 0, 0.85);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.75rem 0.85rem;
|
padding: 0.85rem 0.92rem;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||||
max-width: 320px;
|
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.5);
|
||||||
max-height: 50vh;
|
width: min(360px, calc(100vw - 36px));
|
||||||
|
max-height: calc(100vh - 228px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 120;
|
z-index: 140;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-exif.open {
|
#gallery-exif.open {
|
||||||
@ -179,9 +217,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.82rem;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 8px;
|
||||||
line-height: 1.3;
|
line-height: 1.28;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exif-row:last-child {
|
.exif-row:last-child {
|
||||||
@ -190,76 +228,127 @@
|
|||||||
|
|
||||||
.exif-key {
|
.exif-key {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #e5e5e5;
|
color: rgba(255, 255, 255, 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
.exif-value {
|
.exif-value {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.98);
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exif-placeholder {
|
.exif-placeholder {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #d0d0d0;
|
color: rgba(255, 255, 255, 0.76);
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-filename {
|
.gallery-meta-bar {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 16px;
|
||||||
left: 50%;
|
left: 18px;
|
||||||
transform: translateX(-50%);
|
right: 18px;
|
||||||
color: #fff;
|
display: flex;
|
||||||
background: rgba(0, 0, 0, 0.55);
|
align-items: center;
|
||||||
padding: 0.4rem 0.75rem;
|
justify-content: space-between;
|
||||||
border-radius: 6px;
|
gap: 10px;
|
||||||
font-size: 0.95rem;
|
z-index: 120;
|
||||||
max-width: 90%;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
z-index: 20;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-play {
|
#gallery-filename,
|
||||||
position: fixed;
|
#gallery-position {
|
||||||
bottom: 20px;
|
|
||||||
right: 20px;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: grid;
|
background: rgba(0, 0, 0, 0.6);
|
||||||
place-items: center;
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||||
cursor: pointer;
|
border-radius: 8px;
|
||||||
z-index: 120;
|
padding: 0.45rem 0.72rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
text-align: left;
|
||||||
|
overflow: hidden;
|
||||||
|
--filename-progress: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 255, 255, 0.95) 0%,
|
||||||
|
rgba(255, 255, 255, 0.95) var(--filename-progress),
|
||||||
|
rgba(0, 0, 0, 0.62) var(--filename-progress),
|
||||||
|
rgba(0, 0, 0, 0.62) 100%
|
||||||
|
);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename .filename-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename::after {
|
||||||
|
content: attr(data-filename);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.45rem 0.72rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: inset(0 calc(100% - var(--filename-progress)) 0 0);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #0a0a0a;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-position {
|
||||||
|
min-width: 72px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-play {
|
||||||
|
position: relative;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
line-height: 1;
|
font-weight: 600;
|
||||||
padding: 0 0 0 2px; /* optical compensation for ▶ glyph whitespace */
|
|
||||||
--autoplay-duration: 5s;
|
--autoplay-duration: 5s;
|
||||||
--play-progress: 0deg;
|
--play-progress: 0deg;
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-play.playing::after {
|
#gallery-play.playing::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -8px;
|
inset: -4px;
|
||||||
border-radius: 50%;
|
border-radius: 999px;
|
||||||
background:
|
background:
|
||||||
conic-gradient(
|
conic-gradient(
|
||||||
rgba(255, 255, 255, 0.9) 0deg,
|
rgba(255, 255, 255, 0.96) 0deg,
|
||||||
rgba(255, 255, 255, 0.9) var(--play-progress, 0deg),
|
rgba(255, 255, 255, 0.96) var(--play-progress, 0deg),
|
||||||
rgba(255, 255, 255, 0.2) var(--play-progress, 0deg),
|
rgba(255, 255, 255, 0.2) var(--play-progress, 0deg),
|
||||||
rgba(255, 255, 255, 0.2) 360deg
|
rgba(255, 255, 255, 0.2) 360deg
|
||||||
);
|
);
|
||||||
mask: radial-gradient(circle at center, transparent 60%, #000 70%);
|
mask: radial-gradient(ellipse at center, transparent 67%, #000 74%);
|
||||||
animation: none;
|
animation: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#gallery-play.playing.playing-anim::after {
|
#gallery-play.playing.playing-anim::after {
|
||||||
@ -267,6 +356,186 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gallery-play-fill {
|
@keyframes gallery-play-fill {
|
||||||
from { --play-progress: 0deg; }
|
from {
|
||||||
to { --play-progress: 360deg; }
|
--play-progress: 0deg;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
--play-progress: 360deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 860px) {
|
||||||
|
#gallery-stage {
|
||||||
|
inset: 74px 10px 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-modal-content {
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 210px);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-top-bar {
|
||||||
|
padding: 10px;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-actions-left,
|
||||||
|
.gallery-actions-right {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-control {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
min-width: 42px;
|
||||||
|
min-height: 42px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav {
|
||||||
|
top: auto;
|
||||||
|
bottom: 62px;
|
||||||
|
transform: none;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 48px;
|
||||||
|
font-size: 1.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-prev {
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-next {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav.nav-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-exif {
|
||||||
|
top: 58px;
|
||||||
|
left: 10px;
|
||||||
|
width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 224px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-meta-bar {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename,
|
||||||
|
#gallery-position {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
padding: 0.42rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-play {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-info {
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-download,
|
||||||
|
#gallery-close {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 520px) and (orientation: landscape) {
|
||||||
|
#gallery-stage {
|
||||||
|
inset: 44px 10px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-modal-content {
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
max-height: calc(100vh - 112px);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-top-bar {
|
||||||
|
padding: 8px 10px;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-actions-left,
|
||||||
|
.gallery-actions-right {
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-control {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
min-width: 38px;
|
||||||
|
min-height: 38px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-play {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-info {
|
||||||
|
font-size: 1.22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-download,
|
||||||
|
#gallery-close {
|
||||||
|
font-size: 1.18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav {
|
||||||
|
top: 50%;
|
||||||
|
bottom: auto;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-prev {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-next {
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-nav.nav-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50%) scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-meta-bar {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-filename,
|
||||||
|
#gallery-position {
|
||||||
|
padding: 0.32rem 0.52rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gallery-exif {
|
||||||
|
top: 50px;
|
||||||
|
left: 10px;
|
||||||
|
width: min(360px, calc(100vw - 20px));
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,17 @@ let pinchZoomOccurred = false;
|
|||||||
let initialPinchDistance = null;
|
let initialPinchDistance = null;
|
||||||
let initialScale = 1;
|
let initialScale = 1;
|
||||||
let currentScale = 1;
|
let currentScale = 1;
|
||||||
|
let currentTranslateX = 0;
|
||||||
|
let currentTranslateY = 0;
|
||||||
|
let isPanning = false;
|
||||||
|
let panStartX = 0;
|
||||||
|
let panStartY = 0;
|
||||||
|
let panOriginX = 0;
|
||||||
|
let panOriginY = 0;
|
||||||
|
let pinchStartMidX = 0;
|
||||||
|
let pinchStartMidY = 0;
|
||||||
|
let pinchStartTranslateX = 0;
|
||||||
|
let pinchStartTranslateY = 0;
|
||||||
let exifAbortController = null;
|
let exifAbortController = null;
|
||||||
let isGalleryLoading = false;
|
let isGalleryLoading = false;
|
||||||
let autoPlayTimer = null;
|
let autoPlayTimer = null;
|
||||||
@ -179,16 +190,55 @@ function updateFilenameDisplay(relUrl) {
|
|||||||
if (!filenameEl) {
|
if (!filenameEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const filenameTextEl = filenameEl.querySelector('.filename-text');
|
||||||
|
|
||||||
if (!relUrl) {
|
if (!relUrl) {
|
||||||
|
if (filenameTextEl) {
|
||||||
|
filenameTextEl.textContent = '';
|
||||||
|
} else {
|
||||||
filenameEl.textContent = '';
|
filenameEl.textContent = '';
|
||||||
|
}
|
||||||
|
filenameEl.dataset.filename = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = relUrl.split('/');
|
const parts = relUrl.split('/');
|
||||||
const filename = parts[parts.length - 1] || relUrl;
|
const filename = parts[parts.length - 1] || relUrl;
|
||||||
|
if (filenameTextEl) {
|
||||||
|
filenameTextEl.textContent = filename;
|
||||||
|
} else {
|
||||||
filenameEl.textContent = filename;
|
filenameEl.textContent = filename;
|
||||||
}
|
}
|
||||||
|
filenameEl.dataset.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilenameProgress() {
|
||||||
|
const filenameEl = document.getElementById('gallery-filename');
|
||||||
|
if (!filenameEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = currentGalleryImages.length;
|
||||||
|
const hasValidIndex = currentGalleryIndex >= 0 && currentGalleryIndex < total;
|
||||||
|
const progress = total > 0 && hasValidIndex
|
||||||
|
? ((currentGalleryIndex + 1) / total) * 100
|
||||||
|
: 0;
|
||||||
|
filenameEl.style.setProperty('--filename-progress', `${progress}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGalleryPosition() {
|
||||||
|
const positionEl = document.getElementById('gallery-position');
|
||||||
|
if (!positionEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = currentGalleryImages.length;
|
||||||
|
if (total <= 0 || currentGalleryIndex < 0 || currentGalleryIndex >= total) {
|
||||||
|
positionEl.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
positionEl.textContent = `${currentGalleryIndex + 1} / ${total}`;
|
||||||
|
}
|
||||||
|
|
||||||
function updateDownloadLink(relUrl) {
|
function updateDownloadLink(relUrl) {
|
||||||
const downloadBtn = document.getElementById('gallery-download');
|
const downloadBtn = document.getElementById('gallery-download');
|
||||||
@ -196,15 +246,11 @@ function updateDownloadLink(relUrl) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!relUrl) {
|
if (!relUrl) {
|
||||||
downloadBtn.removeAttribute('href');
|
|
||||||
downloadBtn.removeAttribute('download');
|
|
||||||
downloadBtn.removeAttribute('data-relurl');
|
downloadBtn.removeAttribute('data-relurl');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Store relUrl for the click handler (which fetches a short-lived token)
|
// Store relUrl for the click handler (which fetches a short-lived token)
|
||||||
downloadBtn.dataset.relurl = relUrl;
|
downloadBtn.dataset.relurl = relUrl;
|
||||||
downloadBtn.removeAttribute('href');
|
|
||||||
downloadBtn.removeAttribute('download');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNextButtonState() {
|
function updateNextButtonState() {
|
||||||
@ -214,23 +260,87 @@ function updateNextButtonState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const atLastImage = currentGalleryIndex >= currentGalleryImages.length - 1;
|
const atLastImage = currentGalleryIndex >= currentGalleryImages.length - 1;
|
||||||
nextButton.textContent = atLastImage ? 'x' : '›';
|
nextButton.textContent = atLastImage ? '\u00D7' : '\u203A';
|
||||||
nextButton.setAttribute('aria-label', atLastImage ? 'Close gallery' : 'Next image');
|
nextButton.setAttribute('aria-label', atLastImage ? 'Close gallery' : 'Next image');
|
||||||
|
nextButton.classList.toggle('is-close', atLastImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showGalleryNav() {
|
function showGalleryNav() {
|
||||||
clearTimeout(navHideTimer);
|
clearTimeout(navHideTimer);
|
||||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
||||||
|
if (currentScale > 1.01) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
navHideTimer = setTimeout(() => {
|
navHideTimer = setTimeout(() => {
|
||||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.add('nav-hidden'));
|
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.add('nav-hidden'));
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanBounds(image, scale) {
|
||||||
|
const stage = document.getElementById('gallery-stage');
|
||||||
|
if (!stage || !image) {
|
||||||
|
return { maxX: 0, maxY: 0 };
|
||||||
|
}
|
||||||
|
const baseWidth = image.clientWidth || 0;
|
||||||
|
const baseHeight = image.clientHeight || 0;
|
||||||
|
const scaledWidth = baseWidth * scale;
|
||||||
|
const scaledHeight = baseHeight * scale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
maxX: Math.max(0, (scaledWidth - stage.clientWidth) / 2),
|
||||||
|
maxY: Math.max(0, (scaledHeight - stage.clientHeight) / 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyImageTransform(image) {
|
||||||
|
if (!image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { maxX, maxY } = getPanBounds(image, currentScale);
|
||||||
|
currentTranslateX = clamp(currentTranslateX, -maxX, maxX);
|
||||||
|
currentTranslateY = clamp(currentTranslateY, -maxY, maxY);
|
||||||
|
image.style.transform = `translate(-50%, -50%) translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${currentScale})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetImageTransformState(image = null) {
|
||||||
|
isPinching = false;
|
||||||
|
isPanning = false;
|
||||||
|
pinchZoomOccurred = false;
|
||||||
|
initialPinchDistance = null;
|
||||||
|
initialScale = 1;
|
||||||
|
currentScale = 1;
|
||||||
|
currentTranslateX = 0;
|
||||||
|
currentTranslateY = 0;
|
||||||
|
panStartX = 0;
|
||||||
|
panStartY = 0;
|
||||||
|
panOriginX = 0;
|
||||||
|
panOriginY = 0;
|
||||||
|
pinchStartMidX = 0;
|
||||||
|
pinchStartMidY = 0;
|
||||||
|
pinchStartTranslateX = 0;
|
||||||
|
pinchStartTranslateY = 0;
|
||||||
|
if (image) {
|
||||||
|
applyImageTransform(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openGalleryModal(relUrl) {
|
function openGalleryModal(relUrl) {
|
||||||
|
const modal = document.getElementById('gallery-modal');
|
||||||
|
if (!modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
document.body.style.overflow = 'hidden'; // Disable background scrolling.
|
document.body.style.overflow = 'hidden'; // Disable background scrolling.
|
||||||
currentGalleryIndex = currentGalleryImages.indexOf(relUrl);
|
currentGalleryIndex = currentGalleryImages.indexOf(relUrl);
|
||||||
|
if (currentGalleryIndex < 0) {
|
||||||
|
currentGalleryIndex = 0;
|
||||||
|
}
|
||||||
|
modal.setAttribute('aria-hidden', 'false');
|
||||||
showGalleryImage(relUrl);
|
showGalleryImage(relUrl);
|
||||||
document.getElementById('gallery-modal').style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
showGalleryNav();
|
showGalleryNav();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,12 +351,16 @@ function showGalleryImage(relUrl) {
|
|||||||
isGalleryLoading = true;
|
isGalleryLoading = true;
|
||||||
clearAutoPlayTimer();
|
clearAutoPlayTimer();
|
||||||
updateNextButtonState();
|
updateNextButtonState();
|
||||||
|
updateGalleryPosition();
|
||||||
|
updateFilenameProgress();
|
||||||
loadExifData(relUrl);
|
loadExifData(relUrl);
|
||||||
const fullUrl = '/media/' + relUrl;
|
const fullUrl = '/media/' + relUrl;
|
||||||
const modal = document.getElementById('gallery-modal');
|
const stage = document.getElementById('gallery-stage');
|
||||||
const currentImage = document.getElementById('gallery-modal-content');
|
|
||||||
const loader = document.getElementById('loader-container');
|
const loader = document.getElementById('loader-container');
|
||||||
const closeBtn = document.getElementById('gallery-close');
|
if (!stage || !loader) {
|
||||||
|
isGalleryLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
updateDownloadLink(relUrl);
|
updateDownloadLink(relUrl);
|
||||||
// Set a timeout for showing the loader after 500ms.
|
// Set a timeout for showing the loader after 500ms.
|
||||||
let loaderTimeout = setTimeout(() => {
|
let loaderTimeout = setTimeout(() => {
|
||||||
@ -262,8 +376,8 @@ function showGalleryImage(relUrl) {
|
|||||||
loader.style.display = 'none';
|
loader.style.display = 'none';
|
||||||
updateFilenameDisplay(relUrl);
|
updateFilenameDisplay(relUrl);
|
||||||
|
|
||||||
// Select all current images in the gallery modal
|
// Select all current images in the gallery stage
|
||||||
const existingImages = document.querySelectorAll('#gallery-modal img');
|
const existingImages = stage.querySelectorAll('img');
|
||||||
|
|
||||||
// Create a new image element for the transition
|
// Create a new image element for the transition
|
||||||
const newImage = document.createElement('img');
|
const newImage = document.createElement('img');
|
||||||
@ -272,27 +386,23 @@ function showGalleryImage(relUrl) {
|
|||||||
newImage.style.top = '50%'; // Center vertically
|
newImage.style.top = '50%'; // Center vertically
|
||||||
newImage.style.left = '50%'; // Center horizontally
|
newImage.style.left = '50%'; // Center horizontally
|
||||||
newImage.style.transform = 'translate(-50%, -50%)'; // Ensure centering
|
newImage.style.transform = 'translate(-50%, -50%)'; // Ensure centering
|
||||||
newImage.style.maxWidth = '95vw'; // Prevent overflow on large screens
|
|
||||||
newImage.style.maxHeight = '95vh'; // Prevent overflow on tall screens
|
|
||||||
newImage.style.transition = 'opacity 0.5s ease-in-out';
|
newImage.style.transition = 'opacity 0.5s ease-in-out';
|
||||||
newImage.style.opacity = 0; // Start fully transparent
|
newImage.style.opacity = 0; // Start fully transparent
|
||||||
newImage.id = 'gallery-modal-content';
|
newImage.id = 'gallery-modal-content';
|
||||||
|
newImage.alt = '';
|
||||||
|
|
||||||
// Add pinch-to-zoom event listeners
|
// Add pinch-to-zoom event listeners
|
||||||
newImage.addEventListener('touchstart', handlePinchStart, { passive: false });
|
newImage.addEventListener('touchstart', handlePinchStart, { passive: false });
|
||||||
newImage.addEventListener('touchmove', handlePinchMove, { passive: false });
|
newImage.addEventListener('touchmove', handlePinchMove, { passive: false });
|
||||||
newImage.addEventListener('touchend', handlePinchEnd, { passive: false });
|
newImage.addEventListener('touchend', handlePinchEnd, { passive: false });
|
||||||
newImage.addEventListener('click', handleGalleryImageClick);
|
newImage.addEventListener('touchcancel', handlePinchEnd, { passive: false });
|
||||||
|
|
||||||
|
|
||||||
// Reset pinch variables for the new image
|
// Reset pinch variables for the new image
|
||||||
initialPinchDistance = null;
|
resetImageTransformState(newImage);
|
||||||
initialScale = 1;
|
|
||||||
currentScale = 1;
|
|
||||||
pinchZoomOccurred = false;
|
|
||||||
|
|
||||||
// Append new image on top of existing ones
|
// Append new image on top of existing ones
|
||||||
modal.appendChild(newImage);
|
stage.appendChild(newImage);
|
||||||
|
|
||||||
// Fade out all old images and fade in the new one
|
// Fade out all old images and fade in the new one
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -347,50 +457,88 @@ function preloadNextImage() {
|
|||||||
function closeGalleryModal() {
|
function closeGalleryModal() {
|
||||||
clearTimeout(navHideTimer);
|
clearTimeout(navHideTimer);
|
||||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
||||||
document.getElementById('gallery-modal').style.display = 'none';
|
const modal = document.getElementById('gallery-modal');
|
||||||
|
const stage = document.getElementById('gallery-stage');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
modal.setAttribute('aria-hidden', 'true');
|
||||||
|
}
|
||||||
isGalleryLoading = false;
|
isGalleryLoading = false;
|
||||||
setAutoPlay(false);
|
setAutoPlay(false);
|
||||||
updateDownloadLink(null);
|
updateDownloadLink(null);
|
||||||
// remove all images
|
// remove all images
|
||||||
const existingImages = document.querySelectorAll('#gallery-modal img');
|
if (stage) {
|
||||||
|
const existingImages = stage.querySelectorAll('img');
|
||||||
existingImages.forEach(img => {
|
existingImages.forEach(img => {
|
||||||
if (img.parentNode) {
|
if (img.parentNode) {
|
||||||
img.parentNode.removeChild(img);
|
img.parentNode.removeChild(img);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
const filenameEl = document.getElementById('gallery-filename');
|
const filenameEl = document.getElementById('gallery-filename');
|
||||||
if (filenameEl) {
|
if (filenameEl) {
|
||||||
|
const filenameTextEl = filenameEl.querySelector('.filename-text');
|
||||||
|
if (filenameTextEl) {
|
||||||
|
filenameTextEl.textContent = '';
|
||||||
|
} else {
|
||||||
filenameEl.textContent = '';
|
filenameEl.textContent = '';
|
||||||
}
|
}
|
||||||
|
filenameEl.dataset.filename = '';
|
||||||
|
filenameEl.style.setProperty('--filename-progress', '0%');
|
||||||
|
}
|
||||||
|
const positionEl = document.getElementById('gallery-position');
|
||||||
|
if (positionEl) {
|
||||||
|
positionEl.textContent = '';
|
||||||
|
}
|
||||||
resetExifPanel(true);
|
resetExifPanel(true);
|
||||||
|
resetImageTransformState();
|
||||||
// Restore scrolling.
|
// Restore scrolling.
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGalleryImageClick(e) {
|
function navigateByScreenHalf(clientX) {
|
||||||
// If a pinch zoom occurred, don't change the image.
|
if (isGalleryLoading) {
|
||||||
if (pinchZoomOccurred) {
|
|
||||||
pinchZoomOccurred = false; // Reset the flag for the next gesture.
|
|
||||||
e.stopPropagation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.classList.contains('gallery-next') || e.target.classList.contains('gallery-prev')) {
|
const midpoint = window.innerWidth / 2;
|
||||||
return; // Prevent conflict with navigation buttons.
|
if (clientX < midpoint) {
|
||||||
}
|
if (currentGalleryIndex > 0) {
|
||||||
|
|
||||||
const imgRect = this.getBoundingClientRect();
|
|
||||||
if (e.clientX < imgRect.left + imgRect.width / 2) {
|
|
||||||
if (!isGalleryLoading && currentGalleryIndex > 0) {
|
|
||||||
currentGalleryIndex--;
|
currentGalleryIndex--;
|
||||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
if (!isGalleryLoading && currentGalleryIndex < currentGalleryImages.length - 1) {
|
}
|
||||||
|
|
||||||
|
if (currentGalleryIndex < currentGalleryImages.length - 1) {
|
||||||
currentGalleryIndex++;
|
currentGalleryIndex++;
|
||||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||||
|
} else {
|
||||||
|
closeGalleryModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleGalleryScreenClick(e) {
|
||||||
|
// If a pinch zoom occurred, don't change the image.
|
||||||
|
if (pinchZoomOccurred) {
|
||||||
|
pinchZoomOccurred = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentScale > 1.01 || isPinching || isPanning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = e.target;
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not interpret clicks on controls/panels as navigation.
|
||||||
|
if (target.closest('.gallery-control') || target.closest('.gallery-nav') || target.closest('#gallery-exif')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateByScreenHalf(e.clientX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -400,21 +548,24 @@ function initGallerySwipe() {
|
|||||||
let touchStartX = 0;
|
let touchStartX = 0;
|
||||||
let touchEndX = 0;
|
let touchEndX = 0;
|
||||||
const galleryModal = document.getElementById('gallery-modal');
|
const galleryModal = document.getElementById('gallery-modal');
|
||||||
|
if (!galleryModal) {
|
||||||
galleryModal.addEventListener('mousemove', showGalleryNav, false);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
galleryModal.addEventListener('touchstart', function(e) {
|
galleryModal.addEventListener('touchstart', function(e) {
|
||||||
showGalleryNav();
|
|
||||||
// If more than one finger is on screen, ignore swipe tracking.
|
// If more than one finger is on screen, ignore swipe tracking.
|
||||||
if (e.touches.length > 1) {
|
if (e.touches.length > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (currentScale > 1.01 || isPinching || isPanning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
touchStartX = e.changedTouches[0].screenX;
|
touchStartX = e.changedTouches[0].screenX;
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
galleryModal.addEventListener('touchend', function(e) {
|
galleryModal.addEventListener('touchend', function(e) {
|
||||||
// If the event was part of a multi-touch (pinch) gesture or a pinch was detected, skip swipe.
|
// If the event was part of a multi-touch (pinch) gesture or a pinch was detected, skip swipe.
|
||||||
if (e.changedTouches.length > 1 || isPinching || pinchZoomOccurred) {
|
if (e.changedTouches.length > 1 || isPinching || isPanning || pinchZoomOccurred || currentScale > 1.01) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
touchEndX = e.changedTouches[0].screenX;
|
touchEndX = e.changedTouches[0].screenX;
|
||||||
@ -441,7 +592,7 @@ function initGallerySwipe() {
|
|||||||
|
|
||||||
function clearAutoPlayTimer() {
|
function clearAutoPlayTimer() {
|
||||||
if (autoPlayTimer) {
|
if (autoPlayTimer) {
|
||||||
clearInterval(autoPlayTimer);
|
clearTimeout(autoPlayTimer);
|
||||||
autoPlayTimer = null;
|
autoPlayTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,8 +601,9 @@ function setAutoPlay(active) {
|
|||||||
const playButton = document.getElementById('gallery-play');
|
const playButton = document.getElementById('gallery-play');
|
||||||
isAutoPlay = active;
|
isAutoPlay = active;
|
||||||
if (playButton) {
|
if (playButton) {
|
||||||
playButton.textContent = active ? '\u23F8' : '\u25B6';
|
playButton.textContent = active ? '\u2225' : '\u25B6';
|
||||||
playButton.setAttribute('aria-pressed', active ? 'true' : 'false');
|
playButton.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||||
|
playButton.setAttribute('aria-label', active ? 'Pause slideshow' : 'Start slideshow');
|
||||||
playButton.classList.toggle('playing', active);
|
playButton.classList.toggle('playing', active);
|
||||||
}
|
}
|
||||||
pendingAutoAdvance = false;
|
pendingAutoAdvance = false;
|
||||||
@ -541,10 +693,28 @@ function handlePinchStart(e) {
|
|||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isPinching = true;
|
isPinching = true;
|
||||||
|
isPanning = false;
|
||||||
pinchZoomOccurred = false; // Reset at the start of a pinch gesture.
|
pinchZoomOccurred = false; // Reset at the start of a pinch gesture.
|
||||||
const dx = e.touches[0].pageX - e.touches[1].pageX;
|
const dx = e.touches[0].pageX - e.touches[1].pageX;
|
||||||
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
||||||
initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
|
initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
initialScale = currentScale;
|
||||||
|
pinchStartMidX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
|
||||||
|
pinchStartMidY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
|
||||||
|
pinchStartTranslateX = currentTranslateX;
|
||||||
|
pinchStartTranslateY = currentTranslateY;
|
||||||
|
showGalleryNav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.touches.length === 1 && currentScale > 1.01) {
|
||||||
|
e.preventDefault();
|
||||||
|
isPanning = true;
|
||||||
|
panStartX = e.touches[0].pageX;
|
||||||
|
panStartY = e.touches[0].pageY;
|
||||||
|
panOriginX = currentTranslateX;
|
||||||
|
panOriginY = currentTranslateY;
|
||||||
|
showGalleryNav();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,21 +725,97 @@ function handlePinchMove(e) {
|
|||||||
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
||||||
const currentDistance = Math.sqrt(dx * dx + dy * dy);
|
const currentDistance = Math.sqrt(dx * dx + dy * dy);
|
||||||
let scale = currentDistance / initialPinchDistance;
|
let scale = currentDistance / initialPinchDistance;
|
||||||
scale = Math.max(0.5, Math.min(scale * initialScale, 3));
|
scale = Math.max(1, Math.min(scale * initialScale, 4));
|
||||||
// If the scale has changed enough, mark that a pinch zoom occurred.
|
// If the scale has changed enough, mark that a pinch zoom occurred.
|
||||||
if (Math.abs(scale - initialScale) > 0.05) {
|
if (Math.abs(scale - currentScale) > 0.01) {
|
||||||
pinchZoomOccurred = true;
|
pinchZoomOccurred = true;
|
||||||
}
|
}
|
||||||
currentScale = scale;
|
currentScale = scale;
|
||||||
e.currentTarget.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
const midX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
|
||||||
|
const midY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
|
||||||
|
currentTranslateX = pinchStartTranslateX + (midX - pinchStartMidX);
|
||||||
|
currentTranslateY = pinchStartTranslateY + (midY - pinchStartMidY);
|
||||||
|
applyImageTransform(e.currentTarget);
|
||||||
|
showGalleryNav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.touches.length === 1 && isPanning && currentScale > 1.01) {
|
||||||
|
e.preventDefault();
|
||||||
|
const deltaX = e.touches[0].pageX - panStartX;
|
||||||
|
const deltaY = e.touches[0].pageY - panStartY;
|
||||||
|
currentTranslateX = panOriginX + deltaX;
|
||||||
|
currentTranslateY = panOriginY + deltaY;
|
||||||
|
pinchZoomOccurred = true;
|
||||||
|
applyImageTransform(e.currentTarget);
|
||||||
|
showGalleryNav();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePinchEnd(e) {
|
function handlePinchEnd(e) {
|
||||||
if (e.touches.length < 2) {
|
if (e.touches.length === 1 && currentScale > 1.01) {
|
||||||
initialScale = currentScale;
|
// Transition from pinch to one-finger pan.
|
||||||
initialPinchDistance = null;
|
|
||||||
isPinching = false;
|
isPinching = false;
|
||||||
|
isPanning = true;
|
||||||
|
initialPinchDistance = null;
|
||||||
|
panStartX = e.touches[0].pageX;
|
||||||
|
panStartY = e.touches[0].pageY;
|
||||||
|
panOriginX = currentTranslateX;
|
||||||
|
panOriginY = currentTranslateY;
|
||||||
|
showGalleryNav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.touches.length < 1) {
|
||||||
|
isPinching = false;
|
||||||
|
isPanning = false;
|
||||||
|
initialPinchDistance = null;
|
||||||
|
initialScale = currentScale;
|
||||||
|
const image = e.currentTarget;
|
||||||
|
if (currentScale <= 1.01) {
|
||||||
|
currentScale = 1;
|
||||||
|
currentTranslateX = 0;
|
||||||
|
currentTranslateY = 0;
|
||||||
|
}
|
||||||
|
applyImageTransform(image);
|
||||||
|
showGalleryNav();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGalleryKeydown(e) {
|
||||||
|
const modal = document.getElementById('gallery-modal');
|
||||||
|
if (!modal || modal.style.display !== 'flex') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeGalleryModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isGalleryLoading && currentGalleryIndex > 0) {
|
||||||
|
currentGalleryIndex--;
|
||||||
|
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'ArrowRight') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isGalleryLoading && currentGalleryIndex < currentGalleryImages.length - 1) {
|
||||||
|
currentGalleryIndex++;
|
||||||
|
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||||
|
} else if (!isGalleryLoading) {
|
||||||
|
closeGalleryModal();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setAutoPlay(!isAutoPlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,12 +920,8 @@ function initGallery() {
|
|||||||
initGallerySwipe();
|
initGallerySwipe();
|
||||||
initGalleryControls();
|
initGalleryControls();
|
||||||
|
|
||||||
// Close modal when clicking outside the image.
|
galleryModal.addEventListener('click', handleGalleryScreenClick);
|
||||||
galleryModal.addEventListener('click', function(e) {
|
document.addEventListener('keydown', handleGalleryKeydown);
|
||||||
if (e.target === galleryModal) {
|
|
||||||
closeGalleryModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
galleryModal.dataset.galleryBound = '1';
|
galleryModal.dataset.galleryBound = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -203,16 +203,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Gallery Modal for Images -->
|
<!-- Gallery Modal for Images -->
|
||||||
<div id="gallery-modal" style="display: none;">
|
<div id="gallery-modal" style="display: none;" role="dialog" aria-modal="true" aria-hidden="true">
|
||||||
<button id="gallery-close">✕</button>
|
<div id="gallery-stage">
|
||||||
<button id="gallery-info" aria-expanded="false" aria-controls="gallery-exif">i</button>
|
<img id="gallery-modal-content" src="" alt="">
|
||||||
<a id="gallery-download" aria-label="Download original image" title="Download original">⇩</a>
|
</div>
|
||||||
<img id="gallery-modal-content" src="">
|
|
||||||
<button class="gallery-nav gallery-prev">‹</button>
|
<div class="gallery-top-bar">
|
||||||
<button class="gallery-nav gallery-next">›</button>
|
<div class="gallery-actions-left">
|
||||||
<button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">▶</button>
|
<button id="gallery-info" class="gallery-control" type="button" aria-expanded="false" aria-controls="gallery-exif">i</button>
|
||||||
|
<button id="gallery-download" class="gallery-control" type="button" aria-label="Download original image" title="Download original image">↓</button>
|
||||||
|
</div>
|
||||||
|
<div class="gallery-actions-right">
|
||||||
|
<button id="gallery-play" class="gallery-control" type="button" aria-pressed="false" aria-label="Start slideshow">▶</button>
|
||||||
|
<button id="gallery-close" class="gallery-control" type="button" aria-label="Close gallery">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="gallery-nav gallery-prev" type="button" aria-label="Previous image">‹</button>
|
||||||
|
<button class="gallery-nav gallery-next" type="button" aria-label="Next image">›</button>
|
||||||
<div id="gallery-exif" aria-live="polite"></div>
|
<div id="gallery-exif" aria-live="polite"></div>
|
||||||
<div id="gallery-filename" aria-live="polite"></div>
|
|
||||||
|
<div class="gallery-meta-bar">
|
||||||
|
<div id="gallery-filename" aria-live="polite"><span class="filename-text"></span></div>
|
||||||
|
<div id="gallery-position" aria-live="polite"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="loader-container" style="display: none;">
|
<div id="loader-container" style="display: none;">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user