sozday-pezu-obuenie-vaybkodi/petmap.html

1023 lines
44 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, maximum-scale=1, user-scalable=no">
<meta name="theme-color" content="#ff6b6b">
<meta name="description" content="PetMap — карта помощи бродячим и потерянным животным Казахстана. Найди, спаси, расскажи.">
<title>PetMap — карта помощи животным</title>
<!-- PWA -->
<link rel="manifest" href="manifest.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="PetMap">
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style>
:root {
--primary: #ff6b6b;
--primary-dark: #ee5a5a;
--secondary: #4ecdc4;
--accent: #ffe66d;
--text: #1a202c;
--text-muted: #718096;
--bg: #f5f7fa;
--card-bg: #ffffff;
--success: #2a9d8f;
--warning: #f4a261;
--danger: #e63946;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); overflow: hidden; }
/* TOP BAR */
.topbar {
position: fixed; top: 0; left: 0; right: 0;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white; padding: 12px 16px; z-index: 1000;
display: flex; align-items: center; gap: 16px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
}
.logo { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 18px; }
.logo-emoji { font-size: 28px; }
.logo-text div:first-child { font-size: 18px; font-weight: 800; }
.logo-text .tagline { font-size: 11px; opacity: 0.9; font-weight: 400; }
.rescued-btn, .memorial-btn {
background: rgba(255,255,255,0.15); border: none; color: white;
padding: 8px 12px; border-radius: 10px; cursor: pointer;
display: flex; align-items: center; gap: 6px; font-size: 12px;
transition: all 0.3s;
}
.rescued-btn:hover, .memorial-btn:hover { background: rgba(255,255,255,0.25); transform: translateY(-1px); }
.rb-count, .mb-count {
background: var(--success); color: white;
padding: 2px 6px; border-radius: 10px; font-size: 11px; font-weight: 700;
}
.mb-count { background: #6c757d; }
.menu-btn {
margin-left: auto; background: rgba(255,255,255,0.15);
border: none; color: white; width: 40px; height: 40px;
border-radius: 10px; cursor: pointer; font-size: 20px;
transition: background 0.3s;
}
.menu-btn:hover { background: rgba(255,255,255,0.25); }
.city-selector {
background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3);
color: white; padding: 8px 12px; border-radius: 8px;
font-size: 13px; cursor: pointer; outline: none;
}
.city-selector option { background: var(--text); color: white; }
/* FILTER BAR */
.filter-bar {
position: fixed; top: 70px; left: 0; right: 0;
background: white; padding: 10px 12px; z-index: 999;
display: flex; gap: 6px; overflow-x: auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.chip {
background: #f0f0f0; border: none; padding: 6px 12px;
border-radius: 16px; font-size: 12px; cursor: pointer;
white-space: nowrap; transition: all 0.3s;
display: flex; align-items: center; gap: 5px;
}
.chip:hover { background: #e0e0e0; }
.chip.active { background: var(--primary); color: white; }
.chip .dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--primary);
}
.chip .dot-vet { background: var(--success); }
.chip .dot-shelter { background: var(--warning); }
.chip .dot-petshop { background: var(--danger); }
/* MAP */
#map { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1; }
/* FAB */
.fab {
position: fixed; bottom: 30px; right: 30px;
width: 60px; height: 60px; border-radius: 50%;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white; border: none; font-size: 30px;
cursor: pointer; box-shadow: 0 4px 20px rgba(255,107,107,0.4);
z-index: 1000; transition: all 0.3s;
}
.fab:hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(255,107,107,0.5); }
/* AI FAB */
.ai-fab {
position: fixed; bottom: 100px; right: 30px;
width: 48px; height: 48px; border-radius: 50%;
background: linear-gradient(135deg, var(--secondary) 0%, #3dbdb5 100%);
color: white; border: none; font-size: 22px;
cursor: pointer; box-shadow: 0 4px 15px rgba(78,205,196,0.3);
z-index: 1000; transition: all 0.3s;
}
.ai-fab:hover { transform: scale(1.05); }
/* AI PANEL */
.ai-panel {
position: fixed; bottom: 160px; right: 30px;
width: 320px; max-height: 420px;
background: white; border-radius: 14px;
box-shadow: 0 8px 35px rgba(0,0,0,0.15);
z-index: 1001; display: flex; flex-direction: column;
overflow: hidden;
}
.ai-panel[hidden] { display: none; }
.ai-panel-head {
background: linear-gradient(135deg, var(--secondary), #3dbdb5);
color: white; padding: 12px 14px;
display: flex; justify-content: space-between; align-items: center;
}
.ai-head-info { display: flex; align-items: center; gap: 8px; }
.ai-head-avatar { font-size: 20px; }
.ai-head-name { font-weight: 700; font-size: 13px; }
.ai-head-status { font-size: 10px; opacity: 0.9; }
.ai-panel-close {
background: rgba(255,255,255,0.2); border: none;
color: white; width: 26px; height: 26px; border-radius: 50%;
cursor: pointer; font-size: 16px;
}
.ai-messages {
flex: 1; overflow-y: auto; padding: 12px;
display: flex; flex-direction: column; gap: 10px;
background: #f8f9fa;
}
.ai-msg {
max-width: 85%; padding: 10px 13px; border-radius: 12px;
font-size: 13px; line-height: 1.5;
}
.ai-msg.ai {
background: white; align-self: flex-start;
border-bottom-left-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.ai-msg.user {
background: var(--secondary); color: white;
align-self: flex-end; border-bottom-right-radius: 4px;
}
.ai-suggestions {
display: flex; gap: 6px; padding: 10px 12px;
overflow-x: auto; background: white;
border-top: 1px solid #e2e8f0;
}
.ai-sug {
background: #e8f8f6; color: var(--secondary);
padding: 6px 11px; border-radius: 16px;
font-size: 11px; white-space: nowrap; cursor: pointer;
border: 1px solid transparent; transition: all 0.3s;
}
.ai-sug:hover { border-color: var(--secondary); background: white; }
.ai-input-row {
display: flex; gap: 6px; padding: 10px 12px;
background: white; border-top: 1px solid #e2e8f0;
}
.ai-input-row input {
flex: 1; padding: 8px 12px; border: 1px solid #e2e8f0;
border-radius: 20px; font-size: 13px; outline: none;
}
.ai-input-row input:focus { border-color: var(--secondary); }
.ai-input-row button {
background: var(--secondary); color: white; border: none;
width: 36px; height: 36px; border-radius: 50%;
cursor: pointer; font-size: 14px;
}
.ai-input-row button:hover { background: #3dbdb5; }
/* DRAWER */
.drawer { position: fixed; inset: 0; z-index: 2000; }
.drawer[hidden] { display: none; }
.drawer-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.drawer-panel {
position: absolute; left: 0; top: 0; bottom: 0;
width: 280px; background: white;
box-shadow: 4px 0 20px rgba(0,0,0,0.15);
display: flex; flex-direction: column;
}
.drawer-head {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white; padding: 18px 14px;
display: flex; justify-content: space-between; align-items: center;
}
.drawer-logo { font-size: 18px; font-weight: 800; }
.drawer-close {
background: rgba(255,255,255,0.2); border: none;
color: white; width: 30px; height: 30px; border-radius: 50%;
cursor: pointer; font-size: 18px;
}
.drawer-nav { flex: 1; padding: 14px 0; }
.drawer-nav button {
width: 100%; padding: 12px 16px; text-align: left;
background: none; border: none; font-size: 14px;
color: var(--text); cursor: pointer;
display: flex; align-items: center; gap: 10px;
transition: background 0.2s;
}
.drawer-nav button:hover { background: #f0f8f7; }
.drawer-footer {
padding: 16px; border-top: 1px solid #e2e8f0;
font-size: 11px; color: var(--text-muted); text-align: center;
}
/* PREVIEW BOTTOM SHEET */
.preview {
position: fixed; bottom: 0; left: 0; right: 0;
background: white; border-radius: 20px 20px 0 0;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
z-index: 1002; transform: translateY(100%);
transition: transform 0.3s;
max-height: 65vh; overflow-y: auto;
}
.preview.open { transform: translateY(0); }
.preview-handle {
width: 40px; height: 4px; background: #cbd5e0;
border-radius: 2px; margin: 12px auto;
}
.preview-content { padding: 0 18px 18px; }
.preview-title { font-size: 18px; font-weight: 700; margin-bottom: 10px; }
.preview-meta { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.preview-tag {
background: #f0f8f7; color: var(--secondary);
padding: 3px 9px; border-radius: 10px; font-size: 11px;
}
.preview-tag.cat-lost { background: #ffe5e5; color: var(--danger); }
.preview-tag.cat-found { background: #e8f8f6; color: var(--success); }
.preview-tag.cat-help { background: #fff4e5; color: var(--warning); }
.preview-desc { color: var(--text-muted); line-height: 1.6; margin-bottom: 14px; font-size: 14px; }
.preview-image {
width: 100%; height: 180px; background: #f0f0f0;
border-radius: 12px; margin-bottom: 14px;
display: flex; align-items: center; justify-content: center;
font-size: 60px;
}
.preview-contact {
background: #f8f9fa; padding: 10px; border-radius: 8px;
margin-bottom: 14px; font-size: 13px;
}
.preview-actions { display: flex; gap: 10px; }
.preview-btn {
flex: 1; padding: 11px; border: none; border-radius: 9px;
font-weight: 600; cursor: pointer; transition: all 0.3s;
font-size: 13px;
}
.preview-btn.primary { background: var(--primary); color: white; }
.preview-btn.primary:hover { background: var(--primary-dark); }
.preview-btn.secondary { background: #f0f0f0; color: var(--text); }
.preview-btn.secondary:hover { background: #e0e0e0; }
.preview-btn.call { background: var(--success); color: white; }
.preview-btn.call:hover { background: #218c7e; }
/* MODAL */
.modal {
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
z-index: 3000; display: none; justify-content: center;
align-items: center; padding: 20px;
}
.modal.open { display: flex; }
.modal-content {
background: white; border-radius: 14px;
max-width: 480px; width: 100%; max-height: 90vh;
overflow-y: auto; padding: 20px;
}
.modal-title { font-size: 20px; font-weight: 700; margin-bottom: 18px; }
.form-group { margin-bottom: 14px; }
.form-group label {
display: block; margin-bottom: 5px; font-weight: 600;
font-size: 13px; color: var(--text-muted);
}
.form-group input, .form-group textarea, .form-group select {
width: 100%; padding: 10px; border: 1px solid #e2e8f0;
border-radius: 8px; font-size: 13px; outline: none;
}
.form-group input:focus, .form-group textarea:focus { border-color: var(--primary); }
.form-actions { display: flex; gap: 10px; margin-top: 18px; }
/* TOAST */
.toast {
position: fixed; bottom: 30px; left: 50%;
transform: translateX(-50%) translateY(100px);
background: var(--text); color: white;
padding: 12px 20px; border-radius: 8px;
z-index: 5000; transition: transform 0.3s;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
font-size: 14px;
}
.toast.show { transform: translateX(-50%) translateY(0); }
/* MASCOT - КОТЁНОК */
.mascot {
position: fixed; bottom: 15px; left: 15px;
width: 90px; height: 90px; z-index: 999;
cursor: pointer; transition: transform 0.3s;
}
.mascot:hover { transform: scale(1.05); }
.mascot-bubble {
position: absolute; bottom: 90px; left: 50%;
transform: translateX(-50%);
background: white; padding: 10px 14px;
border-radius: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
font-size: 12px; white-space: nowrap;
opacity: 0; transition: opacity 0.3s;
pointer-events: none;
}
.mascot:hover .mascot-bubble { opacity: 1; }
/* RUNNING CAT ANIMATION */
.kitty-runner {
position: fixed; width: 100px; height: 80px;
z-index: 998; pointer-events: none;
animation: runAcross 8s linear infinite;
}
.kitty-runner svg { width: 100%; height: 100%; }
@keyframes runAcross {
0% { left: -100px; bottom: 20px; transform: scaleX(1); }
45% { bottom: 20px; }
50% { left: 50%; bottom: 100px; transform: scaleX(1); }
55% { transform: scaleX(-1); }
100% { left: 100%; bottom: 20px; transform: scaleX(-1); }
}
/* RESCUED BOARD */
.rescued-board {
position: fixed; inset: 0; background: var(--bg);
z-index: 4000; overflow-y: auto;
display: none;
}
.rescued-board.open { display: block; }
.rb-header {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white; padding: 16px; position: sticky; top: 0;
display: flex; align-items: center; gap: 12px;
}
.rb-back {
background: rgba(255,255,255,0.2); border: none;
color: white; width: 36px; height: 36px; border-radius: 50%;
cursor: pointer; font-size: 18px;
}
.rb-title { flex: 1; }
.rb-title-main { font-weight: 800; font-size: 16px; }
.rb-title-sub { font-size: 11px; opacity: 0.9; }
.rb-stats {
display: flex; gap: 12px; padding: 16px; overflow-x: auto;
}
.rb-stat {
background: white; padding: 14px 18px; border-radius: 12px;
text-align: center; min-width: 100px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.rb-stat-num { font-size: 24px; font-weight: 800; color: var(--primary); }
.rb-stat-label { font-size: 11px; color: var(--text-muted); }
.rb-filters {
display: flex; gap: 8px; padding: 0 16px 12px; overflow-x: auto;
}
.rb-chip {
background: white; border: 1px solid #e2e8f0;
padding: 6px 12px; border-radius: 16px;
font-size: 12px; cursor: pointer; white-space: nowrap;
}
.rb-chip.active { background: var(--primary); color: white; border-color: var(--primary); }
.rb-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px; padding: 16px;
}
.rb-card {
background: white; border-radius: 12px; overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
transition: transform 0.3s;
}
.rb-card:hover { transform: translateY(-4px); }
.rb-card-img {
height: 160px; background: linear-gradient(135deg, #ffe5e5, #fff);
display: flex; align-items: center; justify-content: center;
font-size: 60px;
}
.rb-card-body { padding: 14px; }
.rb-card-title { font-weight: 700; font-size: 15px; margin-bottom: 6px; }
.rb-card-desc { font-size: 13px; color: var(--text-muted); line-height: 1.5; }
/* MEMORIAL BOARD */
.memorial-page {
position: fixed; inset: 0; background: linear-gradient(180deg, #1a1a2e 0%, #2d2d44 100%);
z-index: 4000; overflow-y: auto; color: white;
display: none;
}
.memorial-page.open { display: block; }
.memorial-header {
padding: 16px; position: sticky; top: 0;
display: flex; align-items: center; gap: 12px;
background: rgba(26,26,46,0.95);
}
.memorial-new-btn {
margin-left: auto; background: rgba(255,255,255,0.15); border: none;
color: white; width: 36px; height: 36px; border-radius: 50%;
cursor: pointer; font-size: 20px;
}
.memorial-quote {
padding: 40px 20px; text-align: center;
background: rgba(255,255,255,0.05);
}
.memorial-candle { font-size: 48px; margin-bottom: 16px; }
.memorial-quote blockquote {
font-size: 18px; font-style: italic; margin-bottom: 12px;
opacity: 0.9;
}
.memorial-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px; padding: 16px;
}
.memorial-card {
background: rgba(255,255,255,0.08); border-radius: 12px;
padding: 16px; text-align: center;
}
.memorial-card-emoji { font-size: 50px; margin-bottom: 12px; }
.memorial-card-name { font-weight: 700; margin-bottom: 6px; }
.memorial-card-date { font-size: 12px; opacity: 0.7; }
@media (max-width: 768px) {
.topbar { padding: 10px 12px; }
.logo-text .tagline { display: none; }
.rescued-btn .rb-text, .memorial-btn .mb-text { display: none; }
.ai-panel { width: calc(100% - 30px); right: 15px; bottom: 160px; }
.fab { bottom: 20px; right: 20px; width: 54px; height: 54px; }
.ai-fab { bottom: 90px; right: 20px; }
.mascot { width: 70px; height: 70px; bottom: 12px; left: 12px; }
.rb-grid, .memorial-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- TOP BAR -->
<header class="topbar">
<div class="logo">
<span class="logo-emoji">🐾</span>
<div class="logo-text">
<div>PetMap</div>
<div class="tagline">Спасаем жизни животных</div>
</div>
</div>
<button class="rescued-btn" id="open-rescued">
<span class="rb-icon">🏆</span>
<span class="rb-text">Спасённые</span>
<span class="rb-count" id="rescued-count">0</span>
</button>
<button class="memorial-btn" id="open-memorial">
<span class="mb-icon">🕯</span>
<span class="mb-text">Память</span>
<span class="mb-count" id="memorial-count">0</span>
</button>
<select class="city-selector" id="city">
<option value="astana">Астана</option>
<option value="almaty">Алматы</option>
<option value="shymkent">Шымкент</option>
<option value="karaganda">Караганда</option>
<option value="aktobe">Актобе</option>
<option value="taraz">Тараз</option>
<option value="pavlodar">Павлодар</option>
<option value="uk">Усть-Каменогорск</option>
<option value="semey">Семей</option>
<option value="kostanay">Костанай</option>
</select>
<button class="menu-btn" id="open-menu" title="Меню">☰</button>
</header>
<!-- FILTER BAR -->
<div class="filter-bar">
<button class="chip chip-all active">Все</button>
<button class="chip" data-cat="lost"><span class="dot"></span>Потерян</button>
<button class="chip" data-cat="found"><span class="dot"></span>Нашёлся</button>
<button class="chip" data-cat="help"><span class="dot"></span>Нужна помощь</button>
<button class="chip" data-cat="home"><span class="dot"></span>Ищет дом</button>
<button class="chip" data-poi="vet"><span class="dot dot-vet"></span>Ветклиники</button>
<button class="chip" data-poi="shelter"><span class="dot dot-shelter"></span>Приюты</button>
</div>
<!-- MAP -->
<div id="map"></div>
<!-- FAB -->
<button class="fab" id="fab" title="Добавить объявление">+</button>
<!-- AI FAB -->
<button class="ai-fab" id="ai-fab" title="ИИ-помощник">💬</button>
<!-- AI PANEL -->
<div class="ai-panel" id="ai-panel" hidden>
<header class="ai-panel-head">
<div class="ai-head-info">
<span class="ai-head-avatar">🐾</span>
<div>
<div class="ai-head-name">PetMap Помощник</div>
<div class="ai-head-status">Онлайн · отвечу за секунду</div>
</div>
</div>
<button class="ai-panel-close" id="ai-panel-close">×</button>
</header>
<div class="ai-messages" id="ai-messages">
<div class="ai-msg ai">
👋 Привет! Я помощник PetMap. Спроси меня:
<br>• Как помочь бездомному?
<br>• Где ближайшая ветклиника?
<br>• Как найти хозяина?
</div>
</div>
<div class="ai-suggestions" id="ai-suggestions">
<button class="ai-sug" data-sug="Как помочь бездомному?">Как помочь?</button>
<button class="ai-sug" data-sug="Где ветклиника?">Ветклиники</button>
<button class="ai-sug" data-sug="Как найти хозяина?">Найти дом</button>
</div>
<form class="ai-input-row" id="ai-form">
<input type="text" id="ai-input" placeholder="Спросите что угодно..." autocomplete="off">
<button type="submit">➤</button>
</form>
</div>
<!-- DRAWER -->
<div class="drawer" id="drawer" hidden>
<div class="drawer-backdrop" id="drawer-backdrop"></div>
<aside class="drawer-panel">
<div class="drawer-head">
<div class="drawer-logo">🐾 PetMap</div>
<button class="drawer-close" id="drawer-close">×</button>
</div>
<nav class="drawer-nav">
<button data-nav="map">🗺️ Карта</button>
<button data-nav="rescued">🏆 Доска спасённых</button>
<button data-nav="memorial">🕯 Доска памяти</button>
<button data-nav="about">ℹ️ О приложении</button>
<button data-nav="install">📲 Установить</button>
</nav>
<div class="drawer-footer">
<div>PetMap © 2025</div>
<div>Сделано с ❤️ для животных Казахстана</div>
</div>
</aside>
</div>
<!-- PREVIEW BOTTOM SHEET -->
<div class="preview" id="preview">
<div class="preview-handle"></div>
<div class="preview-content">
<div class="preview-image" id="preview-image">🐕</div>
<h3 class="preview-title" id="preview-title">Название</h3>
<div class="preview-meta" id="preview-meta"></div>
<p class="preview-desc" id="preview-desc">Описание</p>
<div class="preview-contact" id="preview-contact"></div>
<div class="preview-actions">
<button class="preview-btn call" id="preview-whatsapp">💬 WhatsApp</button>
<button class="preview-btn primary" id="preview-share">📤 Поделиться</button>
<button class="preview-btn secondary" id="preview-close">Закрыть</button>
</div>
</div>
</div>
<!-- MODAL -->
<div class="modal" id="modal">
<div class="modal-content">
<h3 class="modal-title">➕ Добавить объявление</h3>
<form id="animal-form">
<div class="form-group">
<label>Тип объявления</label>
<select name="type">
<option value="lost">🔍 Потерян питомец</option>
<option value="found">👀 Нашёлся на улице</option>
<option value="help">🆘 Нужна помощь</option>
<option value="home">🏠 Ищет дом</option>
</select>
</div>
<div class="form-group">
<label>Вид животного</label>
<select name="animal">
<option value="dog">🐕 Собака</option>
<option value="cat">🐈 Кошка</option>
<option value="bird">🐦 Птица</option>
<option value="other">🐾 Другое</option>
</select>
</div>
<div class="form-group">
<label>Город</label>
<select name="city">
<option value="astana">Астана</option>
<option value="almaty">Алматы</option>
<option value="shymkent">Шымкент</option>
<option value="karaganda">Караганда</option>
<option value="aktobe">Актобе</option>
</select>
</div>
<div class="form-group">
<label>Кличка/Описание</label>
<input type="text" name="title" required placeholder="Бобик, рыжий, средний">
</div>
<div class="form-group">
<label>Где найден/потерян</label>
<input type="text" name="location" placeholder="Улица, район, ориентир">
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" rows="3" placeholder="Окрас, возраст, особенности..."></textarea>
</div>
<div class="form-group">
<label>Контакт (телефон)</label>
<input type="tel" name="contact" required placeholder="+7 777 123 45 67">
</div>
<div class="form-actions">
<button type="button" class="preview-btn secondary" id="modal-close">Отмена</button>
<button type="submit" class="preview-btn primary">Опубликовать</button>
</div>
</form>
</div>
</div>
<!-- RESCUED BOARD -->
<div class="rescued-board" id="rescued-board">
<header class="rb-header">
<button class="rb-back" id="rescued-back">←</button>
<div class="rb-title">
<div class="rb-title-main">🏆 Доска спасённых</div>
<div class="rb-title-sub">Истории успеха через PetMap</div>
</div>
</header>
<div class="rb-stats" id="rb-stats"></div>
<div class="rb-filters" id="rb-filters">
<button class="rb-chip active" data-filter="all">Все</button>
<button class="rb-chip" data-filter="home_found">🏠 Новый дом</button>
<button class="rb-chip" data-filter="owner_found">💚 К хозяину</button>
<button class="rb-chip" data-filter="treated">🩹 Вылечены</button>
</div>
<div class="rb-grid" id="rb-grid"></div>
</div>
<!-- MEMORIAL BOARD -->
<div class="memorial-page" id="memorial-page">
<header class="memorial-header">
<button class="rb-back" id="memorial-back">←</button>
<div class="rb-title">
<div class="rb-title-main">🕯 Доска памяти</div>
<div class="rb-title-sub">Тем, кого не успели спасти</div>
</div>
<button class="memorial-new-btn" id="memorial-new-btn">+</button>
</header>
<section class="memorial-quote">
<div class="memorial-candle">🕯</div>
<blockquote>«Зачем кому-то умирать, чтобы он нами был замечен?»</blockquote>
<p>Каждая история — напоминание не быть безразличным.</p>
</section>
<div class="memorial-grid" id="memorial-grid"></div>
</div>
<!-- TOAST -->
<div class="toast" id="toast"></div>
<!-- MASCOT - КОТЁНОК -->
<div class="mascot" id="mascot" title="Котёнок-маскот">
<div class="mascot-bubble" id="mascot-bubble">Нажми + чтобы помочь! 🐾</div>
<svg viewBox="0 0 100 100">
<defs>
<radialGradient id="mg" cx="0.4" cy="0.4" r="0.7">
<stop offset="0%" stop-color="#ffb070"/>
<stop offset="100%" stop-color="#ff9a3c"/>
</radialGradient>
</defs>
<circle cx="50" cy="50" r="45" fill="url(#mg)"/>
<!-- Уши -->
<path d="M20 25 L25 10 L35 28 Z" fill="#ff9a3c"/>
<path d="M65 28 L75 10 L80 25 Z" fill="#ff9a3c"/>
<path d="M24 24 L27 16 L32 26 Z" fill="#ffb8c5"/>
<path d="M68 26 L73 16 L76 24 Z" fill="#ffb8c5"/>
<!-- Глаза -->
<ellipse cx="35" cy="45" rx="8" ry="10" fill="white"/>
<ellipse cx="65" cy="45" rx="8" ry="10" fill="white"/>
<ellipse cx="35" cy="46" rx="5" ry="7" fill="#1a202c"/>
<ellipse cx="65" cy="46" rx="5" ry="7" fill="#1a202c"/>
<circle cx="37" cy="43" r="2" fill="white"/>
<circle cx="67" cy="43" r="2" fill="white"/>
<!-- Нос -->
<path d="M47 58 Q50 61 53 58 Q53 56 50 55.5 Q47 56 47 58 Z" fill="#ff6b6b"/>
<!-- Рот -->
<path d="M50 60 Q47 63 44 62 M50 60 Q53 63 56 62" stroke="#1a202c" stroke-width="1.5" fill="none" stroke-linecap="round"/>
<!-- Щёки -->
<circle cx="25" cy="55" r="5" fill="#ff6b6b" opacity="0.3"/>
<circle cx="75" cy="55" r="5" fill="#ff6b6b" opacity="0.3"/>
<!-- Усы -->
<line x1="15" y1="52" x2="28" y2="53" stroke="#666" stroke-width="0.8"/>
<line x1="15" y1="56" x2="28" y2="56" stroke="#666" stroke-width="0.8"/>
<line x1="72" y1="53" x2="85" y2="52" stroke="#666" stroke-width="0.8"/>
<line x1="72" y1="56" x2="85" y2="56" stroke="#666" stroke-width="0.8"/>
</svg>
</div>
<!-- RUNNING CAT -->
<div class="kitty-runner" id="kitty-runner" hidden>
<svg viewBox="0 0 120 100">
<defs>
<radialGradient id="rfk-fur" cx="0.4" cy="0.4" r="0.75">
<stop offset="0%" stop-color="#ffd9b3"/>
<stop offset="65%" stop-color="#ffa755"/>
<stop offset="100%" stop-color="#ff7e1f"/>
</radialGradient>
</defs>
<!-- Тень -->
<ellipse cx="60" cy="94" rx="38" ry="3.5" fill="#000" opacity="0.18"/>
<!-- Хвост -->
<path d="M95 60 Q112 42 108 20" stroke="url(#rfk-fur)" stroke-width="12" fill="none" stroke-linecap="round"/>
<!-- Тело -->
<ellipse cx="62" cy="68" rx="26" ry="16" fill="url(#rfk-fur)"/>
<ellipse cx="62" cy="74" rx="18" ry="9" fill="#fff5e2"/>
<!-- Лапки -->
<ellipse cx="78" cy="86" rx="6" ry="5" fill="#ff9a3c"/>
<ellipse cx="68" cy="86" rx="6" ry="5" fill="#ff8a30"/>
<ellipse cx="44" cy="86" rx="6" ry="5" fill="#ff9a3c"/>
<ellipse cx="34" cy="86" rx="6" ry="5" fill="#ff8a30"/>
<!-- Голова -->
<path d="M6 36 L13 8 L24 32 Z" fill="url(#rfk-fur)"/>
<path d="M34 32 L42 8 L48 36 Z" fill="url(#rfk-fur)"/>
<circle cx="27" cy="48" r="24" fill="url(#rfk-fur)"/>
<!-- Глаза -->
<ellipse cx="17" cy="48" rx="6.5" ry="8" fill="white"/>
<ellipse cx="37" cy="48" rx="6.5" ry="8" fill="white"/>
<ellipse cx="17" cy="49" rx="4.5" ry="6" fill="#2a9d8f"/>
<ellipse cx="37" cy="49" rx="4.5" ry="6" fill="#2a9d8f"/>
<ellipse cx="17" cy="49" rx="2" ry="5" fill="#1a1a2e"/>
<ellipse cx="37" cy="49" rx="2" ry="5" fill="#1a1a2e"/>
<circle cx="18.5" cy="45" r="2" fill="white"/>
<circle cx="38.5" cy="45" r="2" fill="white"/>
<!-- Нос и рот -->
<path d="M24 58 Q27 61 30 58 Q30 56 27 56.5 Q24 56 24 58 Z" fill="#ff6b6b"/>
<path d="M27 60 Q24 63 21 62 M27 60 Q30 63 33 62" stroke="#1a1a2e" stroke-width="1.1" fill="none" stroke-linecap="round"/>
</svg>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ДАННЫЕ ЖИВОТНЫХ
const ANIMALS = [
{ id: 1, type: 'lost', animal: 'dog', city: 'astana', title: 'Бобик, лабрадор', location: 'район Арбата', description: 'Пропал 5 августа, ошейник синий', contact: '+7 777 111 22 33', emoji: '🐕' },
{ id: 2, type: 'found', animal: 'cat', city: 'almaty', title: 'Рыжий котёнок', location: 'ул. Абая, 45', description: 'Нашёлся у подъезда, около 3 месяцев', contact: '+7 701 234 56 78', emoji: '🐈' },
{ id: 3, type: 'help', animal: 'dog', city: 'astana', title: 'Собака ранена', location: 'проспект Туран', description: 'Нужна срочная помощь, не ходит на лапу', contact: '+7 778 999 88 77', emoji: '🆘' },
{ id: 4, type: 'home', animal: 'cat', city: 'shymkent', title: 'Мурка ищет дом', location: 'приют "Достык"', description: 'Стерилизована, привита, ласковая', contact: '+7 775 444 33 22', emoji: '🏠' },
{ id: 5, type: 'lost', animal: 'bird', city: 'karaganda', title: 'Попугай Кеша', location: 'микрорайон Юго-Восток', description: 'Говорит "привет", зелёный', contact: '+7 702 555 66 77', emoji: '🐦' },
];
// СПАСЁННЫЕ
const RESCUED = [
{ id: 1, title: 'Бобик нашёл дом', emoji: '🐕', outcome: 'home_found', desc: 'Забрала семья из Астаны' },
{ id: 2, title: 'Рыжик вернулся к хозяину', emoji: '🐈', outcome: 'owner_found', desc: 'Нашли по чипу' },
{ id: 3, title: 'Каспер вылечен', emoji: '🩹', outcome: 'treated', desc: 'Операция прошла успешно' },
{ id: 4, title: 'Луна в новой семье', emoji: '🐕', outcome: 'home_found', desc: 'Уехала в Алматы' },
];
// МЕМОРИАЛ
const MEMORIAL = [
{ id: 1, name: 'Рекс', emoji: '🐕', date: '2025' },
{ id: 2, name: 'Барсик', emoji: '🐈', date: '2024' },
{ id: 3, name: 'Кеша', emoji: '🐦', date: '2025' },
];
// ВЕТКЛИНИКИ И ПРИЮТЫ
const POIS = [
{ id: 101, type: 'vet', city: 'astana', title: 'Ветклиника "Айболит"', location: 'ул. Кенесары, 35', contact: '+7 7172 55 66 77', emoji: '🏥' },
{ id: 102, type: 'vet', city: 'almaty', title: 'Ветцентр "ЗооМир"', location: 'пр. Абая, 120', contact: '+7 727 333 44 55', emoji: '🏥' },
{ id: 103, type: 'shelter', city: 'astana', title: 'Приют "Достык"', location: 'п. Коктал-2', contact: '+7 777 888 99 00', emoji: '🏠' },
{ id: 104, type: 'shelter', city: 'almaty', title: 'Приют "Бауыр"', location: 'с. Байсерке', contact: '+7 701 222 33 44', emoji: '🏠' },
];
const CITIES = {
astana: { lat: 51.1694, lng: 71.4491, name: 'Астана' },
almaty: { lat: 43.2220, lng: 76.8512, name: 'Алматы' },
shymkent: { lat: 42.3000, lng: 69.6000, name: 'Шымкент' },
karaganda: { lat: 49.8047, lng: 73.1094, name: 'Караганда' },
aktobe: { lat: 50.2839, lng: 57.1670, name: 'Актобе' },
taraz: { lat: 42.9000, lng: 71.3667, name: 'Тараз' },
pavlodar: { lat: 52.2833, lng: 76.9667, name: 'Павлодар' },
uk: { lat: 49.9667, lng: 82.6000, name: 'Усть-Каменогорск' },
semey: { lat: 50.4111, lng: 80.2275, name: 'Семей' },
kostanay: { lat: 53.2144, lng: 63.6246, name: 'Костанай' }
};
// Карта
const map = L.map('map', { zoomControl: false }).setView([51.1694, 71.4491], 11);
L.control.zoom({ position: 'bottomright' }).addTo(map);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap'
}).addTo(map);
const markers = [];
function addMarkers(filter = 'all') {
markers.forEach(m => map.removeLayer(m));
markers.length = 0;
const items = filter.startsWith('poi-') ? POIS : ANIMALS;
items.forEach(item => {
const itemType = item.type || item.cat;
if (filter !== 'all' && filter !== 'poi-all') {
if (filter.startsWith('poi-') && item.type !== filter.replace('poi-', '')) return;
if (!filter.startsWith('poi-') && itemType !== filter) return;
}
const city = CITIES[item.city];
if (!city) return;
const marker = L.marker([city.lat, city.lng]).addTo(map);
marker.bindPopup(`<b>${item.emoji} ${item.title}</b><br>${item.location}`);
marker.on('click', () => showPreview(item));
markers.push(marker);
});
document.getElementById('rescued-count').textContent = RESCUED.length;
document.getElementById('memorial-count').textContent = MEMORIAL.length;
}
addMarkers();
// Фильтры
document.querySelectorAll('.chip').forEach(chip => {
chip.addEventListener('click', () => {
document.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
chip.classList.add('active');
const filter = chip.dataset.cat || chip.dataset.poi || 'all';
addMarkers(filter);
});
});
// Preview
const preview = document.getElementById('preview');
function showPreview(item) {
document.getElementById('preview-image').textContent = item.emoji;
document.getElementById('preview-title').textContent = item.title;
document.getElementById('preview-meta').innerHTML = `
<span class="preview-tag cat-${item.type}">${getTypeName(item.type)}</span>
<span class="preview-tag">${CITIES[item.city]?.name}</span>
`;
document.getElementById('preview-desc').textContent = item.description;
document.getElementById('preview-contact').textContent = `📞 ${item.contact}`;
// WhatsApp — сразу в чат
document.getElementById('preview-whatsapp').onclick = () => {
const phone = item.contact.replace(/[^0-9]/g, '');
const msg = encodeURIComponent(`Здравствуйте! По объявлению: ${item.title}`);
window.open(`https://wa.me/${phone}?text=${msg}`, '_blank');
};
document.getElementById('preview-share').onclick = () => {
if (navigator.share) {
navigator.share({ title: item.title, text: item.description });
} else {
showToast('📋 Скопировано в буфер');
}
};
preview.classList.add('open');
}
document.getElementById('preview-close').onclick = () => preview.classList.remove('open');
function getTypeName(type) {
const names = { lost: 'Потерян', found: 'Нашёлся', help: 'Нужна помощь', home: 'Ищет дом', vet: 'Ветклиника', shelter: 'Приют' };
return names[type] || type;
}
// Modal
const modal = document.getElementById('modal');
document.getElementById('fab').onclick = () => modal.classList.add('open');
document.getElementById('modal-close').onclick = () => modal.classList.remove('open');
document.getElementById('animal-form').onsubmit = (e) => {
e.preventDefault();
showToast('✅ Объявление опубликовано!');
modal.classList.remove('open');
};
// Drawer
const drawer = document.getElementById('drawer');
document.getElementById('open-menu').onclick = () => drawer.hidden = false;
document.getElementById('drawer-close').onclick = () => drawer.hidden = true;
document.getElementById('drawer-backdrop').onclick = () => drawer.hidden = true;
// AI Panel
const aiPanel = document.getElementById('ai-panel');
document.getElementById('ai-fab').onclick = () => aiPanel.hidden = false;
document.getElementById('ai-panel-close').onclick = () => aiPanel.hidden = true;
const aiMessages = document.getElementById('ai-messages');
const aiForm = document.getElementById('ai-form');
const aiInput = document.getElementById('ai-input');
aiForm.onsubmit = (e) => {
e.preventDefault();
const msg = aiInput.value.trim();
if (!msg) return;
aiMessages.innerHTML += `<div class="ai-msg user">${msg}</div>`;
aiInput.value = '';
setTimeout(() => {
const responses = {
'Как помочь бездомному?': '🐾 1. Покормите 2. Проверьте чип 3. Позвоните в приют 4. Добавьте объявление на карту',
'Где ветклиника?': '🏥 Выберите фильтр "Ветклиники" на карте — увидите все ближайшие клиники!',
'Как найти хозяина?': '🏠 1. Разместите объявление 2. Проверьте потерянных 3. Обратитесь в приюты 4. Используйте соцсети'
};
const answer = responses[msg] || 'Спасибо за вопрос! Я учусь. Попробуйте выбрать вариант из подсказок.';
aiMessages.innerHTML += `<div class="ai-msg ai">${answer}</div>`;
aiMessages.scrollTop = aiMessages.scrollHeight;
}, 500);
};
document.querySelectorAll('.ai-sug').forEach(btn => {
btn.onclick = () => { aiInput.value = btn.dataset.sug; aiForm.requestSubmit(); };
});
// Mascot
const mascot = document.getElementById('mascot');
const mascotBubble = document.getElementById('mascot-bubble');
let clickCount = 0;
mascot.onclick = () => {
clickCount++;
const messages = ['Нажми + чтобы помочь! 🐾', 'Каждое объявление спасает жизнь!', 'Спасибо, что неравнодушен! ❤️', 'Вместе мы можем больше! 🐾'];
mascotBubble.textContent = messages[clickCount % messages.length];
};
// Running cat occasionally
const kittyRunner = document.getElementById('kitty-runner');
setInterval(() => {
if (Math.random() > 0.7) {
kittyRunner.hidden = false;
setTimeout(() => { kittyRunner.hidden = true; }, 8000);
}
}, 30000);
// Toast
function showToast(msg) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
// Rescued Board
const rescuedBoard = document.getElementById('rescued-board');
document.getElementById('open-rescued').onclick = () => {
rescuedBoard.classList.add('open');
renderRescued('all');
};
document.getElementById('rescued-back').onclick = () => rescuedBoard.classList.remove('open');
function renderRescued(filter) {
const grid = document.getElementById('rb-grid');
const filtered = filter === 'all' ? RESCUED : RESCUED.filter(r => r.outcome === filter);
grid.innerHTML = filtered.map(r => `
<div class="rb-card">
<div class="rb-card-img">${r.emoji}</div>
<div class="rb-card-body">
<div class="rb-card-title">${r.title}</div>
<div class="rb-card-desc">${r.desc}</div>
</div>
</div>
`).join('');
document.getElementById('rb-stats').innerHTML = `
<div class="rb-stat"><div class="rb-stat-num">${RESCUED.length}</div><div class="rb-stat-label">Всего спасено</div></div>
<div class="rb-stat"><div class="rb-stat-num">${RESCUED.filter(r => r.outcome === 'home_found').length}</div><div class="rb-stat-label">Нашли дом</div></div>
<div class="rb-stat"><div class="rb-stat-num">${RESCUED.filter(r => r.outcome === 'owner_found').length}</div><div class="rb-stat-label">К хозяину</div></div>
<div class="rb-stat"><div class="rb-stat-num">${RESCUED.filter(r => r.outcome === 'treated').length}</div><div class="rb-stat-label">Вылечены</div></div>
`;
}
document.querySelectorAll('.rb-chip').forEach(chip => {
chip.onclick = () => {
document.querySelectorAll('.rb-chip').forEach(c => c.classList.remove('active'));
chip.classList.add('active');
renderRescued(chip.dataset.filter);
};
});
// Memorial Board
const memorialPage = document.getElementById('memorial-page');
document.getElementById('open-memorial').onclick = () => {
memorialPage.classList.add('open');
renderMemorial();
};
document.getElementById('memorial-back').onclick = () => memorialPage.classList.remove('open');
function renderMemorial() {
document.getElementById('memorial-grid').innerHTML = MEMORIAL.map(m => `
<div class="memorial-card">
<div class="memorial-card-emoji">${m.emoji}</div>
<div class="memorial-card-name">${m.name}</div>
<div class="memorial-card-date">${m.date}</div>
</div>
`).join('');
}
// City selector
document.getElementById('city').onchange = (e) => {
const city = CITIES[e.target.value];
if (city) map.flyTo([city.lat, city.lng], 11);
};
</script>
</body>
</html>