635 lines
22 KiB
JavaScript
635 lines
22 KiB
JavaScript
// Preloader
|
||
function rt(key, fallback) {
|
||
return (window.I18n && typeof window.I18n.t === 'function') ? window.I18n.t(key) : fallback;
|
||
}
|
||
window.addEventListener('load', () => {
|
||
setTimeout(() => {
|
||
document.getElementById('preloader').classList.add('hidden');
|
||
}, 1500);
|
||
});
|
||
|
||
// Navbar scroll effect
|
||
const navbar = document.querySelector('.navbar');
|
||
window.addEventListener('scroll', () => {
|
||
if (window.scrollY > 80) {
|
||
navbar.classList.add('scrolled');
|
||
} else {
|
||
navbar.classList.remove('scrolled');
|
||
}
|
||
});
|
||
|
||
// Scroll animations
|
||
const observerOptions = {
|
||
threshold: 0.1,
|
||
rootMargin: '0px 0px -50px 0px'
|
||
};
|
||
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('animated');
|
||
}
|
||
});
|
||
}, observerOptions);
|
||
|
||
document.querySelectorAll('.animate-on-scroll').forEach(el => {
|
||
observer.observe(el);
|
||
});
|
||
|
||
// Counter animation
|
||
const statsPromise = fetch('/api/stats').then(r => r.ok ? r.json() : null).catch(() => null);
|
||
|
||
const counterObserver = new IntersectionObserver(async (entries) => {
|
||
const stats = await statsPromise;
|
||
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
if (stats) {
|
||
entry.target.querySelectorAll('.counter[data-stat]').forEach(el => {
|
||
const stat = el.getAttribute('data-stat');
|
||
if (stats[stat] !== undefined) {
|
||
el.setAttribute('data-target', stats[stat]);
|
||
}
|
||
});
|
||
}
|
||
|
||
const counters = entry.target.querySelectorAll('.counter');
|
||
counters.forEach(counter => {
|
||
const target = parseInt(counter.getAttribute('data-target'));
|
||
const duration = 2000;
|
||
const step = target / (duration / 16);
|
||
let current = 0;
|
||
const timer = setInterval(() => {
|
||
current += step;
|
||
if (current >= target) {
|
||
counter.textContent = target;
|
||
clearInterval(timer);
|
||
} else {
|
||
counter.textContent = Math.floor(current);
|
||
}
|
||
}, 16);
|
||
});
|
||
counterObserver.unobserve(entry.target);
|
||
}
|
||
});
|
||
}, { threshold: 0.5 });
|
||
|
||
const statsSection = document.querySelector('.hero-stats');
|
||
if (statsSection) counterObserver.observe(statsSection);
|
||
|
||
// Set min date for checkin to today
|
||
const today = new Date().toISOString().split('T')[0];
|
||
const checkinInput = document.querySelector('[name="checkin"]');
|
||
const checkoutInput = document.querySelector('[name="checkout"]');
|
||
if (checkinInput) checkinInput.min = today;
|
||
if (checkoutInput) checkoutInput.min = today;
|
||
|
||
checkinInput.addEventListener('change', function() {
|
||
if (this.value) {
|
||
var next = new Date(this.value);
|
||
next.setDate(next.getDate() + 1);
|
||
checkoutInput.min = next.toISOString().split('T')[0];
|
||
if (checkoutInput.value && checkoutInput.value <= this.value) {
|
||
checkoutInput.value = '';
|
||
}
|
||
}
|
||
updateNightsCount();
|
||
});
|
||
|
||
function updateNightsCount() {
|
||
var ci = checkinInput.value, co = checkoutInput.value;
|
||
var el = document.getElementById('nightsCount');
|
||
if (ci && co && el) {
|
||
var n = Math.ceil((new Date(co) - new Date(ci)) / 86400000);
|
||
if (n > 0) el.textContent = n + ' ' + (n === 1 ? 'ночь' : n < 5 ? 'ночи' : 'ночей');
|
||
}
|
||
}
|
||
|
||
// Booking modal - set room name
|
||
document.querySelectorAll('.btn-book').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const room = this.getAttribute('data-room');
|
||
document.getElementById('selectedRoom').value = room;
|
||
const maxGuests = parseInt(this.getAttribute('data-max-guests')) || ROOM_MAX_GUESTS[room] || 4;
|
||
updateGuestOptionsFallback(room);
|
||
hidePriceInfo();
|
||
});
|
||
});
|
||
|
||
function updateGuestOptionsDynamic(roomType, maxGuests) {
|
||
const guestsSelect = document.querySelector('[name="guests"]');
|
||
if (!guestsSelect) return;
|
||
|
||
let options = [];
|
||
for (let i = 1; i <= maxGuests; i++) {
|
||
const key = 'booking.guest_' + i;
|
||
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
|
||
options.push(`<option value="${i}">${text}</option>`);
|
||
}
|
||
|
||
guestsSelect.innerHTML = options.join('');
|
||
}
|
||
|
||
window.updateRoomPricesData = function(prices, maxGuests) {
|
||
Object.assign(ROOM_PRICES, prices);
|
||
Object.assign(ROOM_MAX_GUESTS, maxGuests);
|
||
};
|
||
|
||
function updateGuestOptions(room) {
|
||
const guestsSelect = document.querySelector('[name="guests"]');
|
||
if (!guestsSelect) return;
|
||
|
||
const maxGuests = ROOM_MAX_GUESTS[room] || 4;
|
||
let options = [];
|
||
for (let i = 1; i <= maxGuests; i++) {
|
||
const key = 'booking.guest_' + i;
|
||
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
|
||
options.push(`<option value="${i}">${text}</option>`);
|
||
}
|
||
|
||
guestsSelect.innerHTML = options.join('');
|
||
}
|
||
|
||
function updateGuestOptionsFallback(room) {
|
||
const guestsSelect = document.querySelector('[name="guests"]');
|
||
if (!guestsSelect) return;
|
||
|
||
const options2x = [
|
||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||
{ value: 2, text: rt('booking.guest_2', '2 гостя') }
|
||
];
|
||
|
||
const options3x = [
|
||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||
{ value: 2, text: rt('booking.guest_2', '2 гостя') },
|
||
{ value: 3, text: rt('booking.guest_3', '3 гостя') }
|
||
];
|
||
|
||
const optionsDefault = [
|
||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||
{ value: 2, text: rt('booking.guest_2', '2 гостя') },
|
||
{ value: 3, text: rt('booking.guest_3', '3 гостя') },
|
||
{ value: 4, text: rt('booking.guest_4', '4 гостя') }
|
||
];
|
||
|
||
let options = optionsDefault;
|
||
if (room === '2x-местный') options = options2x;
|
||
else if (room === '3х-местный') options = options3x;
|
||
|
||
guestsSelect.innerHTML = options.map(o => `<option value="${o.value}">${o.text}</option>`).join('');
|
||
}
|
||
|
||
function hideGuestOptionsUpdate() {}
|
||
|
||
// Price calculation
|
||
const ROOM_PRICES = { '2x-местный': 1500, '3х-местный': 2000, 'Семейный': 3000, 'Люкс': 4500 };
|
||
const ROOM_MAX_GUESTS = { '2x-местный': 2, '3х-местный': 3, 'Семейный': 4, 'Люкс': 4 };
|
||
let currentPromocodeData = null;
|
||
|
||
function calculateNights(checkin, checkout) {
|
||
const ci = new Date(checkin);
|
||
const co = new Date(checkout);
|
||
return Math.ceil((co - ci) / (1000 * 60 * 60 * 24));
|
||
}
|
||
|
||
function validateBookingDates() {
|
||
const checkinInput = document.querySelector('[name="checkin"]');
|
||
const checkoutInput = document.querySelector('[name="checkout"]');
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
|
||
if (checkinInput.value) {
|
||
const checkinDate = new Date(checkinInput.value);
|
||
if (checkinDate < today) {
|
||
checkinInput.setCustomValidity(rt('booking.checkin_past', 'Дата заезда не может быть в прошлом'));
|
||
} else {
|
||
checkinInput.setCustomValidity('');
|
||
}
|
||
}
|
||
|
||
if (checkinInput.value && checkoutInput.value) {
|
||
const checkinDate = new Date(checkinInput.value);
|
||
const checkoutDate = new Date(checkoutInput.value);
|
||
|
||
if (checkoutDate <= checkinDate) {
|
||
checkoutInput.setCustomValidity(rt('booking.checkout_before_checkin', 'Дата выезда должна быть позже даты заезда'));
|
||
} else {
|
||
checkoutInput.setCustomValidity('');
|
||
}
|
||
}
|
||
|
||
return checkinInput.checkValidity() && checkoutInput.checkValidity();
|
||
}
|
||
|
||
document.querySelector('[name="checkin"]').addEventListener('change', function() { validateBookingDates(); if (typeof checkRoomAvailability === 'function') checkRoomAvailability(); updateNightsCount(); });
|
||
document.querySelector('[name="checkout"]').addEventListener('change', function() { validateBookingDates(); if (typeof checkRoomAvailability === 'function') checkRoomAvailability(); updateNightsCount(); });
|
||
|
||
function updatePriceDisplay(basePrice, discountPercent, discountAmount, totalPrice) {
|
||
document.getElementById('basePriceDisplay').textContent = basePrice + ' ₽';
|
||
document.getElementById('discountPercentDisplay').textContent = discountPercent;
|
||
document.getElementById('discountAmountDisplay').textContent = '-' + discountAmount + ' ₽';
|
||
document.getElementById('totalPriceDisplay').textContent = totalPrice + ' ₽';
|
||
document.getElementById('priceInfo').style.display = 'block';
|
||
}
|
||
|
||
function hidePriceInfo() {
|
||
document.getElementById('priceInfo').style.display = 'none';
|
||
currentPromocodeData = null;
|
||
}
|
||
|
||
function getFormData() {
|
||
return {
|
||
room: document.getElementById('selectedRoom').value,
|
||
checkin: document.querySelector('[name="checkin"]').value,
|
||
checkout: document.querySelector('[name="checkout"]').value,
|
||
promocode: document.getElementById('promocodeInput').value.trim()
|
||
};
|
||
}
|
||
|
||
async function checkPromocode() {
|
||
const { room, checkin, checkout, promocode } = getFormData();
|
||
if (!room || !checkin || !checkout) {
|
||
hidePriceInfo();
|
||
return;
|
||
}
|
||
|
||
const guests = parseInt(document.querySelector('[name="guests"]').value) || 1;
|
||
const basePrice = ROOM_PRICES[room] ? ROOM_PRICES[room] * guests * calculateNights(checkin, checkout) : 0;
|
||
|
||
if (!promocode) {
|
||
updatePriceDisplay(basePrice, 0, 0, basePrice);
|
||
currentPromocodeData = null;
|
||
hidePromocodeError();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/promocodes/validate', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ code: promocode, room_type: room, checkin, checkout, guests })
|
||
});
|
||
const data = await response.json();
|
||
|
||
if (data.valid) {
|
||
currentPromocodeData = data;
|
||
updatePriceDisplay(data.base_price, data.discount_percent, data.discount_amount, data.total_price);
|
||
hidePromocodeError();
|
||
} else {
|
||
currentPromocodeData = null;
|
||
updatePriceDisplay(basePrice, 0, 0, basePrice);
|
||
showPromocodeError(rt('booking.promocode_invalid', 'Промокод не найден или срок его действия истёк'));
|
||
}
|
||
} catch (error) {
|
||
console.error('Promocode validation error:', error);
|
||
showPromocodeError(rt('booking.promocode_error', 'Ошибка проверки промокода'));
|
||
}
|
||
}
|
||
|
||
function showPromocodeError(message) {
|
||
const errorDiv = document.getElementById('promocodeError');
|
||
if (errorDiv) {
|
||
errorDiv.textContent = '✖ ' + message;
|
||
errorDiv.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function hidePromocodeError() {
|
||
const errorDiv = document.getElementById('promocodeError');
|
||
if (errorDiv) {
|
||
errorDiv.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
document.getElementById('checkPromocodeBtn').addEventListener('click', checkPromocode);
|
||
document.getElementById('promocodeInput').addEventListener('blur', checkPromocode);
|
||
document.querySelector('[name="checkin"]').addEventListener('change', checkPromocode);
|
||
document.querySelector('[name="checkout"]').addEventListener('change', checkPromocode);
|
||
document.querySelector('[name="guests"]').addEventListener('change', checkPromocode);
|
||
document.querySelectorAll('.btn-book').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
setTimeout(checkPromocode, 100);
|
||
});
|
||
});
|
||
|
||
// Form submission
|
||
document.getElementById('bookingForm').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
const btn = this.querySelector('.btn-submit-booking');
|
||
const originalText = btn.innerHTML;
|
||
|
||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + rt('booking.sending', 'Отправка...');
|
||
btn.disabled = true;
|
||
|
||
const formData = {
|
||
name: this.querySelector('[name="name"]').value,
|
||
phone: this.querySelector('[name="phone"]').value,
|
||
adults: parseInt(this.querySelector('[name="guests"]').value),
|
||
children: 0,
|
||
checkin: this.querySelector('[name="checkin"]').value,
|
||
checkout: this.querySelector('[name="checkout"]').value,
|
||
wishes: this.querySelector('[name="wishes"]').value,
|
||
room: document.getElementById('selectedRoom').value,
|
||
room_id: parseInt(document.getElementById('selectedRoomId').value) || null,
|
||
promocode: document.getElementById('promocodeInput').value.trim() || null
|
||
};
|
||
|
||
try {
|
||
const response = await fetch('/api/bookings', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(formData)
|
||
});
|
||
|
||
if (response.ok) {
|
||
btn.innerHTML = '<i class="fas fa-check me-2"></i>' + rt('booking.success_sent', 'Заявка отправлена Рауфу Алексеевичу!');
|
||
btn.style.background = '#25d366';
|
||
this.reset();
|
||
hidePriceInfo();
|
||
setTimeout(() => {
|
||
btn.innerHTML = originalText;
|
||
btn.style.background = '';
|
||
btn.disabled = false;
|
||
bootstrap.Modal.getInstance(document.getElementById('bookingModal')).hide();
|
||
}, 2500);
|
||
} else {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || rt('booking.server_error', 'Ошибка сервера'));
|
||
}
|
||
} catch (error) {
|
||
console.error('Booking error:', error);
|
||
btn.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + rt('booking.send_error', 'Ошибка отправки');
|
||
btn.style.background = '#c9302c';
|
||
setTimeout(() => {
|
||
btn.innerHTML = originalText;
|
||
btn.style.background = '';
|
||
btn.disabled = false;
|
||
}, 3000);
|
||
}
|
||
});
|
||
|
||
// Smooth scroll for nav links
|
||
document.querySelectorAll('a[href^="#"]:not([data-phone])').forEach(anchor => {
|
||
anchor.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const target = document.querySelector(this.getAttribute('href'));
|
||
if (target) {
|
||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
// Close mobile menu
|
||
const navCollapse = document.querySelector('.navbar-collapse');
|
||
if (navCollapse.classList.contains('show')) {
|
||
bootstrap.Collapse.getInstance(navCollapse).hide();
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Hero media slideshow
|
||
let heroSlides = [];
|
||
let heroCurrentIndex = 0;
|
||
let heroTimer = null;
|
||
let heroPaused = false;
|
||
|
||
async function initHeroSlideshow() {
|
||
const layer = document.getElementById('heroBgLayer');
|
||
if (!layer) return;
|
||
|
||
try {
|
||
const resp = await fetch('/api/hero');
|
||
if (resp.ok) {
|
||
const media = await resp.json();
|
||
if (media && media.length > 0) {
|
||
heroSlides = media;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('Hero API not available, using default image');
|
||
}
|
||
|
||
if (heroSlides.length === 0) return;
|
||
|
||
layer.innerHTML = '';
|
||
const defaultImg = layer.querySelector('.hero-bg-default');
|
||
if (defaultImg) defaultImg.remove();
|
||
|
||
heroSlides.forEach((item, i) => {
|
||
if (item.type === 'image') {
|
||
const img = document.createElement('img');
|
||
img.className = 'hero-bg hero-bg-slide';
|
||
img.src = item.path;
|
||
img.alt = 'Hotel 777';
|
||
img.dataset.index = i;
|
||
if (i === 0) img.classList.add('active');
|
||
layer.appendChild(img);
|
||
} else if (item.type === 'video') {
|
||
const isYouTube = item.path.includes('youtube') || item.path.includes('youtu.be');
|
||
if (isYouTube) {
|
||
let embedUrl = item.path;
|
||
if (item.path.includes('watch?v=')) {
|
||
const videoId = item.path.split('watch?v=')[1]?.split('&')[0];
|
||
embedUrl = 'https://www.youtube.com/embed/' + videoId + '?autoplay=0&controls=0&mute=1&loop=0&playlist=' + videoId + '&enablejsapi=1';
|
||
}
|
||
if (item.path.includes('youtu.be/')) {
|
||
const videoId = item.path.split('youtu.be/')[1]?.split('?')[0];
|
||
embedUrl = 'https://www.youtube.com/embed/' + videoId + '?autoplay=0&controls=0&mute=1&loop=0&playlist=' + videoId + '&enablejsapi=1';
|
||
}
|
||
if (!embedUrl.includes('enablejsapi=1')) {
|
||
embedUrl += (embedUrl.includes('?') ? '&' : '?') + 'enablejsapi=1';
|
||
}
|
||
const iframe = document.createElement('iframe');
|
||
iframe.src = embedUrl;
|
||
iframe.allow = 'autoplay; encrypted-media';
|
||
iframe.allowFullscreen = false;
|
||
iframe.dataset.index = i;
|
||
iframe.dataset.type = 'youtube';
|
||
layer.appendChild(iframe);
|
||
} else {
|
||
const video = document.createElement('video');
|
||
video.src = item.path;
|
||
video.muted = true;
|
||
video.playsInline = true;
|
||
video.dataset.index = i;
|
||
video.dataset.type = 'video';
|
||
if (i === 0) video.classList.add('active');
|
||
layer.appendChild(video);
|
||
video.addEventListener('ended', () => onVideoEnded(i));
|
||
}
|
||
}
|
||
});
|
||
|
||
startHeroSlideshow();
|
||
}
|
||
|
||
function startHeroSlideshow() {
|
||
if (heroSlides.length <= 1) {
|
||
const firstSlide = document.querySelector('#heroBgLayer .active, #heroBgLayer img[data-index="0"], #heroBgLayer video[data-index="0"]');
|
||
if (firstSlide && firstSlide.tagName === 'VIDEO') {
|
||
firstSlide.play().catch(() => {});
|
||
}
|
||
return;
|
||
}
|
||
|
||
showHeroSlide(0);
|
||
}
|
||
|
||
function showHeroSlide(index) {
|
||
if (heroPaused) return;
|
||
|
||
const layer = document.getElementById('heroBgLayer');
|
||
if (!layer) return;
|
||
|
||
const allSlides = layer.querySelectorAll('img, video, iframe');
|
||
allSlides.forEach(s => s.classList.remove('active'));
|
||
|
||
const slide = layer.querySelector(`[data-index="${index}"]`);
|
||
if (!slide) return;
|
||
|
||
// Reset transform on all slides (for parallax cleanup)
|
||
allSlides.forEach(s => { if (s.tagName === 'IMG' || s.tagName === 'VIDEO') s.style.transform = ''; });
|
||
|
||
slide.classList.add('active');
|
||
heroCurrentIndex = index;
|
||
|
||
if (heroTimer) clearTimeout(heroTimer);
|
||
|
||
if (slide.tagName === 'VIDEO') {
|
||
slide.currentTime = 0;
|
||
slide.play().catch(() => {});
|
||
// 'ended' event will trigger next slide
|
||
} else if (slide.dataset.type === 'youtube') {
|
||
// Post message to YouTube iframe to play
|
||
const src = new URL(slide.src);
|
||
src.searchParams.set('autoplay', '1');
|
||
slide.src = src.toString();
|
||
// Wait ~30 seconds for YouTube video, then advance (no reliable end event without API)
|
||
heroTimer = setTimeout(() => goToNextSlide(), 30000);
|
||
} else {
|
||
// Image: show for 10 seconds
|
||
heroTimer = setTimeout(() => goToNextSlide(), 10000);
|
||
}
|
||
}
|
||
|
||
function onVideoEnded(index) {
|
||
if (heroCurrentIndex !== index) return;
|
||
goToNextSlide();
|
||
}
|
||
|
||
function goToNextSlide() {
|
||
if (heroPaused || heroSlides.length === 0) return;
|
||
const nextIndex = (heroCurrentIndex + 1) % heroSlides.length;
|
||
|
||
const prevSlide = document.querySelector(`#heroBgLayer [data-index="${heroCurrentIndex}"]`);
|
||
if (prevSlide && prevSlide.tagName === 'VIDEO') {
|
||
prevSlide.pause();
|
||
}
|
||
if (prevSlide && prevSlide.dataset.type === 'youtube') {
|
||
prevSlide.src = prevSlide.src.replace('autoplay=1', 'autoplay=0');
|
||
}
|
||
|
||
showHeroSlide(nextIndex);
|
||
}
|
||
|
||
document.addEventListener('visibilitychange', () => {
|
||
heroPaused = document.hidden;
|
||
if (!heroPaused && heroSlides.length > 1) {
|
||
showHeroSlide(heroCurrentIndex);
|
||
}
|
||
});
|
||
|
||
// Parallax effect on hero
|
||
window.addEventListener('scroll', () => {
|
||
const hero = document.querySelector('#heroBgLayer .active');
|
||
if (hero && (hero.tagName === 'IMG' || hero.tagName === 'VIDEO')) {
|
||
const scrolled = window.scrollY;
|
||
hero.style.transform = `scale(${1 + scrolled * 0.0001}) translateY(${scrolled * 0.3}px)`;
|
||
} else {
|
||
const fallback = document.querySelector('.hero-bg-default');
|
||
if (fallback) {
|
||
const scrolled = window.scrollY;
|
||
fallback.style.transform = `scale(${1 + scrolled * 0.0001}) translateY(${scrolled * 0.3}px)`;
|
||
}
|
||
}
|
||
});
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
initHeroSlideshow();
|
||
var cy = document.getElementById('copyrightYear');
|
||
if (cy) cy.textContent = new Date().getFullYear();
|
||
var navToggler = document.querySelector('.navbar-toggler');
|
||
var navMenu = document.getElementById('navMenu');
|
||
if (navToggler && navMenu) {
|
||
navMenu.addEventListener('show.bs.collapse', () => navToggler.setAttribute('aria-expanded', 'true'));
|
||
navMenu.addEventListener('hide.bs.collapse', () => navToggler.setAttribute('aria-expanded', 'false'));
|
||
}
|
||
});
|
||
|
||
// Gallery Lightbox
|
||
function openLightbox(src, caption) {
|
||
const lb = document.getElementById('galleryLightbox');
|
||
const img = document.getElementById('galleryLightboxImg');
|
||
const cap = document.getElementById('galleryLightboxCaption');
|
||
img.src = src;
|
||
img.alt = caption || 'Фото отеля Hotel 777';
|
||
cap.textContent = caption || '';
|
||
lb.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeLightbox() {
|
||
const lb = document.getElementById('galleryLightbox');
|
||
lb.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
closeLightbox();
|
||
}
|
||
});
|
||
|
||
async function loadActivities() {
|
||
const container = document.getElementById('activitiesContainer');
|
||
if (!container) return;
|
||
|
||
const lang = (window.I18n && I18n.currentLang) ? I18n.currentLang : 'ru';
|
||
|
||
try {
|
||
const res = await fetch('/api/activities?lang=' + lang);
|
||
if (!res.ok) throw new Error('Failed to load activities');
|
||
const activities = await res.json();
|
||
|
||
if (!activities.length) {
|
||
container.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = activities.map(a => {
|
||
const imgSrc = a.image_path || '';
|
||
const iconHtml = a.icon ? `<div class="activity-icon"><i class="${a.icon}"></i></div>` : '';
|
||
const onclickAttr = imgSrc ? `onclick="openLightbox('${imgSrc}', '${(a.title || '').replace(/'/g, "\\'")}')"` : '';
|
||
return `<div class="col-lg-4 animate-on-scroll">
|
||
<div class="activity-card" ${onclickAttr}>
|
||
${imgSrc ? `<img src="${imgSrc}" alt="${a.title || ''}">` : ''}
|
||
<div class="activity-overlay">
|
||
${iconHtml}
|
||
<h3 class="activity-title">${a.title || ''}</h3>
|
||
<p class="activity-desc">${a.desc || ''}</p>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
|
||
container.querySelectorAll('.animate-on-scroll').forEach(el => {
|
||
observer.observe(el);
|
||
});
|
||
} catch (err) {
|
||
console.error('Activities load error:', err);
|
||
}
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', loadActivities);
|
||
} else {
|
||
loadActivities();
|
||
}
|