sozday-predlozhenie-dlya-pla/index.html
2026-08-28 05:58:26 +00:00

270 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Планировщик отпуска</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f0f2f5; color: #1a1a1a; line-height: 1.6; }
.wrapper { min-height: 100vh; }
.hero { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%); padding: 60px 20px; text-align: center; color: white; }
.hero-content h1 { font-size: 2.5rem; font-weight: 700; margin-bottom: 12px; text-shadow: 0 2px 10px rgba(0,0,0,0.15); }
.hero-content p { font-size: 1.1rem; opacity: 0.95; max-width: 500px; margin: 0 auto; }
.container { max-width: 1400px; margin: 0 auto; padding: 30px 20px; }
.grid { display: grid; grid-template-columns: 380px 1fr; gap: 30px; align-items: start; }
.sidebar { display: flex; flex-direction: column; gap: 20px; position: sticky; top: 20px; }
.card { background: white; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden; }
.card-header { display: flex; align-items: center; gap: 12px; padding: 20px 24px; border-bottom: 1px solid #f0f0f0; background: #fafafa; }
.card-header h2 { font-size: 1.1rem; font-weight: 600; color: #1a1a1a; }
.card-icon { font-size: 1.4rem; }
.main-header { justify-content: space-between; flex-wrap: wrap; gap: 15px; }
.header-left { display: flex; align-items: center; gap: 12px; }
.form-card { border-top: 4px solid #6366f1; }
.vacation-form { padding: 24px; display: flex; flex-direction: column; gap: 18px; }
.form-group { display: flex; flex-direction: column; gap: 8px; }
.form-group label { font-size: 0.85rem; font-weight: 600; color: #555; display: flex; align-items: center; gap: 6px; }
.form-group label .icon { font-size: 1rem; }
.form-group input { padding: 12px 14px; border: 2px solid #e5e7eb; border-radius: 10px; font-size: 0.95rem; font-family: inherit; transition: all 0.2s; background: #fafafa; -webkit-appearance: none; }
.form-group input:focus { outline: none; border-color: #6366f1; background: white; box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1); }
.form-group input::placeholder { color: #9ca3af; }
.form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.btn-primary { margin-top: 8px; padding: 14px 24px; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: white; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; font-family: inherit; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; }
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(99, 102, 241, 0.35); }
.stats-card { border-top: 4px solid #10b981; }
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; padding: 24px; }
.stat-item { text-align: center; padding: 15px; background: #f8fafc; border-radius: 12px; }
.stat-value { font-size: 1.8rem; font-weight: 700; color: #6366f1; margin-bottom: 6px; }
.stat-label { font-size: 0.8rem; color: #64748b; font-weight: 500; }
.main-content .card { border-top: 4px solid #8b5cf6; }
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; padding: 24px; background: #fafafa; }
.vacation-card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.06); transition: all 0.3s; position: relative; overflow: hidden; }
.vacation-card:hover { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(0,0,0,0.12); }
.vacation-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, #6366f1, #8b5cf6); }
.card-location { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.flag-icon { font-size: 2rem; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-radius: 10px; }
.location-info h3 { font-size: 1.1rem; font-weight: 600; color: #1a1a1a; margin-bottom: 2px; }
.location-info p { font-size: 0.9rem; color: #6b7280; }
.card-details { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.detail-row { display: flex; align-items: center; gap: 8px; font-size: 0.9rem; color: #374151; }
.detail-row .d-icon { font-size: 1.1rem; width: 20px; text-align: center; }
.detail-row strong { color: #1a1a1a; }
.card-budget { background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); padding: 12px 16px; border-radius: 10px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.budget-label { font-size: 0.85rem; color: #059669; font-weight: 500; }
.budget-value { font-size: 1.4rem; font-weight: 700; color: #059669; }
.card-actions { display: flex; gap: 8px; }
.btn-delete { flex: 1; padding: 10px 16px; background: #fef2f2; color: #dc2626; border: 1px solid #fee2e2; border-radius: 8px; font-size: 0.9rem; font-weight: 600; font-family: inherit; cursor: pointer; transition: all 0.2s; }
.btn-delete:hover { background: #dc2626; color: white; border-color: #dc2626; }
.btn-export { padding: 10px 18px; background: white; color: #10b981; border: 2px solid #10b981; border-radius: 10px; font-size: 0.9rem; font-weight: 600; font-family: inherit; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 6px; }
.btn-export:hover { background: #10b981; color: white; }
.empty-state { text-align: center; padding: 80px 24px; color: #6b7280; }
.empty-icon { font-size: 4rem; margin-bottom: 20px; opacity: 0.5; }
.empty-state h3 { font-size: 1.3rem; color: #374151; margin-bottom: 8px; }
.empty-state p { font-size: 1rem; color: #9ca3af; }
@media (max-width: 1024px) { .grid { grid-template-columns: 1fr; } .sidebar { position: static; } .cards-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); } }
@media (max-width: 768px) { .hero { padding: 40px 15px; } .hero-content h1 { font-size: 1.6rem; } .container { padding: 15px; } .card-header { padding: 15px 18px; } .vacation-form { padding: 18px; gap: 14px; } .form-group input { padding: 11px 12px; font-size: 16px; } .stats-grid { grid-template-columns: 1fr 1fr; gap: 12px; padding: 18px; } .cards-grid { grid-template-columns: 1fr; padding: 18px; gap: 15px; } .main-header { flex-direction: column; align-items: flex-start; } .btn-export { width: 100%; justify-content: center; } }
@media (max-width: 480px) { .hero { padding: 30px 12px; } .hero-content h1 { font-size: 1.4rem; } .container { padding: 10px; } .card { border-radius: 12px; } .card-header { padding: 14px 15px; } .card-header h2 { font-size: 1rem; } .vacation-form { padding: 15px; gap: 12px; } .form-group label { font-size: 0.8rem; } .form-group input { padding: 10px 11px; font-size: 16px; border-radius: 8px; } .form-row-2 { grid-template-columns: 1fr; gap: 10px; } .btn-primary { padding: 12px 18px; font-size: 0.95rem; border-radius: 8px; } .stats-grid { grid-template-columns: 1fr; gap: 10px; padding: 15px; } .stat-item { padding: 12px; } .stat-value { font-size: 1.3rem; } .cards-grid { padding: 15px; gap: 12px; } .vacation-card { padding: 16px; border-radius: 10px; } .flag-icon { width: 45px; height: 45px; font-size: 1.6rem; } .location-info h3 { font-size: 1rem; } .budget-value { font-size: 1.2rem; } .btn-delete { padding: 9px 14px; font-size: 0.85rem; border-radius: 7px; } .empty-state { padding: 50px 15px; } .empty-icon { font-size: 3rem; } }
</style>
</head>
<body>
<div class="wrapper">
<div class="hero">
<div class="hero-content">
<h1>✈️ Планировщик отпуска</h1>
<p>Собирай идеи для путешествий, сравнивай варианты и выбирай лучшее</p>
</div>
</div>
<div class="container">
<div class="grid">
<aside class="sidebar">
<div class="card form-card">
<div class="card-header">
<span class="card-icon">📝</span>
<h2>Новый вариант</h2>
</div>
<form id="vacationForm" class="vacation-form">
<div class="form-group">
<label for="country"><span class="icon">🌍</span> Страна</label>
<input type="text" id="country" placeholder="Турция, Таиланд..." required>
</div>
<div class="form-group">
<label for="city"><span class="icon">🏙️</span> Город</label>
<input type="text" id="city" placeholder="Анталья, Пхукет..." required>
</div>
<div class="form-row-2">
<div class="form-group">
<label for="dateFrom"><span class="icon">📅</span> Вылет</label>
<input type="date" id="dateFrom" required>
</div>
<div class="form-group">
<label for="dateTo"><span class="icon">📅</span> Возвращение</label>
<input type="date" id="dateTo" required>
</div>
</div>
<div class="form-group">
<label for="budget"><span class="icon">💰</span> Бюджет ($)</label>
<input type="number" id="budget" placeholder="1500" min="0" required>
</div>
<div class="form-group">
<label for="hotel"><span class="icon">🏨</span> Отель</label>
<input type="text" id="hotel" placeholder="Название отеля">
</div>
<div class="form-group">
<label for="tickets"><span class="icon">🎫</span> Билеты</label>
<input type="text" id="tickets" placeholder="Ссылка или номер рейса">
</div>
<button type="submit" class="btn-primary"><span class="btn-icon">+</span> Добавить вариант</button>
</form>
</div>
<div class="card stats-card">
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="totalCount">0</div>
<div class="stat-label">Вариантов</div>
</div>
<div class="stat-item">
<div class="stat-value" id="avgBudget">$0</div>
<div class="stat-label">Средний бюджет</div>
</div>
</div>
</div>
</aside>
<main class="main-content">
<div class="card">
<div class="card-header main-header">
<div class="header-left">
<span class="card-icon">🗂️</span>
<h2>Все варианты</h2>
</div>
<button onclick="exportToExcel()" class="btn-export">
<span class="btn-icon">📥</span> Экспорт в Excel
</button>
</div>
<div class="cards-grid" id="cardsGrid"></div>
<div id="emptyState" class="empty-state">
<div class="empty-icon">🏖️</div>
<h3>Пока пусто</h3>
<p>Добавьте первый вариант отпуска с помощью формы слева</p>
</div>
</div>
</main>
</div>
</div>
</div>
<script>
let vacations = [
{ id: 1, country: 'Турция', city: 'Анталья', dateFrom: '2026-09-15', dateTo: '2026-09-25', budget: 1200, hotel: 'Rixos Sungate 5*', tickets: 'Рейс TK-412, Турецкие авиалинии' },
{ id: 2, country: 'Таиланд', city: 'Пхукет', dateFrom: '2026-11-10', dateTo: '2026-11-24', budget: 2100, hotel: 'JW Marriott Phuket 5*', tickets: 'Рейс SU-270, Аэрофлот + TG-2203' },
{ id: 3, country: 'ОАЭ', city: 'Дубай', dateFrom: '2026-12-20', dateTo: '2026-12-30', budget: 2800, hotel: 'Atlantis The Palm 5*', tickets: 'Рейс FZ-654, FlyDubai' },
{ id: 4, country: 'Мальдивы', city: 'Мале', dateFrom: '2027-01-15', dateTo: '2027-01-25', budget: 4500, hotel: 'Conrad Maldives Rangali 5*', tickets: 'Рейс QR-234, Qatar Airways + гидросамолёт' },
{ id: 5, country: 'Италия', city: 'Римини', dateFrom: '2026-07-01', dateTo: '2026-07-14', budget: 1600, hotel: 'Grand Hotel Rimini 5*', tickets: 'Рейс AZ-596, ITA Airways' },
{ id: 6, country: 'Испания', city: 'Барселона', dateFrom: '2026-08-05', dateTo: '2026-08-15', budget: 1400, hotel: 'W Barcelona 5*', tickets: 'Рейс VY-3124, Vueling' }
];
document.getElementById('vacationForm').addEventListener('submit', function(e) {
e.preventDefault();
const vacation = {
id: Date.now(),
country: document.getElementById('country').value.trim(),
city: document.getElementById('city').value.trim(),
dateFrom: document.getElementById('dateFrom').value,
dateTo: document.getElementById('dateTo').value,
budget: parseFloat(document.getElementById('budget').value) || 0,
hotel: document.getElementById('hotel').value.trim(),
tickets: document.getElementById('tickets').value.trim()
};
vacations.unshift(vacation);
renderCards();
updateStats();
this.reset();
});
function renderCards() {
const grid = document.getElementById('cardsGrid');
const emptyState = document.getElementById('emptyState');
if (vacations.length === 0) { grid.innerHTML = ''; emptyState.style.display = 'block'; return; }
emptyState.style.display = 'none';
grid.innerHTML = vacations.map(v => {
const days = calculateDays(v.dateFrom, v.dateTo);
const dates = formatDateRange(v.dateFrom, v.dateTo);
const flag = getCountryFlag(v.country);
return `<div class="vacation-card">
<div class="card-location"><div class="flag-icon">${flag}</div><div class="location-info"><h3>${escapeHtml(v.country)}</h3><p>${escapeHtml(v.city)}</p></div></div>
<div class="card-details">
<div class="detail-row"><span class="d-icon">📅</span><span>${dates} <strong>(${days} дн.)</strong></span></div>
${v.hotel ? `<div class="detail-row"><span class="d-icon">🏨</span><span>${escapeHtml(v.hotel)}</span></div>` : ''}
${v.tickets ? `<div class="detail-row"><span class="d-icon">🎫</span><span>${escapeHtml(v.tickets)}</span></div>` : ''}
</div>
<div class="card-budget"><span class="budget-label">Бюджет</span><span class="budget-value">$${v.budget.toLocaleString()}</span></div>
<div class="card-actions"><button class="btn-delete" onclick="deleteVacation(${v.id})">🗑️ Удалить</button></div>
</div>`;
}).join('');
}
function deleteVacation(id) {
if (confirm('Удалить этот вариант?')) {
vacations = vacations.filter(v => v.id !== id);
renderCards();
updateStats();
}
}
function updateStats() {
const total = vacations.length;
const avg = total > 0 ? Math.round(vacations.reduce((sum, v) => sum + v.budget, 0) / total) : 0;
document.getElementById('totalCount').textContent = total;
document.getElementById('avgBudget').textContent = avg > 0 ? `$${avg.toLocaleString()}` : '$0';
}
function calculateDays(from, to) {
const start = new Date(from);
const end = new Date(to);
const diff = end - start;
return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
}
function formatDateRange(from, to) {
const options = { day: 'numeric', month: 'short' };
return `${new Date(from).toLocaleDateString('ru-RU', options)} — ${new Date(to).toLocaleDateString('ru-RU', options)}`;
}
function formatDate(dateStr) {
return new Date(dateStr).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getCountryFlag(country) {
const flags = { 'Турция': '🇹🇷', 'Таиланд': '🇹🇭', 'ОАЭ': '🇦🇪', 'Мальдивы': '🇲🇻', 'Италия': '🇮🇹', 'Испания': '🇪🇸', 'Грузия': '🇬🇪', 'Египет': '🇪🇬', 'Греция': '🇬🇷', 'Кипр': '🇨🇾' };
return flags[country] || '🌍';
}
function exportToExcel() {
if (vacations.length === 0) { alert('Сначала добавьте варианты отпуска'); return; }
const headers = ['Страна', 'Город', 'Дата вылета', 'Дата возвращения', 'Дней', 'Бюджет ($)', 'Отель', 'Билеты'];
const rows = vacations.map(v => [v.country, v.city, formatDate(v.dateFrom), formatDate(v.dateTo), calculateDays(v.dateFrom, v.dateTo), v.budget, v.hotel, v.tickets]);
const csvContent = '\uFEFF' + [headers.join(';'), ...rows.map(row => row.map(cell => '"' + String(cell || '').replace(/"/g, '""') + '"').join(';'))].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'otpusk_plan.csv';
link.click();
}
renderCards();
updateStats();
</script>
</body>
</html>