make image preview dialog

This commit is contained in:
Atash03 2026-04-11 16:53:15 +05:00
parent c7ab879a19
commit 60f4855b06
4 changed files with 161 additions and 55 deletions

2
dist/styles.css vendored

File diff suppressed because one or more lines are too long

View File

@ -229,38 +229,27 @@
<h2 class="text-3xl md:text-4xl lg:text-5xl font-black text-on-surface mb-8 md:mb-12 lg:mb-16 tracking-tight" data-i18n="completed_projects">Реализованные проекты</h2> <h2 class="text-3xl md:text-4xl lg:text-5xl font-black text-on-surface mb-8 md:mb-12 lg:mb-16 tracking-tight" data-i18n="completed_projects">Реализованные проекты</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-px bg-outline-variant overflow-hidden"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-px bg-outline-variant overflow-hidden">
<!-- Project 1: Water Treatment Plant --> <!-- Project 1: Water Treatment Plant -->
<div class="group relative bg-background overflow-hidden aspect-video lg:aspect-square"> <div class="group relative bg-background overflow-hidden aspect-video lg:aspect-square cursor-pointer"
<div class="carousel absolute inset-0" data-autoplay="4000"> data-lightbox
<img class="carousel-slide active w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_1.webp" alt="Tejen water treatment plant 1" /> data-lightbox-images='["public/tejen_1.webp","public/tejen_2.webp","public/tejen_3.webp","public/tejen_4.webp","public/tejen_5.webp","public/tejen_6.webp","public/tejen_7.webp"]'
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_2.webp" alt="Tejen water treatment plant 2" /> data-lightbox-title="project1_title"
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_3.webp" alt="Tejen water treatment plant 3" /> data-lightbox-desc="project1_desc">
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_4.webp" alt="Tejen water treatment plant 4" /> <img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy" decoding="async" src="public/tejen_1.webp" alt="Tejen water treatment plant" />
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_5.webp" alt="Tejen water treatment plant 5" /> <div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent p-4 md:p-8 lg:p-12 flex flex-col justify-end z-10 pointer-events-none">
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_6.webp" alt="Tejen water treatment plant 6" /> <h4 class="text-xl md:text-2xl lg:text-3xl font-bold text-white mb-1" data-i18n="project1_title">Водоочистное сооружение в г. Теджен</h4>
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/tejen_7.webp" alt="Tejen water treatment plant 7" /> <p class="text-sm text-white/80" data-i18n="project1_desc">Строительство водоочистного сооружения мощностью 30 000 м³ в сутки в городе Теджен Ахалского велаята — завершён 2024 г.</p>
<button class="carousel-prev" aria-label="Previous">&lsaquo;</button>
<button class="carousel-next" aria-label="Next">&rsaquo;</button>
<div class="carousel-dots"></div>
</div>
<div
class="absolute inset-0 bg-primary/80 opacity-0 group-hover:opacity-100 transition-opacity duration-500 p-4 md:p-8 lg:p-12 flex flex-col justify-end text-on-primary z-20">
<h4 class="text-xl md:text-2xl lg:text-3xl font-bold mb-2" data-i18n="project1_title">Водоочистное сооружение в г. Теджен</h4>
<p class="text-sm opacity-90" data-i18n="project1_desc">Строительство водоочистного сооружения мощностью 30 000 м³ в сутки в городе Теджен Ахалского велаята — завершён 2024 г.</p>
</div> </div>
</div> </div>
<!-- Project 2: Power Supply System --> <!-- Project 2: Power Supply System -->
<div class="group relative bg-background overflow-hidden aspect-video lg:aspect-square"> <div class="group relative bg-background overflow-hidden aspect-video lg:aspect-square cursor-pointer"
<div class="carousel absolute inset-0" data-autoplay="4000"> data-lightbox
<img class="carousel-slide active w-full h-full object-cover" loading="lazy" decoding="async" src="public/electric_1.webp" alt="Power supply infrastructure 1" /> data-lightbox-images='["public/electric_1.webp","public/electric_2.webp"]'
<img class="carousel-slide w-full h-full object-cover" loading="lazy" decoding="async" src="public/electric_2.webp" alt="Power supply infrastructure 2" /> data-lightbox-title="project2_title"
<button class="carousel-prev" aria-label="Previous">&lsaquo;</button> data-lightbox-desc="project2_desc">
<button class="carousel-next" aria-label="Next">&rsaquo;</button> <img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy" decoding="async" src="public/electric_1.webp" alt="Power supply infrastructure" />
<div class="carousel-dots"></div> <div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent p-4 md:p-8 lg:p-12 flex flex-col justify-end z-10 pointer-events-none">
</div> <h4 class="text-xl md:text-2xl lg:text-3xl font-bold text-white mb-1" data-i18n="project2_title">Электроснабжение водоочистного сооружения</h4>
<div <p class="text-sm text-white/80" data-i18n="project2_desc">Строительно-монтажные работы по внешней системе электроснабжения водоочистного сооружения мощностью 30 000 м³ в сутки в г. Теджен — завершён 2026 г.</p>
class="absolute inset-0 bg-primary/80 opacity-0 group-hover:opacity-100 transition-opacity duration-500 p-4 md:p-8 lg:p-12 flex flex-col justify-end text-on-primary z-20">
<h4 class="text-xl md:text-2xl lg:text-3xl font-bold mb-2" data-i18n="project2_title">Электроснабжение водоочистного сооружения</h4>
<p class="text-sm opacity-90" data-i18n="project2_desc">Строительно-монтажные работы по внешней системе электроснабжения водоочистного сооружения мощностью 30 000 м³ в сутки в г. Теджен — завершён 2026 г.</p>
</div> </div>
</div> </div>
</div> </div>
@ -280,14 +269,22 @@
<h3 class="text-xl md:text-2xl lg:text-3xl font-black mt-2 mb-4 uppercase tracking-tight" data-i18n="current1_title">Водоочистное сооружение в Бабадайханском районе</h3> <h3 class="text-xl md:text-2xl lg:text-3xl font-black mt-2 mb-4 uppercase tracking-tight" data-i18n="current1_title">Водоочистное сооружение в Бабадайханском районе</h3>
<p class="text-primary-fixed leading-relaxed" data-i18n="current1_desc">Строительство водоочистного сооружения мощностью 20 000 м³ в сутки в Бабадайханском районе Ахалского велаята (Туркменистан).</p> <p class="text-primary-fixed leading-relaxed" data-i18n="current1_desc">Строительство водоочистного сооружения мощностью 20 000 м³ в сутки в Бабадайханском районе Ахалского велаята (Туркменистан).</p>
</div> </div>
<div class="relative aspect-video overflow-hidden"> <div class="group/img relative aspect-video overflow-hidden cursor-pointer"
<img class="w-full h-full object-cover" loading="lazy" decoding="async" src="public/babdayhan.webp" alt="Babadayhan water treatment plant" /> data-lightbox
data-lightbox-images='["public/babdayhan.webp"]'
data-lightbox-title="current1_title"
data-lightbox-desc="current1_desc">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover/img:scale-105" loading="lazy" decoding="async" src="public/babdayhan.webp" alt="Babadayhan water treatment plant" />
</div> </div>
</div> </div>
<!-- Current Project 2: carousel left, text right --> <!-- Current Project 2: carousel left, text right -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12 items-center"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12 items-center">
<div class="relative aspect-video overflow-hidden order-2 lg:order-1"> <div class="group/img relative aspect-video overflow-hidden order-2 lg:order-1 cursor-pointer"
<img class="w-full h-full object-cover" loading="lazy" decoding="async" src="public/garashsyzlyk.webp" alt="Garashsyzlyk water supply" /> data-lightbox
data-lightbox-images='["public/garashsyzlyk.webp"]'
data-lightbox-title="current2_title"
data-lightbox-desc="current2_desc">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover/img:scale-105" loading="lazy" decoding="async" src="public/garashsyzlyk.webp" alt="Garashsyzlyk water supply" />
</div> </div>
<div class="order-1 lg:order-2"> <div class="order-1 lg:order-2">
<span class="text-xs uppercase font-bold tracking-widest text-primary-fixed/60" data-i18n="current2_status">Статус: В процессе</span> <span class="text-xs uppercase font-bold tracking-widest text-primary-fixed/60" data-i18n="current2_status">Статус: В процессе</span>
@ -405,8 +402,31 @@
</div> </div>
</footer> </footer>
<!-- Lightbox -->
<div id="lightbox" class="lightbox fixed inset-0 z-[80] flex items-center justify-center p-4 md:p-8 bg-black/80">
<div id="lightbox-content" class="relative w-full max-w-5xl max-h-[90vh] flex flex-col">
<div id="lightbox-counter" class="absolute top-4 left-4 z-20 text-white/90 text-sm font-bold bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-sm"></div>
<button id="lightbox-close" class="absolute top-4 right-4 z-20 w-10 h-10 flex items-center justify-center text-white/90 hover:text-white bg-black/50 hover:bg-black/70 backdrop-blur-sm rounded-sm transition-colors" aria-label="Close">
<span class="material-symbols-outlined">close</span>
</button>
<div class="relative flex-1 min-h-0 flex items-center justify-center bg-black rounded-t-sm overflow-hidden">
<img id="lightbox-image" class="max-w-full max-h-[calc(90vh-5rem)] object-contain select-none" draggable="false" alt="" />
<button id="lightbox-prev" class="absolute left-3 top-1/2 -translate-y-1/2 w-11 h-11 md:w-12 md:h-12 flex items-center justify-center bg-black/40 hover:bg-black/60 text-white/90 hover:text-white backdrop-blur-sm rounded-sm transition-colors" aria-label="Previous">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<button id="lightbox-next" class="absolute right-3 top-1/2 -translate-y-1/2 w-11 h-11 md:w-12 md:h-12 flex items-center justify-center bg-black/40 hover:bg-black/60 text-white/90 hover:text-white backdrop-blur-sm rounded-sm transition-colors" aria-label="Next">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
</button>
</div>
<div class="bg-white p-4 md:p-6 rounded-b-sm">
<h4 id="lightbox-title" class="text-base md:text-lg font-bold text-on-surface"></h4>
<p id="lightbox-desc" class="text-sm text-on-surface-variant mt-1"></p>
</div>
</div>
</div>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script src="js/carousel.js"></script> <script src="js/lightbox.js"></script>
<script> <script>
const drawer = document.getElementById('mobile-drawer'); const drawer = document.getElementById('mobile-drawer');
const backdrop = document.getElementById('drawer-backdrop'); const backdrop = document.getElementById('drawer-backdrop');

98
js/lightbox.js Normal file
View File

@ -0,0 +1,98 @@
(function () {
const lightbox = document.getElementById('lightbox');
const content = document.getElementById('lightbox-content');
const image = document.getElementById('lightbox-image');
const title = document.getElementById('lightbox-title');
const desc = document.getElementById('lightbox-desc');
const counter = document.getElementById('lightbox-counter');
const prevBtn = document.getElementById('lightbox-prev');
const nextBtn = document.getElementById('lightbox-next');
const closeBtn = document.getElementById('lightbox-close');
let images = [];
let current = 0;
let touchStartX = 0;
function open(imgArray, titleKey, descKey) {
images = imgArray;
current = 0;
const lang = localStorage.getItem('lang') || 'ru';
title.textContent = translations[lang][titleKey] || '';
title.dataset.i18n = titleKey;
desc.textContent = translations[lang][descKey] || '';
desc.dataset.i18n = descKey;
updateImage();
updateNav();
lightbox.classList.add('open');
document.body.style.overflow = 'hidden';
}
function close() {
lightbox.classList.remove('open');
document.body.style.overflow = '';
}
function goTo(index) {
current = (index + images.length) % images.length;
updateImage();
}
function updateImage() {
image.src = images[current];
if (images.length > 1) {
counter.textContent = (current + 1) + ' / ' + images.length;
counter.style.display = '';
} else {
counter.style.display = 'none';
}
}
function updateNav() {
const multi = images.length > 1;
prevBtn.style.display = multi ? '' : 'none';
nextBtn.style.display = multi ? '' : 'none';
}
// Close handlers
closeBtn.addEventListener('click', close);
lightbox.addEventListener('click', function (e) {
if (e.target === lightbox) close();
});
content.addEventListener('click', function (e) {
e.stopPropagation();
});
// Navigation
prevBtn.addEventListener('click', function () { goTo(current - 1); });
nextBtn.addEventListener('click', function () { goTo(current + 1); });
// Keyboard
document.addEventListener('keydown', function (e) {
if (!lightbox.classList.contains('open')) return;
if (e.key === 'Escape') { close(); e.preventDefault(); }
if (e.key === 'ArrowLeft') { goTo(current - 1); e.preventDefault(); }
if (e.key === 'ArrowRight') { goTo(current + 1); e.preventDefault(); }
});
// Touch swipe
lightbox.addEventListener('touchstart', function (e) {
touchStartX = e.changedTouches[0].screenX;
}, { passive: true });
lightbox.addEventListener('touchend', function (e) {
var diff = touchStartX - e.changedTouches[0].screenX;
if (Math.abs(diff) > 50) {
goTo(diff > 0 ? current + 1 : current - 1);
}
});
// Initialize triggers
document.querySelectorAll('[data-lightbox]').forEach(function (el) {
el.addEventListener('click', function () {
var imgs = JSON.parse(el.dataset.lightboxImages);
open(imgs, el.dataset.lightboxTitle, el.dataset.lightboxDesc);
});
});
})();

View File

@ -90,28 +90,16 @@
.lang-btn { color: rgba(42, 52, 57, 0.4); } .lang-btn { color: rgba(42, 52, 57, 0.4); }
.lang-btn.active { color: #3a5f94; background: rgba(58, 95, 148, 0.1); } .lang-btn.active { color: #3a5f94; background: rgba(58, 95, 148, 0.1); }
.carousel { position: relative; } /* Lightbox */
.carousel-slide { position: absolute; inset: 0; opacity: 0; transition: opacity 1s ease; } .lightbox {
.carousel-slide.active { opacity: 0.9; } opacity: 0;
.carousel-prev, .carousel-next { pointer-events: none;
position: absolute; top: 50%; transform: translateY(-50%); z-index: 10; transition: opacity 0.25s ease;
width: 40px; height: 40px; background: rgba(255, 255, 255, 0.8); border: none;
font-size: 24px; line-height: 1; cursor: pointer; color: #2a3439;
opacity: 0; transition: opacity 0.3s;
} }
.carousel:hover .carousel-prev, .carousel:hover .carousel-next { opacity: 1; } .lightbox.open {
.carousel-prev { left: 12px; } opacity: 1;
.carousel-next { right: 12px; } pointer-events: auto;
.carousel-prev:hover, .carousel-next:hover { background: #fff; }
.carousel-dots {
position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);
display: flex; gap: 6px; z-index: 10;
} }
.carousel-dot {
width: 8px; height: 8px; border-radius: 50%; border: none; cursor: pointer;
background: rgba(255, 255, 255, 0.5); transition: background 0.3s;
}
.carousel-dot.active { background: #fff; }
/* Mobile drawer */ /* Mobile drawer */
.mobile-drawer { transform: translateX(100%); transition: transform 0.3s ease; } .mobile-drawer { transform: translateX(100%); transition: transform 0.3s ease; }