✨ Компактные hero секции и улучшенная инициализация БД
🎨 UI улучшения: - Уменьшена высота синих панелей с 100vh до 70vh на главной - Добавлен класс .compact (25vh) для всех остальных страниц - Улучшена адаптивность для мобильных устройств - Обновлены все шаблоны с hero секциями 🚀 Инфраструктура: - Автоматическая инициализация базы данных при деплое - Улучшены мокапные данные (больше отзывов, бронирований, сообщений) - Добавлены настройки сайта в базу данных - Создан скрипт автоматического деплоя deploy.sh 📦 Система сборки: - Обновлен .gitignore с полным покрытием файлов - Добавлена папка для загрузок с .gitkeep - Улучшен README с инструкциями по запуску - ES модули для инициализации базы данных 🐛 Исправления: - Совместимость с ES модулями в Node.js - Правильная обработка ошибок инициализации БД - Корректные SQL запросы для PostgreSQL
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white text-center py-5">
|
||||
<section class="hero-section compact bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white py-5">
|
||||
<section class="hero-section compact bg-primary text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white text-center py-5">
|
||||
<section class="hero-section compact bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white text-center py-5">
|
||||
<section class="hero-section compact bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
|
||||
316
views/guides/index-backup.ejs
Normal file
316
views/guides/index-backup.ejs
Normal file
@@ -0,0 +1,316 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section compact bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h1 class="display-4 fw-bold mb-4">Наши гиды</h1>
|
||||
<p class="lead">Профессиональные и опытные гиды сделают ваше путешествие незабываемым</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Guides Grid -->
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<% if (guides.length === 0) { %>
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-user-tie text-muted" style="font-size: 4rem;"></i>
|
||||
<h3 class="mt-3 text-muted">Гиды не найдены</h3>
|
||||
<p class="text-muted">В данный момент нет доступных гидов.</p>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="row">
|
||||
<% guides.forEach(guide => { %>
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card h-100 shadow-sm guide-card" data-guide-id="<%= guide.id %>" style="cursor: pointer; transition: transform 0.2s;">
|
||||
<% if (guide.image_url && guide.image_url.trim()) { %>
|
||||
<img src="<%= guide.image_url %>" class="card-img-top" alt="<%= guide.name %>" style="height: 250px; object-fit: cover;">
|
||||
<% } else { %>
|
||||
<img src="/images/placeholders/default-guide.svg" class="card-img-top" alt="<%= guide.name %>" style="height: 250px; object-fit: cover;">
|
||||
<% } %>
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0"><%= guide.name %></h5>
|
||||
<div class="rating-display" data-target-type="guide" data-target-id="<%= guide.id %>">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="rating-percentage text-warning fw-bold">
|
||||
<% if (guide.rating_percentage > 0) { %>
|
||||
<%= parseFloat(guide.rating_percentage).toFixed(0) %>%
|
||||
<% } else { %>
|
||||
0%
|
||||
<% } %>
|
||||
</span>
|
||||
<small class="text-muted ms-1">
|
||||
(<%= guide.likes || 0 %>/<%= (parseInt(guide.likes || 0) + parseInt(guide.dislikes || 0)) %>)
|
||||
</small>
|
||||
</div>
|
||||
<div class="rating-buttons mt-1">
|
||||
<button class="btn btn-sm btn-outline-success like-btn" data-rating="1">
|
||||
<i class="fas fa-thumbs-up"></i> <span class="like-count"><%= guide.likes || 0 %></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger dislike-btn ms-1" data-rating="-1">
|
||||
<i class="fas fa-thumbs-down"></i> <span class="dislike-count"><%= guide.dislikes || 0 %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<% if (guide.specialization === 'city') { %>
|
||||
<span class="badge bg-info">
|
||||
<i class="fas fa-city me-1"></i>Городские туры
|
||||
</span>
|
||||
<% } else if (guide.specialization === 'mountain') { %>
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-mountain me-1"></i>Горные походы
|
||||
</span>
|
||||
<% } else if (guide.specialization === 'fishing') { %>
|
||||
<span class="badge bg-primary">
|
||||
<i class="fas fa-fish me-1"></i>Рыбалка
|
||||
</span>
|
||||
<% } else { %>
|
||||
<span class="badge bg-secondary">
|
||||
<i class="fas fa-star me-1"></i>Универсальный
|
||||
</span>
|
||||
<% } %>
|
||||
|
||||
<% if (guide.experience > 0) { %>
|
||||
<span class="badge bg-warning text-dark ms-1">
|
||||
<i class="fas fa-medal me-1"></i><%= guide.experience %> лет опыта
|
||||
</span>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<% if (guide.bio) { %>
|
||||
<p class="card-text text-muted flex-grow-1"><%= guide.bio.length > 100 ? guide.bio.substring(0, 100) + '...' : guide.bio %></p>
|
||||
<% } %>
|
||||
|
||||
<div class="mt-auto">
|
||||
<% if (guide.languages) { %>
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-language me-1"></i>
|
||||
<%= guide.languages %>
|
||||
</small>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (guide.avg_rating) { %>
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
<i class="far fa-star text-muted"></i>
|
||||
<small class="text-muted ms-2">(<%= parseFloat(guide.avg_rating).toFixed(1) %>)</small>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="text-muted small">
|
||||
<i class="fas fa-calendar-check me-1"></i>
|
||||
<%= guide.booking_count || 0 %> туров проведено
|
||||
</div>
|
||||
<% if (guide.hourly_rate) { %>
|
||||
<div class="fw-bold text-primary">
|
||||
₩<%= parseInt(guide.hourly_rate || 0).toLocaleString('ko-KR') %>/час
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Guide Detail Modal -->
|
||||
<div class="modal fade" id="guideModal" tabindex="-1" aria-labelledby="guideModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="guideModalLabel">Информация о гиде</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="guideModalBody">
|
||||
<!-- Динамически загружаемая информация о гиде -->
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||
<button type="button" class="btn btn-primary" id="bookGuideBtn">
|
||||
<i class="fas fa-calendar-plus me-2"></i>Забронировать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.guide-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
|
||||
}
|
||||
|
||||
.rating-buttons .btn {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.rating-percentage {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.rating-buttons .btn.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.rating-buttons .btn:not(.active) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Обработчик клика по карточке гида
|
||||
$('.guide-card').on('click', function(e) {
|
||||
// Не открываем модальное окно при клике на кнопки рейтинга
|
||||
if ($(e.target).closest('.rating-buttons').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const guideId = $(this).data('guide-id');
|
||||
loadGuideDetails(guideId);
|
||||
$('#guideModal').modal('show');
|
||||
});
|
||||
|
||||
// Обработчики для кнопок лайк/дизлайк
|
||||
$('.like-btn, .dislike-btn').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
const $container = $(this).closest('.rating-display');
|
||||
const targetType = $container.data('target-type');
|
||||
const targetId = $container.data('target-id');
|
||||
const rating = parseInt($(this).data('rating'));
|
||||
|
||||
setRating(targetType, targetId, rating, $container);
|
||||
});
|
||||
});
|
||||
|
||||
function loadGuideDetails(guideId) {
|
||||
$('#guideModalBody').html(`
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$.get(`/guides/${guideId}`)
|
||||
.done(function(html) {
|
||||
$('#guideModalBody').html(html);
|
||||
$('#bookGuideBtn').data('guide-id', guideId);
|
||||
})
|
||||
.fail(function() {
|
||||
$('#guideModalBody').html(`
|
||||
<div class="text-center text-danger">
|
||||
<i class="fas fa-exclamation-triangle fa-2x mb-3"></i>
|
||||
<p>Ошибка загрузки информации о гиде</p>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
function setRating(targetType, targetId, rating, $container) {
|
||||
$.post(`/api/rating/${targetType}/${targetId}`, { rating: rating })
|
||||
.done(function(data) {
|
||||
if (data.success) {
|
||||
// Обновляем отображение рейтинга
|
||||
updateRatingDisplay($container, data.rating);
|
||||
|
||||
// Показываем уведомление
|
||||
showToast(data.message, 'success');
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
const error = xhr.responseJSON?.error || 'Ошибка при установке рейтинга';
|
||||
showToast(error, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function updateRatingDisplay($container, ratingData) {
|
||||
const percentage = parseFloat(ratingData.rating_percentage || 0);
|
||||
const likes = parseInt(ratingData.likes_count || 0);
|
||||
const dislikes = parseInt(ratingData.dislikes_count || 0);
|
||||
const total = likes + dislikes;
|
||||
|
||||
$container.find('.rating-percentage').text(percentage.toFixed(0) + '%');
|
||||
$container.find('.like-count').text(likes);
|
||||
$container.find('.dislike-count').text(dislikes);
|
||||
$container.find('small.text-muted').text(`(${likes}/${total})`);
|
||||
}
|
||||
|
||||
function showToast(message, type) {
|
||||
const bgClass = type === 'success' ? 'bg-success' : 'bg-danger';
|
||||
const toast = $(`
|
||||
<div class="toast align-items-center text-white ${bgClass} border-0" role="alert"
|
||||
style="position: fixed; top: 20px; right: 20px; z-index: 9999;">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$('body').append(toast);
|
||||
new bootstrap.Toast(toast[0]).show();
|
||||
|
||||
// Удаляем toast после скрытия
|
||||
toast.on('hidden.bs.toast', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar me-1"></i><%= guide.experience %> лет опыта
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-route me-1"></i><%= guide.route_count || 0 %> туров
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/guides/<%= guide.id %>" class="btn btn-primary w-100">
|
||||
<i class="fas fa-user me-1"></i>Подробнее
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container text-center">
|
||||
<h2 class="mb-4">Нужен индивидуальный подход?</h2>
|
||||
<p class="lead text-muted mb-4">Наши гиды готовы создать уникальный маршрут специально для вас!</p>
|
||||
<a href="/contact" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-envelope me-1"></i>Связаться с нами
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,12 +1,8 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h1 class="display-4 fw-bold mb-4">Наши гиды</h1>
|
||||
<p class="lead">Профессиональные и опытные гиды сделают ваше путешествие незабываемым</p>
|
||||
</div>
|
||||
</div>
|
||||
<section class="hero-section compact bg-gradient-primary text-white py-5">
|
||||
<div class="container text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">Наши профессиональные гиды</h1>
|
||||
<p class="lead">Опытные гиды для незабываемых путешествий по Корее</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -15,15 +11,15 @@
|
||||
<div class="container">
|
||||
<% if (guides.length === 0) { %>
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-user-tie text-muted" style="font-size: 4rem;"></i>
|
||||
<h3 class="mt-3 text-muted">Гиды не найдены</h3>
|
||||
<i class="fas fa-user-tie fa-3x text-muted mb-3"></i>
|
||||
<h3>Гиды не найдены</h3>
|
||||
<p class="text-muted">В данный момент нет доступных гидов.</p>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="row">
|
||||
<% guides.forEach(guide => { %>
|
||||
<% guides.forEach(function(guide) { %>
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card h-100 shadow-sm">
|
||||
<div class="card h-100 shadow-sm guide-card" data-guide-id="<%= guide.id %>" style="cursor: pointer; transition: transform 0.2s;">
|
||||
<% if (guide.image_url && guide.image_url.trim()) { %>
|
||||
<img src="<%= guide.image_url %>" class="card-img-top" alt="<%= guide.name %>" style="height: 250px; object-fit: cover;">
|
||||
<% } else { %>
|
||||
@@ -31,11 +27,39 @@
|
||||
<% } %>
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title"><%= guide.name %></h5>
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0"><%= guide.name %></h5>
|
||||
<div class="rating-display" data-target-type="guide" data-target-id="<%= guide.id %>">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="rating-percentage text-warning fw-bold">
|
||||
<%
|
||||
let percentage = parseFloat(guide.rating_percentage || 0);
|
||||
%>
|
||||
<%= Math.round(percentage) %>%
|
||||
</span>
|
||||
<small class="text-muted ms-1">
|
||||
<%
|
||||
let likes = parseInt(guide.likes || 0);
|
||||
let dislikes = parseInt(guide.dislikes || 0);
|
||||
let total = likes + dislikes;
|
||||
%>
|
||||
(<%= likes %>/<%= total %>)
|
||||
</small>
|
||||
</div>
|
||||
<div class="rating-buttons mt-1">
|
||||
<button class="btn btn-sm btn-outline-success like-btn" data-rating="1">
|
||||
<i class="fas fa-thumbs-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger dislike-btn" data-rating="-1">
|
||||
<i class="fas fa-thumbs-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<% if (guide.specialization === 'city') { %>
|
||||
<span class="badge bg-info">
|
||||
<span class="badge bg-primary">
|
||||
<i class="fas fa-city me-1"></i>Городские туры
|
||||
</span>
|
||||
<% } else if (guide.specialization === 'mountain') { %>
|
||||
@@ -43,14 +67,21 @@
|
||||
<i class="fas fa-mountain me-1"></i>Горные походы
|
||||
</span>
|
||||
<% } else if (guide.specialization === 'fishing') { %>
|
||||
<span class="badge bg-primary">
|
||||
<i class="fas fa-fish me-1"></i>Рыбалка
|
||||
<span class="badge bg-info">
|
||||
<i class="fas fa-fish me-1"></i>Морская рыбалка
|
||||
</span>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-briefcase me-1"></i>
|
||||
Опыт: <%= guide.experience || 0 %> лет
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<% if (guide.bio) { %>
|
||||
<p class="card-text text-muted flex-grow-1"><%= truncateText(guide.bio, 120) %></p>
|
||||
<p class="card-text text-muted flex-grow-1"><%= guide.bio.length > 100 ? guide.bio.substring(0, 100) + '...' : guide.bio %></p>
|
||||
<% } %>
|
||||
|
||||
<div class="mt-auto">
|
||||
@@ -63,29 +94,19 @@
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (guide.avg_rating) { %>
|
||||
<div class="mb-2">
|
||||
<%- generateStars(guide.avg_rating) %>
|
||||
<small class="text-muted ms-2">(<%= parseFloat(guide.avg_rating).toFixed(1) %>)</small>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar me-1"></i><%= guide.experience %> лет опыта
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-route me-1"></i><%= guide.route_count || 0 %> туров
|
||||
</small>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="text-muted small">
|
||||
<i class="fas fa-calendar-check me-1"></i>
|
||||
<%= guide.booking_count || 0 %> туров проведено
|
||||
</div>
|
||||
<% if (guide.hourly_rate) { %>
|
||||
<div class="text-end">
|
||||
<strong class="text-success">
|
||||
₩<%= parseInt(guide.hourly_rate || 0).toLocaleString('ko-KR') %>/час
|
||||
</strong>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<a href="/guides/<%= guide.id %>" class="btn btn-primary w-100">
|
||||
<i class="fas fa-user me-1"></i>Подробнее
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,13 +117,85 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container text-center">
|
||||
<h2 class="mb-4">Нужен индивидуальный подход?</h2>
|
||||
<p class="lead text-muted mb-4">Наши гиды готовы создать уникальный маршрут специально для вас!</p>
|
||||
<a href="/contact" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-envelope me-1"></i>Связаться с нами
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
// Guide card click handler and rating system
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const guideCards = document.querySelectorAll('.guide-card');
|
||||
|
||||
guideCards.forEach(card => {
|
||||
card.addEventListener('click', function(e) {
|
||||
// Don't trigger card click if clicking on rating buttons
|
||||
if (!e.target.closest('.rating-buttons')) {
|
||||
const guideId = this.dataset.guideId;
|
||||
// Redirect to guide details or show modal
|
||||
window.location.href = `/guides/${guideId}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-5px)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// Rating system
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.like-btn') || e.target.closest('.dislike-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const button = e.target.closest('.like-btn') || e.target.closest('.dislike-btn');
|
||||
const ratingDisplay = button.closest('.rating-display');
|
||||
const targetType = ratingDisplay.dataset.targetType;
|
||||
const targetId = ratingDisplay.dataset.targetId;
|
||||
const rating = parseInt(button.dataset.rating);
|
||||
|
||||
// Send rating to server
|
||||
fetch('/api/rating/' + targetType + '/' + targetId, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ rating: rating })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update rating display
|
||||
const percentageSpan = ratingDisplay.querySelector('.rating-percentage');
|
||||
const countSpan = ratingDisplay.querySelector('small');
|
||||
|
||||
const percentage = data.percentage || 0;
|
||||
const likes = data.likes || 0;
|
||||
const total = data.total || 0;
|
||||
|
||||
percentageSpan.textContent = Math.round(percentage) + '%';
|
||||
countSpan.textContent = `(${likes}/${total})`;
|
||||
|
||||
// Update button states
|
||||
ratingDisplay.querySelectorAll('.rating-buttons button').forEach(btn => {
|
||||
btn.classList.remove('btn-success', 'btn-danger');
|
||||
btn.classList.add(btn.classList.contains('like-btn') ? 'btn-outline-success' : 'btn-outline-danger');
|
||||
});
|
||||
|
||||
// Highlight selected button
|
||||
if (rating === 1) {
|
||||
button.classList.remove('btn-outline-success');
|
||||
button.classList.add('btn-success');
|
||||
} else if (rating === -1) {
|
||||
button.classList.remove('btn-outline-danger');
|
||||
button.classList.add('btn-danger');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при отправке рейтинга:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white py-5">
|
||||
<section class="hero-section compact bg-primary text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-4 text-center text-lg-start">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="hero-background"></div>
|
||||
<div class="hero-overlay"></div>
|
||||
<div class="container position-relative">
|
||||
<div class="row align-items-center min-vh-100 py-5">
|
||||
<div class="row align-items-center py-5" style="min-height: 70vh;">
|
||||
<div class="col-lg-6" data-aos="fade-right">
|
||||
<h1 class="hero-title display-3 fw-bold text-white mb-4">
|
||||
Откройте красоту
|
||||
@@ -175,7 +175,7 @@
|
||||
<i class="fas fa-route display-4"></i>
|
||||
</div>
|
||||
<h3 class="stat-number fw-bold display-5 mb-2">50+</h3>
|
||||
<p class="stat-label fs-5">Unique Tours</p>
|
||||
<p class="stat-label fs-5">Уникальных туров</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="100">
|
||||
@@ -193,7 +193,7 @@
|
||||
<i class="fas fa-users display-4"></i>
|
||||
</div>
|
||||
<h3 class="stat-number fw-bold display-5 mb-2">1000+</h3>
|
||||
<p class="stat-label fs-5">Happy Travelers</p>
|
||||
<p class="stat-label fs-5">Счастливых путешественников</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="300">
|
||||
@@ -202,7 +202,7 @@
|
||||
<i class="fas fa-star display-4"></i>
|
||||
</div>
|
||||
<h3 class="stat-number fw-bold display-5 mb-2">4.9</h3>
|
||||
<p class="stat-label fs-5">Average Rating</p>
|
||||
<p class="stat-label fs-5">Средний рейтинг</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,7 +214,7 @@
|
||||
<div class="container">
|
||||
<div class="text-center mb-5" data-aos="fade-up">
|
||||
<h2 class="section-title display-5 fw-bold mb-3">Полезная информация о путешествиях</h2>
|
||||
<p class="section-subtitle fs-5 text-muted">Tips, guides, and stories from Korea</p>
|
||||
<p class="section-subtitle fs-5 text-muted">Советы, гиды и истории из Кореи</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
@@ -249,8 +249,8 @@
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="text-muted fs-5">No articles available at the moment.</p>
|
||||
<a href="/articles" class="btn btn-primary">Browse All Articles</a>
|
||||
<p class="text-muted fs-5">Пока нет доступных статей.</p>
|
||||
<a href="/articles" class="btn btn-primary">Смотреть все статьи</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
@@ -270,7 +270,7 @@
|
||||
<div class="col-lg-8" data-aos="fade-right">
|
||||
<h2 class="display-5 fw-bold mb-3">Готовы к вашему корейскому приключению?</h2>
|
||||
<p class="fs-5 text-white-50 mb-0">
|
||||
Join thousands of travelers who have discovered the magic of Korea with our expert guides.
|
||||
Присоединяйтесь к тысячам путешественников, которые открыли для себя магию Кореи с нашими опытными гидами.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-4 text-lg-end" data-aos="fade-left">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white">
|
||||
<section class="hero-section compact bg-primary text-white">
|
||||
<div class="container py-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-8">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section bg-primary text-white text-center py-5">
|
||||
<section class="hero-section compact bg-primary text-white text-center py-5">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
|
||||
Reference in New Issue
Block a user