653 lines
30 KiB
HTML
653 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Europe Trip — 15 дней | Маршрут из Астаны</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&family=Unbounded:wght@700&display=swap" rel="stylesheet">
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🇪🇺</text></svg>">
|
||
<style>
|
||
:root{
|
||
--bg: #F7F7F8;
|
||
--ink: #172033;
|
||
--accent: #6C5CE7;
|
||
--accent-light: #FFB84D;
|
||
--white: #FFFFFF;
|
||
--gray-100: #E8E8EA;
|
||
--gray-200: #D0D0D3;
|
||
--gray-500: #6B7280;
|
||
--shadow: 0 4px 20px rgba(0,0,0,0.08);
|
||
--shadow-lg: 0 10px 40px rgba(0,0,0,0.12);
|
||
--radius: 16px;
|
||
--radius-sm: 8px;
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:17px/1.6 'Manrope',-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--ink);background:var(--bg);overflow-x:hidden}
|
||
h1,h2,h3,h4{font:'Unbounded',sans-serif;line-height:1.2}
|
||
a{text-decoration:none;color:inherit}
|
||
img{max-width:100%;display:block;border-radius:var(--radius)}
|
||
.container{max-width:1200px;margin:0 auto;padding:0 20px}
|
||
.section{padding:80px 0}
|
||
.section-title{font-size:36px;font-weight:700;margin-bottom:40px;text-align:center}
|
||
.btn{display:inline-block;background:var(--accent);color:var(--white);padding:16px 32px;border-radius:var(--radius-sm);font-weight:700;font-size:16px;cursor:pointer;border:none;transition:all 0.3s ease}
|
||
.btn:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(108,92,231,0.4)}
|
||
.btn-secondary{background:var(--white);color:var(--accent);border:2px solid var(--accent)}
|
||
.btn-secondary:hover{background:var(--accent);color:var(--white)}
|
||
|
||
/* Header */
|
||
.header{position:fixed;top:0;left:0;right:0;background:rgba(255,255,255,0.95);backdrop-filter:blur(10px);z-index:1000;box-shadow:0 2px 10px rgba(0,0,0,0.05)}
|
||
.header-inner{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;max-width:1400px;margin:0 auto}
|
||
.logo{font:'Unbounded',sans-serif;font-size:20px;font-weight:700;color:var(--accent)}
|
||
.nav{display:flex;gap:24px}
|
||
.nav a{font-size:14px;font-weight:600;color:var(--gray-500);transition:color 0.3s}
|
||
.nav a:hover{color:var(--accent)}
|
||
.menu-toggle{display:none;background:none;border:none;font-size:24px;cursor:pointer}
|
||
|
||
/* Hero */
|
||
.hero{position:relative;height:100vh;min-height:700px;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
||
.hero-bg{position:absolute;inset:0;background:linear-gradient(135deg, var(--accent) 0%, #a78bfa 100%)}
|
||
.hero-pattern{position:absolute;inset:0;opacity:0.1;background-image:radial-gradient(circle at 2px 2px, white 1px, transparent 0);background-size:40px 40px}
|
||
.hero-content{position:relative;z-index:2;text-align:center;color:var(--white);max-width:900px;padding:0 20px}
|
||
.hero h1{font-size:clamp(36px, 8vw, 72px);font-weight:700;margin-bottom:24px;line-height:1.1}
|
||
.hero p{font-size:clamp(18px, 3vw, 24px);margin-bottom:32px;opacity:0.95}
|
||
.hero-route{display:flex;flex-wrap:wrap;justify-content:center;gap:12px;margin-bottom:40px;font-size:clamp(14px, 2vw, 18px)}
|
||
.hero-route span{background:rgba(255,255,255,0.2);padding:8px 16px;border-radius:20px}
|
||
.hero-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}
|
||
.hero-buttons .btn{padding:18px 36px;font-size:18px}
|
||
|
||
/* Route */
|
||
.route-visual{display:flex;align-items:center;justify-content:center;gap:20px;margin:40px 0;flex-wrap:wrap}
|
||
.route-city{text-align:center;padding:20px;background:var(--white);border-radius:var(--radius);box-shadow:var(--shadow);min-width:140px;transition:transform 0.3s}
|
||
.route-city:hover{transform:translateY(-5px)}
|
||
.route-city .flag{font-size:48px;display:block;margin-bottom:8px}
|
||
.route-city .name{font-weight:700;font-size:16px}
|
||
.route-arrow{font-size:32px;color:var(--accent);animation:pulse 2s infinite}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}
|
||
|
||
/* City Cards */
|
||
.cities-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:24px}
|
||
.city-card{background:var(--white);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);transition:all 0.3s ease;cursor:pointer}
|
||
.city-card:hover{transform:translateY(-8px);box-shadow:var(--shadow-lg)}
|
||
.city-card-img{height:200px;object-fit:cover;width:100%}
|
||
.city-card-body{padding:24px}
|
||
.city-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
||
.city-card-title{font-size:22px;font-weight:700}
|
||
.city-card-nights{color:var(--accent);font-weight:600}
|
||
.city-card-price{color:var(--gray-500);margin-bottom:16px}
|
||
.city-card-district{font-size:14px;color:var(--gray-500);margin-bottom:16px}
|
||
|
||
/* Timeline */
|
||
.timeline{position:relative;max-width:800px;margin:0 auto}
|
||
.timeline::before{content:'';position:absolute;left:30px;top:0;bottom:0;width:3px;background:linear-gradient(to bottom, var(--accent), var(--accent-light))}
|
||
.timeline-item{position:relative;padding-left:80px;margin-bottom:40px;opacity:0;transform:translateY(30px);transition:all 0.6s ease}
|
||
.timeline-item.in{opacity:1;transform:none}
|
||
.timeline-marker{position:absolute;left:0;width:60px;height:60px;background:var(--white);border:3px solid var(--accent);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:var(--shadow)}
|
||
.timeline-content{background:var(--white);border-radius:var(--radius);padding:24px;box-shadow:var(--shadow)}
|
||
.timeline-day{font-size:14px;color:var(--accent);font-weight:700;margin-bottom:8px}
|
||
.timeline-title{font-size:20px;font-weight:700;margin-bottom:12px}
|
||
.timeline-activities{list-style:none;margin-bottom:16px}
|
||
.timeline-activities li{padding:6px 0;padding-left:20px;position:relative;color:var(--gray-500)}
|
||
.timeline-activities li::before{content:'•';position:absolute;left:0;color:var(--accent)}
|
||
.timeline-cost{font-weight:700;color:var(--accent-light)}
|
||
|
||
/* Attractions */
|
||
.attractions-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(300px, 1fr));gap:24px}
|
||
.attraction-card{background:var(--white);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);transition:transform 0.3s}
|
||
.attraction-card:hover{transform:translateY(-5px)}
|
||
.attraction-img{height:240px;object-fit:cover;width:100%}
|
||
.attraction-body{padding:24px}
|
||
.attraction-name{font-size:20px;font-weight:700;margin-bottom:8px}
|
||
.attraction-rating{color:var(--accent-light);margin-bottom:12px}
|
||
.attraction-desc{color:var(--gray-500);margin-bottom:16px;line-height:1.5}
|
||
.attraction-price{font-weight:700;color:var(--accent)}
|
||
|
||
/* Hotels */
|
||
.hotels-section{background:var(--white)}
|
||
.hotels-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(300px, 1fr));gap:24px}
|
||
.hotel-card{border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow)}
|
||
.hotel-card-img{height:200px;object-fit:cover;width:100%}
|
||
.hotel-card-body{padding:24px}
|
||
.hotel-category{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;margin-bottom:12px}
|
||
.hotel-category.economy{background:#d1fae5;color:#065f46}
|
||
.hotel-category.comfort{background:#dbeafe;color:#1e40af}
|
||
.hotel-category.premium{background:#ede9fe;color:#5b21b6}
|
||
.hotel-name{font-size:20px;font-weight:700;margin-bottom:8px}
|
||
.hotel-district{color:var(--gray-500);margin-bottom:12px}
|
||
.hotel-price{font-size:18px;font-weight:700;color:var(--accent)}
|
||
|
||
/* Transport */
|
||
.transport-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:20px}
|
||
.transport-card{background:var(--white);border-radius:var(--radius);padding:24px;display:flex;align-items:center;gap:16px;box-shadow:var(--shadow);transition:transform 0.3s}
|
||
.transport-card:hover{transform:translateX(5px)}
|
||
.transport-icon{font-size:40px}
|
||
.transport-info{flex:1}
|
||
.transport-route{font-weight:700;margin-bottom:4px}
|
||
.transport-type{color:var(--gray-500);font-size:14px}
|
||
.transport-details{text-align:right}
|
||
.transport-duration{font-size:14px;color:var(--gray-500)}
|
||
.transport-price{font-weight:700;color:var(--accent)}
|
||
|
||
/* Budget Calculator */
|
||
.budget-section{background:linear-gradient(135deg, var(--accent) 0%, #a78bfa 100%);color:var(--white)}
|
||
.budget-section .section-title{color:var(--white)}
|
||
.calculator{background:var(--white);border-radius:var(--radius);padding:40px;box-shadow:var(--shadow-lg);max-width:900px;margin:0 auto}
|
||
.calculator-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:24px;margin-bottom:32px}
|
||
.calculator-item label{display:block;font-weight:700;margin-bottom:12px;color:var(--ink)}
|
||
.calculator-item select,.calculator-item input{width:100%;padding:14px;border:2px solid var(--gray-200);border-radius:var(--radius-sm);font-size:16px;font-family:inherit;cursor:pointer}
|
||
.calculator-item select:focus,.calculator-item input:focus{outline:none;border-color:var(--accent)}
|
||
.budget-result{background:var(--bg);border-radius:var(--radius);padding:32px}
|
||
.budget-breakdown{margin-bottom:24px}
|
||
.budget-line{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px solid var(--gray-200)}
|
||
.budget-line:last-child{border:none}
|
||
.budget-label{display:flex;align-items:center;gap:12px}
|
||
.budget-label span:first-child{font-size:20px}
|
||
.budget-total{border-top:3px solid var(--ink);margin-top:16px;padding-top:24px}
|
||
.budget-total .budget-line{font-size:24px;font-weight:700}
|
||
.budget-per-person{font-size:18px;color:var(--accent);margin-top:16px;text-align:center}
|
||
|
||
/* Budget Cards */
|
||
.budget-cards{display:grid;grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));gap:20px}
|
||
.budget-card{background:var(--white);border-radius:var(--radius);padding:24px;text-align:center;box-shadow:var(--shadow);transition:all 0.3s}
|
||
.budget-card:hover{transform:scale(1.05)}
|
||
.budget-card-icon{font-size:48px;margin-bottom:16px}
|
||
.budget-card-title{font-weight:700}
|
||
|
||
/* Tips */
|
||
.tips-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(300px, 1fr));gap:24px}
|
||
.tip-card{background:var(--white);border-radius:var(--radius);padding:24px;box-shadow:var(--shadow)}
|
||
.tip-icon{font-size:40px;margin-bottom:16px}
|
||
.tip-title{font-size:20px;font-weight:700;margin-bottom:12px}
|
||
.tip-text{color:var(--gray-500)}
|
||
|
||
/* Checklist */
|
||
.checklist-section{background:var(--white)}
|
||
.checklist-container{max-width:600px;margin:0 auto}
|
||
.checklist-progress{margin-bottom:32px}
|
||
.checklist-progress-bar{height:12px;background:var(--gray-200);border-radius:6px;overflow:hidden;margin-top:12px}
|
||
.checklist-progress-fill{height:100%;background:linear-gradient(90deg, var(--accent), var(--accent-light));transition:width 0.3s}
|
||
.checklist-items{list-style:none}
|
||
.checklist-item{display:flex;align-items:center;padding:16px;border-bottom:1px solid var(--gray-200);cursor:pointer;transition:background 0.3s}
|
||
.checklist-item:hover{background:var(--bg)}
|
||
.checklist-item:last-child{border:none}
|
||
.checklist-checkbox{width:24px;height:24px;border:2px solid var(--gray-500);border-radius:6px;margin-right:16px;display:flex;align-items:center;justify-content:center;transition:all 0.3s}
|
||
.checklist-item.done .checklist-checkbox{background:var(--accent);border-color:var(--accent)}
|
||
.checklist-item.done .checklist-checkbox::after{content:'✓';color:var(--white);font-size:16px;font-weight:700}
|
||
.checklist-item.done .checklist-text{text-decoration:line-through;color:var(--gray-500)}
|
||
.checklist-text{flex:1;font-weight:600}
|
||
|
||
/* Map */
|
||
.map-section{background:var(--white)}
|
||
.map-container{height:500px;border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow)}
|
||
|
||
/* Modal */
|
||
.modal{position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:2000;display:none;align-items:center;justify-content:center;padding:20px}
|
||
.modal.open{display:flex}
|
||
.modal-content{background:var(--white);border-radius:var(--radius);max-width:900px;max-height:90vh;overflow-y:auto;position:relative;animation:modalSlide 0.3s ease}
|
||
@keyframes modalSlide{from{transform:translateY(50px);opacity:0}to{transform:translateY(0);opacity:1}}
|
||
.modal-close{position:absolute;top:16px;right:16px;background:none;border:none;font-size:32px;cursor:pointer;color:var(--gray-500);z-index:10}
|
||
.modal-close:hover{color:var(--ink)}
|
||
.modal-header{position:sticky;top:0;background:var(--white);padding:24px;border-bottom:1px solid var(--gray-200);z-index:5}
|
||
.modal-title{font-size:32px;font-weight:700}
|
||
.modal-body{padding:24px}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 768px){
|
||
.nav{display:none}
|
||
.menu-toggle{display:block}
|
||
.nav.active{display:flex;position:absolute;top:100%;left:0;right:0;background:var(--white);flex-direction:column;padding:20px;box-shadow:var(--shadow)}
|
||
.hero-buttons{flex-direction:column;align-items:center}
|
||
.hero-buttons .btn{width:100%;max-width:300px}
|
||
.timeline::before{left:20px}
|
||
.timeline-item{padding-left:60px}
|
||
.timeline-marker{width:40px;height:40px;font-size:18px}
|
||
.calculator{padding:24px}
|
||
.section{padding:60px 0}
|
||
}
|
||
|
||
/* Reveal animation */
|
||
.reveal{opacity:0;transform:translateY(30px);transition:all 0.6s ease}
|
||
.reveal.in{opacity:1;transform:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Header -->
|
||
<header class="header">
|
||
<div class="header-inner">
|
||
<div class="logo">🇪🇺 Europe Trip</div>
|
||
<nav class="nav" id="nav">
|
||
<a href="#route">🗺️ Маршрут</a>
|
||
<a href="#days">📅 15 дней</a>
|
||
<a href="#hotels">🏨 Отели</a>
|
||
<a href="#transport">🚆 Транспорт</a>
|
||
<a href="#budget">💰 Бюджет</a>
|
||
<a href="#checklist">🎒 Чек-лист</a>
|
||
</nav>
|
||
<button class="menu-toggle" id="menuToggle" aria-label="Меню">☰</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Hero -->
|
||
<section class="hero">
|
||
<div class="hero-bg"></div>
|
||
<div class="hero-pattern"></div>
|
||
<div class="hero-content">
|
||
<h1>ЕВРОПА ЗА 15 ДНЕЙ</h1>
|
||
<p>Идеальный маршрут для первой самостоятельной поездки из Астаны</p>
|
||
<div class="hero-route">
|
||
<span>🇰🇿 Астана</span>
|
||
<span>→</span>
|
||
<span>🇨🇿 Прага</span>
|
||
<span>→</span>
|
||
<span>🇦🇹 Вена</span>
|
||
<span>→</span>
|
||
<span>🇸🇰 Братислава</span>
|
||
<span>→</span>
|
||
<span>🇭🇺 Будапешт</span>
|
||
</div>
|
||
<div class="hero-buttons">
|
||
<button class="btn" onclick="document.getElementById('route').scrollIntoView({behavior:'smooth'})">Посмотреть маршрут</button>
|
||
<button class="btn btn-secondary" onclick="document.getElementById('budget').scrollIntoView({behavior:'smooth'})">Рассчитать бюджет</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Route -->
|
||
<section class="section" id="route">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">Ваш маршрут на 15 дней</h2>
|
||
<div class="route-visual reveal" id="routeVisual"></div>
|
||
<div class="cities-grid" id="citiesGrid"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Days Timeline -->
|
||
<section class="section" id="days" style="background:var(--white)">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">15 дней в Европе</h2>
|
||
<div class="timeline" id="timeline"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Hotels -->
|
||
<section class="section hotels-section" id="hotels">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">Где остановиться</h2>
|
||
<div class="hotels-grid" id="hotelsGrid"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Transport -->
|
||
<section class="section" id="transport">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">Как перемещаться между городами</h2>
|
||
<div class="transport-grid" id="transportGrid"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Budget Calculator -->
|
||
<section class="section budget-section" id="budget">
|
||
<div class="container">
|
||
<h2 class="section-title">Сколько стоит поездка?</h2>
|
||
<div class="calculator reveal">
|
||
<div class="calculator-grid">
|
||
<div class="calculator-item">
|
||
<label>Путешественников</label>
|
||
<input type="number" id="travelers" value="1" min="1" max="10" onchange="calculateBudget()">
|
||
</div>
|
||
<div class="calculator-item">
|
||
<label>Категория отеля</label>
|
||
<select id="hotelCategory" onchange="calculateBudget()">
|
||
<option value="economy">Эконом</option>
|
||
<option value="comfort" selected>Комфорт</option>
|
||
<option value="premium">Премиум</option>
|
||
</select>
|
||
</div>
|
||
<div class="calculator-item">
|
||
<label>Питание</label>
|
||
<select id="foodCategory" onchange="calculateBudget()">
|
||
<option value="economy">Эконом</option>
|
||
<option value="medium" selected>Средний</option>
|
||
<option value="comfort">Комфорт</option>
|
||
</select>
|
||
</div>
|
||
<div class="calculator-item">
|
||
<label>Экскурсии</label>
|
||
<select id="attractionsCategory" onchange="calculateBudget()">
|
||
<option value="min">Минимум</option>
|
||
<option value="standard" selected>Стандарт</option>
|
||
<option value="many">Много экскурсий</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="budget-result">
|
||
<div class="budget-breakdown" id="budgetBreakdown"></div>
|
||
<div class="budget-total">
|
||
<div class="budget-line">
|
||
<span class="budget-label"><span>💰</span> ИТОГО:</span>
|
||
<span id="budgetTotal">0 ₸</span>
|
||
</div>
|
||
</div>
|
||
<div class="budget-per-person" id="budgetPerPerson"></div>
|
||
</div>
|
||
</div>
|
||
<div class="budget-cards reveal" style="margin-top:40px">
|
||
<div class="budget-card"><div class="budget-card-icon">✈️</div><div class="budget-card-title">Авиабилеты</div></div>
|
||
<div class="budget-card"><div class="budget-card-icon">🚆</div><div class="budget-card-title">Переезды</div></div>
|
||
<div class="budget-card"><div class="budget-card-icon">🏨</div><div class="budget-card-title">Отели</div></div>
|
||
<div class="budget-card"><div class="budget-card-icon">🍽️</div><div class="budget-card-title">Питание</div></div>
|
||
<div class="budget-card"><div class="budget-card-icon">🎟️</div><div class="budget-card-title">Достопримечательности</div></div>
|
||
<div class="budget-card"><div class="budget-card-icon">🚇</div><div class="budget-card-title">Транспорт</div></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tips -->
|
||
<section class="section" id="tips">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">Перед поездкой</h2>
|
||
<div class="tips-grid" id="tipsGrid"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Checklist -->
|
||
<section class="section checklist-section" id="checklist">
|
||
<div class="container">
|
||
<h2 class="section-title reveal">Чек-лист подготовки</h2>
|
||
<div class="checklist-container">
|
||
<div class="checklist-progress">
|
||
<div style="display:flex;justify-content:space-between;font-weight:700">
|
||
<span>Готово:</span>
|
||
<span id="checklistCount">0 / 11</span>
|
||
</div>
|
||
<div class="checklist-progress-bar">
|
||
<div class="checklist-progress-fill" id="checklistProgress" style="width:0%"></div>
|
||
</div>
|
||
</div>
|
||
<ul class="checklist-items" id="checklistItems"></ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Modal for City Details -->
|
||
<div class="modal" id="cityModal">
|
||
<div class="modal-content">
|
||
<button class="modal-close" onclick="closeModal()">×</button>
|
||
<div class="modal-header">
|
||
<h2 class="modal-title" id="modalTitle"></h2>
|
||
</div>
|
||
<div class="modal-body" id="modalBody"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Load data
|
||
let DATA = null;
|
||
let checklistData = [];
|
||
|
||
fetch('data.json')
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
DATA = data;
|
||
checklistData = [...data.checklist];
|
||
renderAll();
|
||
calculateBudget();
|
||
updateChecklist();
|
||
})
|
||
.catch(err => {
|
||
console.error('Failed to load data:', err);
|
||
document.getElementById('citiesGrid').innerHTML = '<p>Ошибка загрузки данных. Обновите страницу.</p>';
|
||
});
|
||
|
||
function renderAll() {
|
||
renderRoute();
|
||
renderCities();
|
||
renderTimeline();
|
||
renderHotels();
|
||
renderTransport();
|
||
renderTips();
|
||
renderChecklist();
|
||
}
|
||
|
||
function renderRoute() {
|
||
const route = document.getElementById('routeVisual');
|
||
const cities = DATA.cities;
|
||
let html = '<div class="route-city"><span class="flag">🇰🇿</span><div class="name">Астана</div></div>';
|
||
cities.forEach(city => {
|
||
html += '<div class="route-arrow">↓</div>';
|
||
html += `<div class="route-city"><span class="flag">${city.flag}</span><div class="name">${city.name}</div></div>`;
|
||
});
|
||
html += '<div class="route-arrow">↓</div>';
|
||
html += '<div class="route-city"><span class="flag">🇰🇿</span><div class="name">Астана</div></div>';
|
||
route.innerHTML = html;
|
||
}
|
||
|
||
function renderCities() {
|
||
const grid = document.getElementById('citiesGrid');
|
||
grid.innerHTML = DATA.cities.map(city => `
|
||
<div class="city-card" onclick="openCityModal('${city.id}')">
|
||
<img src="${city.image}" alt="${city.name}" class="city-card-img" loading="lazy">
|
||
<div class="city-card-body">
|
||
<div class="city-card-header">
|
||
<div class="city-card-title">${city.name} ${city.flag}</div>
|
||
<div class="city-card-nights">${city.nights} ночей</div>
|
||
</div>
|
||
<div class="city-card-price">${city.priceRange} / ночь</div>
|
||
<div class="city-card-district">Лучший район: ${city.district}</div>
|
||
<button class="btn" style="width:100%">Подробнее</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderTimeline() {
|
||
const timeline = document.getElementById('timeline');
|
||
timeline.innerHTML = DATA.days.map((day, i) => `
|
||
<div class="timeline-item reveal">
|
||
<div class="timeline-marker">${day.icon}</div>
|
||
<div class="timeline-content">
|
||
<div class="timeline-day">День ${day.day}</div>
|
||
<div class="timeline-title">${day.title}</div>
|
||
<ul class="timeline-activities">
|
||
${day.activities.map(a => `<li>${a}</li>`).join('')}
|
||
</ul>
|
||
${day.cost ? `<div class="timeline-cost">Стоимость дня: ${day.cost}</div>` : ''}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderHotels() {
|
||
const grid = document.getElementById('hotelsGrid');
|
||
const allHotels = [];
|
||
Object.keys(DATA.hotels).forEach(cityId => {
|
||
const city = DATA.cities.find(c => c.id === cityId);
|
||
DATA.hotels[cityId].forEach(hotel => {
|
||
allHotels.push({...hotel, cityName: city.name, cityFlag: city.flag});
|
||
});
|
||
});
|
||
grid.innerHTML = allHotels.map(hotel => `
|
||
<div class="hotel-card">
|
||
<img src="${hotel.image}" alt="${hotel.name}" class="hotel-card-img" loading="lazy">
|
||
<div class="hotel-card-body">
|
||
<span class="hotel-category ${hotel.category.toLowerCase()}">${hotel.category}</span>
|
||
<div class="hotel-name">${hotel.name}</div>
|
||
<div class="hotel-district">📍 ${hotel.cityName} ${hotel.cityFlag} — ${hotel.district}</div>
|
||
<div class="hotel-price">${hotel.price}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderTransport() {
|
||
const grid = document.getElementById('transportGrid');
|
||
grid.innerHTML = DATA.transport.map(t => `
|
||
<div class="transport-card">
|
||
<div class="transport-icon">${t.type.includes('✈️') ? '✈️' : '🚆'}</div>
|
||
<div class="transport-info">
|
||
<div class="transport-route">${t.from} → ${t.to}</div>
|
||
<div class="transport-type">${t.type.replace(/[✈️🚆]/g, '').trim()}</div>
|
||
</div>
|
||
<div class="transport-details">
|
||
<div class="transport-duration">${t.duration}</div>
|
||
<div class="transport-price">${t.price}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderTips() {
|
||
const grid = document.getElementById('tipsGrid');
|
||
grid.innerHTML = DATA.tips.map(tip => `
|
||
<div class="tip-card reveal">
|
||
<div class="tip-icon">${tip.icon}</div>
|
||
<div class="tip-title">${tip.title}</div>
|
||
<div class="tip-text">${tip.text}</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderChecklist() {
|
||
const list = document.getElementById('checklistItems');
|
||
list.innerHTML = checklistData.map((item, i) => `
|
||
<li class="checklist-item ${item.done ? 'done' : ''}" onclick="toggleChecklist(${i})">
|
||
<div class="checklist-checkbox"></div>
|
||
<div class="checklist-text">${item.text}</div>
|
||
</li>
|
||
`).join('');
|
||
updateChecklist();
|
||
}
|
||
|
||
function updateChecklist() {
|
||
const done = checklistData.filter(i => i.done).length;
|
||
const total = checklistData.length;
|
||
document.getElementById('checklistCount').textContent = `${done} / ${total}`;
|
||
document.getElementById('checklistProgress').style.width = `${(done/total)*100}%`;
|
||
}
|
||
|
||
function toggleChecklist(index) {
|
||
checklistData[index].done = !checklistData[index].done;
|
||
renderChecklist();
|
||
localStorage.setItem('europeTripChecklist', JSON.stringify(checklistData));
|
||
}
|
||
|
||
// Load saved checklist
|
||
const saved = localStorage.getItem('europeTripChecklist');
|
||
if (saved) {
|
||
try { checklistData = JSON.parse(saved); } catch(e) {}
|
||
}
|
||
|
||
function calculateBudget() {
|
||
if (!DATA) return;
|
||
const travelers = parseInt(document.getElementById('travelers').value) || 1;
|
||
const hotelCat = document.getElementById('hotelCategory').value;
|
||
const foodCat = document.getElementById('foodCategory').value;
|
||
const attractionsCat = document.getElementById('attractionsCategory').value;
|
||
|
||
const b = DATA.budget;
|
||
const flights = b.flights * travelers;
|
||
const trains = b.trains * travelers;
|
||
const hotels = b.hotels[hotelCat] * travelers;
|
||
const food = b.food[foodCat] * travelers;
|
||
const attractions = b.attractions[attractionsCat] * travelers;
|
||
const transport = b.transport * travelers;
|
||
const total = flights + trains + hotels + food + attractions + transport;
|
||
|
||
document.getElementById('budgetBreakdown').innerHTML = `
|
||
<div class="budget-line"><span class="budget-label"><span>✈️</span> Авиабилеты</span><span>${flights.toLocaleString()} ₸</span></div>
|
||
<div class="budget-line"><span class="budget-label"><span>🚆</span> Переезды</span><span>${trains.toLocaleString()} ₸</span></div>
|
||
<div class="budget-line"><span class="budget-label"><span>🏨</span> Отели</span><span>${hotels.toLocaleString()} ₸</span></div>
|
||
<div class="budget-line"><span class="budget-label"><span>🍽️</span> Питание</span><span>${food.toLocaleString()} ₸</span></div>
|
||
<div class="budget-line"><span class="budget-label"><span>🎟️</span> Достопримечательности</span><span>${attractions.toLocaleString()} ₸</span></div>
|
||
<div class="budget-line"><span class="budget-label"><span>🚇</span> Транспорт</span><span>${transport.toLocaleString()} ₸</span></div>
|
||
`;
|
||
document.getElementById('budgetTotal').textContent = `${total.toLocaleString()} ₸`;
|
||
document.getElementById('budgetPerPerson').textContent = travelers > 1 ? `≈ ${Math.round(total/travelers).toLocaleString()} ₸ на человека` : '';
|
||
}
|
||
|
||
function openCityModal(cityId) {
|
||
const city = DATA.cities.find(c => c.id === cityId);
|
||
const attractions = DATA.attractions[cityId] || [];
|
||
const hotels = DATA.hotels[cityId] || [];
|
||
|
||
document.getElementById('modalTitle').textContent = `${city.name} ${city.flag}`;
|
||
document.getElementById('modalBody').innerHTML = `
|
||
<img src="${city.image}" alt="${city.name}" style="width:100%;height:300px;object-fit:cover;margin-bottom:24px;border-radius:var(--radius)">
|
||
<p style="font-size:18px;margin-bottom:24px;color:var(--gray-500)">${city.description}</p>
|
||
<div style="display:flex;gap:24px;margin-bottom:32px;flex-wrap:wrap">
|
||
<div><strong>📅 Ночей:</strong> ${city.nights}</div>
|
||
<div><strong>💰 Проживание:</strong> ${city.priceRange} / ночь</div>
|
||
<div><strong>📍 Район:</strong> ${city.district}</div>
|
||
</div>
|
||
|
||
<h3 style="margin-bottom:20px">Что посмотреть</h3>
|
||
<div class="attractions-grid" style="margin-bottom:32px">
|
||
${attractions.map(a => `
|
||
<div class="attraction-card">
|
||
<img src="${a.image}" alt="${a.name}" class="attraction-img" loading="lazy">
|
||
<div class="attraction-body">
|
||
<div class="attraction-name">${a.name}</div>
|
||
<div class="attraction-rating">${'⭐'.repeat(a.rating)}${'☆'.repeat(5-a.rating)}</div>
|
||
<div class="attraction-desc">${a.description}</div>
|
||
<div class="attraction-price">Билет: ${a.price}</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<h3 style="margin-bottom:20px">Где жить</h3>
|
||
<div class="hotels-grid">
|
||
${hotels.map(h => `
|
||
<div class="hotel-card">
|
||
<img src="${h.image}" alt="${h.name}" class="hotel-card-img" loading="lazy">
|
||
<div class="hotel-card-body">
|
||
<span class="hotel-category ${h.category.toLowerCase()}">${h.category}</span>
|
||
<div class="hotel-name">${h.name}</div>
|
||
<div class="hotel-district">📍 ${h.district}</div>
|
||
<div class="hotel-price">${h.price}</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
document.getElementById('cityModal').classList.add('open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('cityModal').classList.remove('open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
// Close modal on outside click
|
||
document.getElementById('cityModal').addEventListener('click', (e) => {
|
||
if (e.target.id === 'cityModal') closeModal();
|
||
});
|
||
|
||
// Mobile menu
|
||
document.getElementById('menuToggle').addEventListener('click', () => {
|
||
document.getElementById('nav').classList.toggle('active');
|
||
});
|
||
|
||
// Reveal on scroll
|
||
const observer = new IntersectionObserver(entries => {
|
||
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('in'); });
|
||
}, {threshold: 0.15});
|
||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||
|
||
// Smooth scroll for nav links
|
||
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
||
a.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const target = document.querySelector(this.getAttribute('href'));
|
||
if (target) {
|
||
target.scrollIntoView({behavior: 'smooth'});
|
||
document.getElementById('nav').classList.remove('active');
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|