d
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
.env
|
||||
data
|
||||
BIN
data/bookings.db
BIN
data/bookings.db
Binary file not shown.
@@ -26,6 +26,6 @@ networks:
|
||||
applications:
|
||||
external: true
|
||||
# docker network create applications
|
||||
# docker-compose up -d
|
||||
# docker-compose up -d --build
|
||||
# docker-compose build --no-cache && docker-compose up -d
|
||||
# docker compose up -d
|
||||
# docker compose up -d --build
|
||||
# docker compose build --no-cache && docker compose up -d
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
<title>Hotel 777 | Отдых в Абхазии</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="img/favicon.ico" type="image/x-icon">
|
||||
<!-- Яндекс.Карты API (замените YOUR_API_KEY на реальный ключ) -->
|
||||
<!-- Яндекс.Карты временно отключены (закомментированы)
|
||||
<script src="https://api-maps.yandex.ru/2.1/?apikey=YOUR_YANDEX_MAP_API_KEY&lang=ru_RU"></script>
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -38,7 +39,7 @@
|
||||
<!-- Секция "О нас" (динамически через about.js) -->
|
||||
<section id="about" class="white-bg"></section>
|
||||
|
||||
<!-- Секция "Кухня" -->
|
||||
<!-- Секция "Кухня" (с карточками питания) -->
|
||||
<section id="food">
|
||||
<h2 class="section-title animate" data-i18n="food_title">Вкус Абхазии</h2>
|
||||
<div class="about-grid">
|
||||
@@ -48,22 +49,35 @@
|
||||
<div class="about-text animate delay-2">
|
||||
<h3 data-i18n="food_subtitle">Домашняя кухня из местных продуктов</h3>
|
||||
<p data-i18n="food_text">Почувствуйте настоящее гостеприимство! Мы готовим из того, что выросло прямо здесь: свежайший сыр сулугуни, ароматная абыста, сочные овощи с грядки и домашнее вино.</p>
|
||||
<div class="food-benefits">
|
||||
<p><strong>✨ Для наших гостей:</strong></p>
|
||||
<ul>
|
||||
<li>🍽️ Учитываем пожелания по питанию</li>
|
||||
<li>👨🍳 Приготовим блюдо для Вас – спросите у шефа</li>
|
||||
<li>🕒 Завтрак: с 8:30 до 10:00</li>
|
||||
<li>🕒 Обед: с 12:00 до 14:00</li>
|
||||
<li>🕒 Ужин: с 19:00 до 21:00</li>
|
||||
</ul>
|
||||
<p class="food-note">📢 <em>Сообщите администратору о любых предпочтениях при заселении – мы всё организуем!</em></p>
|
||||
|
||||
<div class="facts-grid food-features">
|
||||
<div class="fact-card">
|
||||
<div class="fact-icon">🍽️</div>
|
||||
<div class="fact-text" data-i18n="food_request">Учитываем пожелания по питанию</div>
|
||||
</div>
|
||||
<div class="fact-card">
|
||||
<div class="fact-icon">👨🍳</div>
|
||||
<div class="fact-text" data-i18n="food_chef">Приготовим блюдо для Вас – спросите у шефа</div>
|
||||
</div>
|
||||
<div class="fact-card">
|
||||
<div class="fact-icon">🕒</div>
|
||||
<div class="fact-text" data-i18n="food_breakfast">Завтрак: с 8:30 до 10:00</div>
|
||||
</div>
|
||||
<div class="fact-card">
|
||||
<div class="fact-icon">🕒</div>
|
||||
<div class="fact-text" data-i18n="food_lunch">Обед: с 12:00 до 14:00</div>
|
||||
</div>
|
||||
<div class="fact-card">
|
||||
<div class="fact-icon">🕒</div>
|
||||
<div class="fact-text" data-i18n="food_dinner">Ужин: с 19:00 до 21:00</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="food-note" data-i18n="food_note">📢 <em>Сообщите администратору о любых предпочтениях при заселении – мы всё организуем!</em></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Секция "Где мы" (динамически через location.js) -->
|
||||
<!-- Секция "Где мы" (без карты, только указатель и города) -->
|
||||
<section id="location" class="white-bg"></section>
|
||||
|
||||
<!-- Секция "Бронирование" -->
|
||||
@@ -76,7 +90,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone" data-i18n="label_phone">Номер телефона</label>
|
||||
<input type="tel" id="phone" name="phone" required placeholder="+7 (___) ___-__-__">
|
||||
<input type="tel" id="phone" name="phone" required placeholder="+ ___ (___) ___-__-__">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="adults" data-i18n="book_adults">Количество взрослых</label>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Глобальные переводы (расширены для location, about, booking)
|
||||
// Глобальные переводы (расширены для location, about, booking, food)
|
||||
window.translations = {
|
||||
ru: {
|
||||
nav_about: "О нас",
|
||||
@@ -19,6 +19,12 @@ window.translations = {
|
||||
food_title: "Вкус Абхазии",
|
||||
food_subtitle: "Домашняя кухня из местных продуктов",
|
||||
food_text: "Почувствуйте гостеприимство! Свежайший сыр сулугуни, мамалыга, овощи с грядки и домашнее вино.",
|
||||
food_request: "🍽️ Учитываем пожелания по питанию",
|
||||
food_chef: "👨🍳 Приготовим блюдо для Вас – спросите у шефа",
|
||||
food_breakfast: "🕒 Завтрак: с 8:30 до 10:00",
|
||||
food_lunch: "🕒 Обед: с 12:00 до 14:00",
|
||||
food_dinner: "🕒 Ужин: с 19:00 до 21:00",
|
||||
food_note: "📢 Сообщите администратору о любых предпочтениях при заселении – мы всё организуем!",
|
||||
loc_title: "Удобное расположение",
|
||||
book_title: "Забронировать отдых",
|
||||
label_name: "Ваше имя",
|
||||
@@ -41,7 +47,6 @@ window.translations = {
|
||||
loc_city_primorsk: "Приморск",
|
||||
loc_city_gudauta: "Гудаута",
|
||||
loc_km: "км",
|
||||
// Новые поля для бронирования
|
||||
book_adults: "Количество взрослых",
|
||||
book_children: "Количество детей (до 12 лет)",
|
||||
book_checkin: "Дата заезда",
|
||||
@@ -49,7 +54,6 @@ window.translations = {
|
||||
please_fill_all: "Пожалуйста, заполните все обязательные поля",
|
||||
booking_success: "Заявка отправлена! Мы свяжемся с вами.",
|
||||
booking_error: "Ошибка отправки. Попробуйте позже.",
|
||||
// Факты о городах (русский)
|
||||
loc_facts_sukhum: [
|
||||
"🏛️ Один из древнейших городов мира, основан в VI веке до н.э.",
|
||||
"🌿 Ботанический сад — один из старейших на Кавказе (основан в 1838 году).",
|
||||
@@ -106,6 +110,12 @@ window.translations = {
|
||||
food_title: "Taste of Abkhazia",
|
||||
food_subtitle: "Homemade cuisine from local ingredients",
|
||||
food_text: "Feel the hospitality! Suluguni cheese, mamalyga, garden vegetables, and homemade wine.",
|
||||
food_request: "🍽️ We accommodate dietary preferences",
|
||||
food_chef: "👨🍳 We'll cook a dish for you – ask the chef",
|
||||
food_breakfast: "🕒 Breakfast: 8:30 – 10:00",
|
||||
food_lunch: "🕒 Lunch: 12:00 – 14:00",
|
||||
food_dinner: "🕒 Dinner: 19:00 – 21:00",
|
||||
food_note: "📢 Tell the administrator about any preferences at check-in – we'll arrange everything!",
|
||||
loc_title: "Convenient location",
|
||||
book_title: "Book your holiday",
|
||||
label_name: "Your name",
|
||||
@@ -128,7 +138,6 @@ window.translations = {
|
||||
loc_city_primorsk: "Primorsk",
|
||||
loc_city_gudauta: "Gudauta",
|
||||
loc_km: "km",
|
||||
// New booking fields
|
||||
book_adults: "Number of adults",
|
||||
book_children: "Number of children (under 12)",
|
||||
book_checkin: "Check-in date",
|
||||
@@ -136,7 +145,6 @@ window.translations = {
|
||||
please_fill_all: "Please fill all required fields",
|
||||
booking_success: "Request sent! We will contact you.",
|
||||
booking_error: "Submission error. Please try again later.",
|
||||
// City facts (English)
|
||||
loc_facts_sukhum: [
|
||||
"🏛️ One of the oldest cities in the world, founded in the 6th century BC.",
|
||||
"🌿 Botanical Garden – one of the oldest in the Caucasus (founded 1838).",
|
||||
@@ -193,6 +201,12 @@ window.translations = {
|
||||
food_title: "Аԥсны аҵәа",
|
||||
food_subtitle: "Аџьа ҭаацәарантәи аҭыԥтә афасаҟәақәа рыла",
|
||||
food_text: "Ашьааҭра шәаазыр! Ибжьа асулугуни, абаста, аҵиаа ҵаҟатәи нас аҭаацәарантәи аҵаа.",
|
||||
food_request: "🍽️ Ашәхәаҭаҩра аиҭаҵра шәаазырҵоит",
|
||||
food_chef: "👨🍳 Шәы жәҩаны ахәылҵа ишәаазырҵоит – ашеф ишәааҽазыр",
|
||||
food_breakfast: "🕒 Ашәхә: 8:30 – 10:00 азы",
|
||||
food_lunch: "🕒 Абжьара: 12:00 – 14:00",
|
||||
food_dinner: "🕒 Аҵх: 19:00 – 21:00",
|
||||
food_note: "📢 Администратор ишәааҽазыр ахынчә қәыԥсқәа – ҳара иҭабуп!",
|
||||
loc_title: "Каратә аҭыԥкаара",
|
||||
book_title: "Ахыҧсыра аҭагалара",
|
||||
label_name: "Шәхы",
|
||||
@@ -215,7 +229,6 @@ window.translations = {
|
||||
loc_city_primorsk: "Приморск",
|
||||
loc_city_gudauta: "Гәдоуҭа",
|
||||
loc_km: "км",
|
||||
// New booking fields in Abkhaz
|
||||
book_adults: "Аҧшәмаҭааҩцәа рхыҧхьаӡара",
|
||||
book_children: "Аҵлақәа рхыҧхьаӡара (12 шықәса рҟынӡа)",
|
||||
book_checkin: "Аҭагалара амш",
|
||||
@@ -223,7 +236,6 @@ window.translations = {
|
||||
please_fill_all: "Ишәҧшәа, азықәҭа змоу аҭыԥқәа рымч",
|
||||
booking_success: "Азаявка ацәыҵит! Ҳара шәыҟазаалак шәааҽазыр.",
|
||||
booking_error: "Ацәыҵра аҟаҵаразы алшара. Ушәа агәаҧш, ушәа шәааҽаз.",
|
||||
// City facts Abkhaz (simplified)
|
||||
loc_facts_sukhum: [
|
||||
"🏛️ Адунеи аиҳабылакьықәа руакы, VI ашәышықәса рахь нҵа иҟоуп.",
|
||||
"🌿 Аботаникатә сад — Акавказ аиҳабылакьықәа руакы (1838 ш.).",
|
||||
@@ -263,7 +275,7 @@ window.translations = {
|
||||
}
|
||||
};
|
||||
|
||||
// Функция закрытия модального окна (глобальная, используется также в location.js)
|
||||
// Функция закрытия модального окна
|
||||
window.closeModal = function() {
|
||||
const modal = document.getElementById('cityModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
@@ -277,42 +289,31 @@ window.acceptCookies = function() {
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Циклическая смена фона Hero
|
||||
// --- Установка статичного фона для hero (первое изображение) ---
|
||||
const hero = document.querySelector('.hero');
|
||||
const images = ['img/h777.webp', 'img/h777o.webp', 'img/h777l.webp', 'img/h777z.webp'];
|
||||
let currentImg = 0;
|
||||
|
||||
const setHeroBackground = () => {
|
||||
const img = new Image();
|
||||
img.src = images[currentImg];
|
||||
img.onload = () => {
|
||||
hero.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('${img.src}')`;
|
||||
};
|
||||
const firstImage = 'img/h777.webp';
|
||||
const img = new Image();
|
||||
img.src = firstImage;
|
||||
img.onload = () => {
|
||||
hero.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('${img.src}')`;
|
||||
};
|
||||
setHeroBackground();
|
||||
setInterval(() => {
|
||||
currentImg = (currentImg + 1) % images.length;
|
||||
setHeroBackground();
|
||||
}, 15000);
|
||||
// Ошибка 404 tiny.webp больше не возникает, так как мы не используем его в качестве фона по умолчанию
|
||||
|
||||
// Локализация основных элементов (с data-i18n)
|
||||
const langSelect = document.getElementById('langSwitch');
|
||||
let currentLang = localStorage.getItem('siteLang') || 'ru';
|
||||
|
||||
const updateText = (lang) => {
|
||||
// Обновляем элементы с data-i18n
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
if (window.translations[lang][key]) el.innerHTML = window.translations[lang][key];
|
||||
});
|
||||
// Обновляем плейсхолдеры
|
||||
document.querySelectorAll('[data-i18n-ph]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n-ph');
|
||||
if (window.translations[lang][key]) el.placeholder = window.translations[lang][key];
|
||||
});
|
||||
localStorage.setItem('siteLang', lang);
|
||||
|
||||
// Вызываем обновление динамических секций, если функции определены
|
||||
if (typeof window.updateLocationLanguage === 'function') {
|
||||
window.updateLocationLanguage(lang);
|
||||
}
|
||||
@@ -330,7 +331,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
setTimeout(() => document.getElementById('cookieBanner').classList.add('show'), 2000);
|
||||
}
|
||||
|
||||
// Форма бронирования с отправкой на сервер
|
||||
// Форма бронирования (без intl-tel-input)
|
||||
const bookingForm = document.getElementById('bookingForm');
|
||||
if (bookingForm) {
|
||||
bookingForm.onsubmit = async (e) => {
|
||||
@@ -373,13 +374,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Анимации при скролле
|
||||
const obs = new IntersectionObserver(entries => {
|
||||
entries.forEach(en => { if (en.isIntersecting) en.target.style.animationPlayState = 'running'; });
|
||||
});
|
||||
// Анимации при скролле (один IntersectionObserver)
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.animationPlayState = 'running';
|
||||
observer.unobserve(entry.target); // отключаем после появления
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1, rootMargin: '50px' });
|
||||
|
||||
document.querySelectorAll('.animate').forEach(el => {
|
||||
el.style.animationPlayState = 'paused';
|
||||
obs.observe(el);
|
||||
observer.observe(el);
|
||||
});
|
||||
|
||||
// Модальное окно: закрытие по крестику и по клику на фон
|
||||
|
||||
@@ -27,7 +27,6 @@ body {
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* ШАПКА */
|
||||
header {
|
||||
background: rgba(0, 18, 25, 0.95);
|
||||
color: var(--white);
|
||||
@@ -76,7 +75,6 @@ nav a:hover {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* HERO */
|
||||
.hero {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
@@ -86,7 +84,7 @@ nav a:hover {
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
padding: 0 20px;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('img/h777-tiny.webp') center/cover;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('img/h777.webp') center/cover;
|
||||
transition: background-image 1.5s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -96,7 +94,6 @@ nav a:hover {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* СЕКЦИИ */
|
||||
section {
|
||||
padding: 6rem 10%;
|
||||
}
|
||||
@@ -164,7 +161,6 @@ section {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Анимированная картинка-указатель */
|
||||
.location-img-animate {
|
||||
transition: transform 0.8s cubic-bezier(0.2, 0.9, 0.4, 1.1), opacity 0.8s ease;
|
||||
transform: scale(0.85);
|
||||
@@ -176,7 +172,6 @@ section {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ФОРМА */
|
||||
.booking-form {
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
@@ -236,7 +231,6 @@ section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* COOKIE & ANIMATIONS */
|
||||
.cookie-banner {
|
||||
position: fixed;
|
||||
bottom: -200px;
|
||||
@@ -298,7 +292,6 @@ footer {
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
/* Блок с расстояниями до городов (разноцветные карточки) */
|
||||
.location-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -307,27 +300,31 @@ footer {
|
||||
}
|
||||
|
||||
.location-card {
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
background: var(--white);
|
||||
padding: 1rem;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(0, 0, 0, 0.03);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.location-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 20px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.location-icon {
|
||||
font-size: 1.5rem;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.location-title {
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.location-distance {
|
||||
@@ -335,7 +332,6 @@ footer {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Кнопка показа карты */
|
||||
.show-map-btn {
|
||||
background: var(--primary);
|
||||
transition: 0.3s;
|
||||
@@ -346,7 +342,6 @@ footer {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Карта под картинкой (скрыта по умолчанию) */
|
||||
.map-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
@@ -363,7 +358,6 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
/* Стили для секции about (модерн) */
|
||||
.modern-about {
|
||||
gap: 3rem;
|
||||
}
|
||||
@@ -424,17 +418,28 @@ footer {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.food-features {
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.food-note {
|
||||
background: #fff8e7;
|
||||
padding: 0.8rem;
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.facts-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.about-text h3 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Модальное окно для фактов о городах */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
@@ -494,43 +499,3 @@ footer {
|
||||
border-left: 4px solid var(--secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
/* Блок с расстояниями до городов (карточки в стиле fact-card) */
|
||||
.location-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.location-card {
|
||||
background: var(--white); /* как у fact-card */
|
||||
padding: 1rem;
|
||||
border-radius: 20px; /* как у fact-card */
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(0, 0, 0, 0.03);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.location-card:hover {
|
||||
transform: translateY(-5px); /* как у fact-card */
|
||||
box-shadow: 0 20px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.location-icon {
|
||||
font-size: 2rem; /* увеличенная иконка, как .fact-icon */
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.location-title {
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
font-size: 0.9rem; /* под размер .fact-text */
|
||||
}
|
||||
|
||||
.location-distance {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
Reference in New Issue
Block a user