sozday-pezu-obuenie-vaybkodi/petmap.html

1267 lines
68 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>
/* Кастомные иконки для маркеров */
.custom-marker {
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.marker-lost { filter: drop-shadow(0 2px 4px rgba(230,57,70,0.4)); }
.marker-found { filter: drop-shadow(0 2px 4px rgba(42,157,143,0.4)); }
.marker-help { filter: drop-shadow(0 2px 4px rgba(244,162,97,0.4)); }
.marker-home { filter: drop-shadow(0 2px 4px rgba(78,205,196,0.4)); }
.marker-vet { filter: drop-shadow(0 2px 4px rgba(42,157,143,0.4)); }
.marker-shelter { filter: drop-shadow(0 2px 4px rgba(244,162,97,0.4)); }
.marker-petshop { filter: drop-shadow(0 2px 4px rgba(255,107,107,0.4)); }
</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, box-shadow 0.3s;
}
.rb-card:hover { transform: translateY(-6px); box-shadow: 0 8px 20px rgba(0,0,0,0.12); }
.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; color: var(--text); }
.rb-card-desc { font-size: 13px; color: var(--text-muted); line-height: 1.5; margin-bottom: 8px; }
/* 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;
transition: transform 0.3s;
}
.memorial-card:hover { transform: translateY(-4px); background: rgba(255,255,255,0.1); }
.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; }
.memorial-card-desc { font-size: 12px; opacity: 0.8; margin-top: 8px; line-height: 1.4; }
@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>
<option value="taraz">Тараз</option>
<option value="pavlodar">Павлодар</option>
<option value="uk">Усть-Каменогорск</option>
<option value="semey">Семей</option>
<option value="kostanay">Костанай</option>
</select>
</div>
<div class="form-group">
<label>🗺️ Точка на карте</label>
<input type="text" name="mapcoords" placeholder="Кликните на карту для выбора места" readonly id="mapcoords">
</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: 'Бобик, лабрадор 5 лет', location: 'район Арбата', description: 'Пропал 5 августа 2025, синий ошейник, отзывчивый. Очень ждём!', contact: '+7 777 111 22 33', emoji: '🐕', lat: 51.1594, lng: 71.4391 },
{ id: 2, type: 'found', animal: 'cat', city: 'astana', title: 'Котёнок рыжий, 3 мес', location: 'ЖК Нуржол, подъезд 5', description: 'Нашёлся 10 августа, игривый, здоровый. Ищем хозяев!', contact: '+7 701 234 56 78', emoji: '🐈', lat: 51.1694, lng: 71.4591 },
{ id: 3, type: 'help', animal: 'dog', city: 'astana', title: 'Собака ранена, нужна помощь', location: 'проспект Туран, 42', description: 'Сбита машиной, не ходит на задние лапы. Срочно нужна клиника!', contact: '+7 778 999 88 77', emoji: '🆘', lat: 51.1794, lng: 71.4291 },
{ id: 4, type: 'home', animal: 'cat', city: 'astana', title: 'Мурка, 2 года', location: 'район Коктал', description: 'Стерилизована, привита, ласковая. Отдадим в добрые руки!', contact: '+7 775 444 33 22', emoji: '🏠', lat: 51.1494, lng: 71.4691 },
{ id: 5, type: 'lost', animal: 'dog', city: 'astana', title: 'Хаски Арон, 3 года', location: 'микрорайон Юго-Восток', description: 'Пропал 1 августа, серо-белый, голубые глаза. Особая примета — белое пятно на спине.', contact: '+7 702 555 66 77', emoji: '🐕', lat: 51.1394, lng: 71.4891 },
// АЛМАТЫ
{ id: 6, type: 'lost', animal: 'dog', city: 'almaty', title: 'Овчарка Рекс, 4 года', location: 'ул. Абая, угол Розыбакиева', description: 'Пропал 3 августа, чёрно-подпалый, строгий ошейник. Вознаграждение!', contact: '+7 777 222 33 44', emoji: '🐕', lat: 43.2320, lng: 76.8612 },
{ id: 7, type: 'found', animal: 'cat', city: 'almaty', title: 'Кошка белая, беременная', location: 'ул. Сатпаева, 28', description: 'Нашлась 8 августа, очень ласковая. Нужен дом до родов.', contact: '+7 701 333 44 55', emoji: '🐈', lat: 43.2420, lng: 76.8712 },
{ id: 8, type: 'help', animal: 'dog', city: 'almaty', title: 'Щенки, нужны руки', location: 'ул. Толе би, 100', description: '5 щенят, 2 месяца, здоровые. Кормим сами, но не справляемся. Помогите пристроить!', contact: '+7 778 444 55 66', emoji: '🐕', lat: 43.2520, lng: 76.8512 },
{ id: 9, type: 'home', animal: 'cat', city: 'almaty', title: 'Кот Барсик, 5 лет', location: 'мкр. Таугуль', description: 'Спокойный, кастрирован, приучен к лотку. Отдаём из-за переезда.', contact: '+7 775 555 66 77', emoji: '🏠', lat: 43.2120, lng: 76.8812 },
{ id: 10, type: 'lost', animal: 'bird', city: 'almaty', title: 'Попугай Корелла, Леша', location: 'пр. Достык, 65', description: 'Улетел 6 августа, жёлто-серый, говорит "привет". Очень любим!', contact: '+7 702 666 77 88', emoji: '🐦', lat: 43.2220, lng: 76.8912 },
// ШЫМКЕНТ
{ id: 11, type: 'found', animal: 'dog', city: 'shymkent', title: 'Дворняга Дружок', location: 'ул. Калбекова, 15', description: 'Нашёлся 9 августа, средний, добрый. Хозяева, отзовитесь!', contact: '+7 777 333 22 11', emoji: '🐕', lat: 42.3100, lng: 69.5900 },
{ id: 12, type: 'home', animal: 'cat', city: 'shymkent', title: 'Котята 3 шт, 2 мес', location: 'мкр. Самал', description: 'Два рыжих, один чёрный. Кушают сами, здоровые. В добрые руки!', contact: '+7 701 444 33 22', emoji: '🐈', lat: 42.2900, lng: 69.6100 },
{ id: 13, type: 'help', animal: 'dog', city: 'shymkent', title: 'Пёс под машиной', location: 'ул. Рыскулова, 88', description: 'Лежит под машиной во дворе, не выходит. Нужна помощь с отловом.', contact: '+7 778 555 44 33', emoji: '🆘', lat: 42.3200, lng: 69.5800 },
// КАРАГАНДА
{ id: 14, type: 'lost', animal: 'cat', city: 'karaganda', title: 'Кошка Муся, 6 лет', location: 'микрорайон Юго-Восток, 56/8', description: 'Пропала 4 августа, черепаховый окрас, очень пугливая.', contact: '+7 777 444 55 66', emoji: '🐈', lat: 49.8147, lng: 73.1194 },
{ id: 15, type: 'found', animal: 'dog', city: 'karaganda', title: 'Лайка, 2 года', location: 'ул. Ермекова, 30', description: 'Нашлась 7 августа, белая, худая. Похожа на ездовую. Кормим временно.', contact: '+7 701 555 66 77', emoji: '🐕', lat: 49.7947, lng: 73.0994 },
{ id: 16, type: 'home', animal: 'dog', city: 'karaganda', title: 'Щенок Шарик, 4 мес', location: 'п. Михайловка', description: 'Дворняжка, умный, игривый. Ищет дом навсегда.', contact: '+7 775 666 77 88', emoji: '🐕', lat: 49.8247, lng: 73.1294 },
// АКТОБЕ
{ id: 17, type: 'lost', animal: 'dog', city: 'aktobe', title: 'Той-терьер Тина', location: 'ул. Шерниязова, 22', description: 'Пропала 2 августа, чёрно-подпалая, красный бантик. Очень маленькая.', contact: '+7 777 666 77 88', emoji: '🐕', lat: 50.2939, lng: 57.1770 },
{ id: 18, type: 'help', animal: 'cat', city: 'aktobe', title: 'Кот после травмы', location: 'ул. Айтеке би, 45', description: 'Подобрали с переломом, в гипсе. Нужна передержка на восстановление.', contact: '+7 701 777 88 99', emoji: '🐈', lat: 50.2739, lng: 57.1570 },
// ТАРАЗ
{ id: 19, type: 'found', animal: 'cat', city: 'taraz', title: 'Кот серый, взрослый', location: 'ул. Байтурсынова, 18', description: 'Нашёлся 5 августа, крупный, спокойный. Похож на домашнего.', contact: '+7 777 888 99 00', emoji: '🐈', lat: 42.9100, lng: 71.3767 },
{ id: 20, type: 'home', animal: 'dog', city: 'taraz', title: 'Овчарка смешанная, 3 года', location: 'мкр. Бауыржан Момышулы', description: 'Добрая, охраняет, любит детей. Отдаём из-за аллергии у ребёнка.', contact: '+7 775 999 00 11', emoji: '🐕', lat: 42.8900, lng: 71.3567 },
// ПАВЛОДАР
{ id: 21, type: 'lost', animal: 'dog', city: 'pavlodar', title: 'Спаниель Ральф', location: 'ул. Кривенко, 52', description: 'Пропал 1 августа, коричневый, белые лапки. Ошейник с адресником.', contact: '+7 777 000 11 22', emoji: '🐕', lat: 52.2933, lng: 76.9767 },
{ id: 22, type: 'help', animal: 'dog', city: 'pavlodar', title: 'Стая собак у рынка', location: 'Центральный рынок', description: '6-7 собак, мешают торговле. Нужен отлов или приют.', contact: '+7 701 111 22 33', emoji: '🆘', lat: 52.2733, lng: 76.9567 },
// УСТЬ-КАМЕНОГОРСК
{ id: 23, type: 'found', animal: 'cat', city: 'uk', title: 'Котёнок чёрный, 4 мес', location: 'пр. Независимости, 62', description: 'Нашёлся 8 августа, игривый, здоровый. Ищет дом.', contact: '+7 777 222 33 44', emoji: '🐈', lat: 49.9767, lng: 82.6100 },
{ id: 24, type: 'home', animal: 'rabbit', city: 'uk', title: 'Кролик Пушок, 1 год', location: 'ул. Горького, 35', description: 'Белый, декоративный. Отдаём с клеткой и кормом.', contact: '+7 701 333 44 55', emoji: '🐇', lat: 49.9567, lng: 82.5900 },
// СЕМЕЙ
{ id: 25, type: 'lost', animal: 'cat', city: 'semey', title: 'Кот Рыжик, 7 лет', location: 'ул. Абай, 45', description: 'Пропал 3 августа, очень рыжий, толстый. Не выходит на улицу обычно.', contact: '+7 777 444 55 66', emoji: '🐈', lat: 50.4211, lng: 80.2375 },
{ id: 26, type: 'help', animal: 'dog', city: 'semey', title: 'Щенки 8 шт, 1 мес', location: 'п. Ново-Баженово', description: 'Бездомная мама родила в сарае. Срочно нужны руки или передержка.', contact: '+7 778 555 66 77', emoji: '🐕', lat: 50.4011, lng: 80.2175 },
// КОСТАНАЙ
{ id: 27, type: 'found', animal: 'dog', city: 'kostanay', title: 'Лабрадор светлый', location: 'ул. Ауэзова, 28', description: 'Нашёлся 6 августа, без ошейника, чистый. Похож на породного.', contact: '+7 777 666 77 88', emoji: '🐕', lat: 53.2244, lng: 63.6346 },
{ id: 28, type: 'home', animal: 'cat', city: 'kostanay', title: 'Кошка Соня, 4 года', location: 'мкр. Восточный', description: 'Спокойная, домашняя. Отдаём из-за рождения ребёнка.', contact: '+7 701 777 88 99', emoji: '🐈', lat: 53.2044, lng: 63.6146 },
];
// СПАСЁННЫЕ — истории успеха
const RESCUED = [
{ id: 1, title: 'Бобик нашёл семью в Астане', emoji: '🐕', outcome: 'home_found', desc: 'Лабрадор, потерялся 05.08.2025. Через 3 дня нашёлся благодаря PetMap! Забрала семья из Алматы.', city: 'Астана', date: '15.08.2025' },
{ id: 2, title: 'Рыжик вернулся домой по чипу', emoji: '🐈', outcome: 'owner_found', desc: 'Кот 5 лет, пропал в Караганде. Волонтёры проверили чип — хозяин нашёлся через 2 дня!', city: 'Караганда', date: '12.08.2025' },
{ id: 3, title: 'Каспер вылечен и пристроен', emoji: '🩹', outcome: 'treated', desc: 'Пёс с переломом, операция прошла успешно. Теперь живёт в новой семье в Шымкенте.', city: 'Шымкент', date: '10.08.2025' },
{ id: 4, title: 'Луна уехала в Германию', emoji: '🐕', outcome: 'home_found', desc: 'Дворняжка из приюта Алматы. Девушка увидела в PetMap и оформила документы на вывоз.', city: 'Алматы', date: '18.08.2025' },
{ id: 5, title: 'Хаски Арон найден', emoji: '🐕', outcome: 'owner_found', desc: 'Пропал в Астане 01.08.2025. Нашёлся через 5 дней в соседнем дворе — заперли дети.', city: 'Астана', date: '06.08.2025' },
{ id: 6, title: 'Котята из Таугуля — все в доме', emoji: '🐈', outcome: 'home_found', desc: '3 котёнка, 2 месяца. Разобрали по домам за 4 дня через объявления.', city: 'Алматы', date: '14.08.2025' },
{ id: 7, title: 'Овчарка Рекс — герой дня', emoji: '🐕', outcome: 'owner_found', desc: 'Убежал во время прогулки. Хозяин разместил в PetMap — нашёлся через 12 часов!', city: 'Алматы', date: '04.08.2025' },
{ id: 8, title: 'Тина той-терьер дома', emoji: '🐕', outcome: 'owner_found', desc: 'Маленькая собачка потерялась в Актобе. Нашли через 2 дня — сидела в подъезде.', city: 'Актобе', date: '03.08.2025' },
];
// МЕМОРИАЛ — те, кого не успели спасти
const MEMORIAL = [
{ id: 1, name: 'Рекс', emoji: '🐕', date: '2025', city: 'Астана', desc: 'Дворняга, сбит машиной. Не успели довезти до клиники.' },
{ id: 2, name: 'Барсик', emoji: '🐈', date: '2024', city: 'Алматы', desc: 'Кот 10 лет, старость. Ушёл на радугу в окружении любимой семьи.' },
{ id: 3, name: 'Кеша', emoji: '🐦', date: '2025', city: 'Караганда', desc: 'Попугай, улетел и не нашёлся. Хозяева очень скучают.' },
{ id: 4, name: 'Мухтар', emoji: '🐕', date: '2025', city: 'Шымкент', desc: 'Овчарка, болезнь. Боролся 3 месяца, но не выдержало сердце.' },
{ id: 5, name: 'Снежок', emoji: '🐈', date: '2024', city: 'Павлодар', desc: 'Белый кот, отравление. Не смогли спасти, слишком поздно обратились.' },
];
// ВЕТКЛИНИКИ, ПРИЮТЫ, ЗООМАГАЗИНЫ — по всем городам
const POIS = [
// АСТАНА
{ id: 101, type: 'vet', city: 'astana', title: 'Ветклиника "Айболит"', location: 'ул. Кенесары, 35', contact: '+7 7172 55 66 77', emoji: '🏥', lat: 51.1694, lng: 71.4491, workHours: 'Круглосуточно' },
{ id: 102, type: 'vet', city: 'astana', title: 'Ветцентр "ЗооДоктор"', location: 'пр. Туран, 42', contact: '+7 7172 66 77 88', emoji: '🏥', lat: 51.1794, lng: 71.4291, workHours: '9:00-21:00' },
{ id: 103, type: 'shelter', city: 'astana', title: 'Приют "Достык"', location: 'п. Коктал-2', contact: '+7 777 888 99 00', emoji: '🏠', lat: 51.1494, lng: 71.4691, workHours: '10:00-18:00' },
{ id: 104, type: 'petshop', city: 'astana', title: 'Зоомагазин "Лапки"', location: 'ЖК Нуржол, 12', contact: '+7 701 222 33 44', emoji: '🛒', lat: 51.1594, lng: 71.4591, workHours: '10:00-22:00' },
// АЛМАТЫ
{ id: 105, type: 'vet', city: 'almaty', title: 'Ветцентр "ЗооМир"', location: 'пр. Абая, 120', contact: '+7 727 333 44 55', emoji: '🏥', lat: 43.2320, lng: 76.8612, workHours: 'Круглосуточно' },
{ id: 106, type: 'vet', city: 'almaty', title: 'Клиника "Верный друг"', location: 'ул. Сатпаева, 28', contact: '+7 727 444 55 66', emoji: '🏥', lat: 43.2420, lng: 76.8712, workHours: '8:00-22:00' },
{ id: 107, type: 'shelter', city: 'almaty', title: 'Приют "Бауыр"', location: 'с. Байсерке', contact: '+7 701 222 33 44', emoji: '🏠', lat: 43.2120, lng: 76.8812, workHours: '10:00-17:00' },
{ id: 108, type: 'petshop', city: 'almaty', title: 'Зообазар "4 Лапы"', location: 'ул. Толе би, 100', contact: '+7 777 333 22 11', emoji: '🛒', lat: 43.2520, lng: 76.8512, workHours: '9:00-20:00' },
// ШЫМКЕНТ
{ id: 109, type: 'vet', city: 'shymkent', title: 'Ветклиника "Айболит-Юг"', location: 'ул. Калбекова, 15', contact: '+7 7252 55 66 77', emoji: '🏥', lat: 42.3100, lng: 69.5900, workHours: '9:00-20:00' },
{ id: 110, type: 'shelter', city: 'shymkent', title: 'Приют "Мейрим"', location: 'мкр. Самал', contact: '+7 777 444 55 66', emoji: '🏠', lat: 42.2900, lng: 69.6100, workHours: '10:00-18:00' },
// КАРАГАНДА
{ id: 111, type: 'vet', city: 'karaganda', title: 'Ветклиника "Доктор Вет"', location: 'микрорайон Юго-Восток, 56/8', contact: '+7 7212 55 66 77', emoji: '🏥', lat: 49.8147, lng: 73.1194, workHours: '9:00-21:00' },
{ id: 112, type: 'petshop', city: 'karaganda', title: 'Зоомагазин "Пушок"', location: 'ул. Ермекова, 30', contact: '+7 701 555 66 77', emoji: '🛒', lat: 49.7947, lng: 73.0994, workHours: '10:00-21:00' },
// АКТОБЕ
{ id: 113, type: 'vet', city: 'aktobe', title: 'Ветцентр "Актобе Вет"', location: 'ул. Шерниязова, 22', contact: '+7 7132 55 66 77', emoji: '🏥', lat: 50.2939, lng: 57.1770, workHours: '9:00-20:00' },
{ id: 114, type: 'shelter', city: 'aktobe', title: 'Приют "Үміт"', location: 'ул. Айтеке би, 45', contact: '+7 777 666 77 88', emoji: '🏠', lat: 50.2739, lng: 57.1570, workHours: '10:00-17:00' },
// ТАРАЗ
{ id: 115, type: 'vet', city: 'taraz', title: 'Ветклиника "Тараз Вет"', location: 'ул. Байтурсынова, 18', contact: '+7 7262 55 66 77', emoji: '🏥', lat: 42.9100, lng: 71.3767, workHours: '9:00-19:00' },
// ПАВЛОДАР
{ id: 116, type: 'vet', city: 'pavlodar', title: 'Ветклиника "Ертіс"', location: 'ул. Кривенко, 52', contact: '+7 7182 55 66 77', emoji: '🏥', lat: 52.2933, lng: 76.9767, workHours: '9:00-20:00' },
{ id: 117, type: 'petshop', city: 'pavlodar', title: 'Зоомагазин "Хвостик"', location: 'пр. Ломова, 25', contact: '+7 701 111 22 33', emoji: '🛒', lat: 52.2733, lng: 76.9567, workHours: '10:00-21:00' },
// УСТЬ-КАМЕНОГОРСК
{ id: 118, type: 'vet', city: 'uk', title: 'Ветклиника "Алтай Вет"', location: 'пр. Независимости, 62', contact: '+7 7232 55 66 77', emoji: '🏥', lat: 49.9767, lng: 82.6100, workHours: '9:00-20:00' },
{ id: 119, type: 'shelter', city: 'uk', title: 'Приют "Сенім"', location: 'ул. Горького, 35', contact: '+7 777 888 99 00', emoji: '🏠', lat: 49.9567, lng: 82.5900, workHours: '10:00-17:00' },
// СЕМЕЙ
{ id: 120, type: 'vet', city: 'semey', title: 'Ветклиника "Семей Вет"', location: 'ул. Абай, 45', contact: '+7 7222 55 66 77', emoji: '🏥', lat: 50.4211, lng: 80.2375, workHours: '9:00-19:00' },
// КОСТАНАЙ
{ id: 121, type: 'vet', city: 'kostanay', title: 'Ветклиника "Қостанай Вет"', location: 'ул. Ауэзова, 28', contact: '+7 7142 55 66 77', emoji: '🏥', lat: 53.2244, lng: 63.6346, workHours: '9:00-20:00' },
{ id: 122, type: 'petshop', city: 'kostanay', title: 'Зоомагазин "Барбос"', location: 'мкр. Восточный, 15', contact: '+7 701 777 88 99', emoji: '🛒', lat: 53.2044, lng: 63.6146, workHours: '10:00-21:00' },
];
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 contributors'
}).addTo(map);
// Иконки для разных типов маркеров
function getMarkerIcon(item) {
const itemType = item.type || item.cat;
let emoji = item.emoji || '📍';
let className = 'custom-marker';
// Разные смайлики для разных типов
if (itemType === 'lost') {
emoji = '👀'; // Глаза — ищут, потерян
className += ' marker-lost';
} else if (itemType === 'found' || itemType === 'sighting') {
emoji = '👁️'; // Глаз — видел на улице
className += ' marker-found';
} else if (itemType === 'needs_help' || itemType === 'help') {
emoji = '🆘'; // SOS — нужна помощь
className += ' marker-help';
} else if (itemType === 'seeking_home' || itemType === 'home') {
emoji = '🏠'; // Дом — ищет дом
className += ' marker-home';
} else if (itemType === 'vet') {
emoji = '🏥'; // Больница — ветклиника
className += ' marker-vet';
} else if (itemType === 'shelter') {
emoji = '🏡'; // Приют
className += ' marker-shelter';
} else if (itemType === 'petshop') {
emoji = '🛒'; // Магазин
className += ' marker-petshop';
}
return L.divIcon({
className: className,
html: `<div style="font-size:32px;background:white;border-radius:50%;width:44px;height:44px;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,0.2);">${emoji}</div>`,
iconSize: [44, 44],
iconAnchor: [22, 22],
popupAnchor: [0, -22]
});
}
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 lat = item.lat || CITIES[item.city]?.lat;
const lng = item.lng || CITIES[item.city]?.lng;
if (!lat || !lng) return;
const icon = getMarkerIcon(item);
const marker = L.marker([lat, lng], { icon: icon }).addTo(map);
const popupContent = item.workHours
? `<b>${item.emoji || '📍'} ${item.title}</b><br>${item.location}<br>🕒 ${item.workHours}<br>📞 ${item.contact}`
: `<b>${item.emoji || '📍'} ${item.title}</b><br>${item.location}<br>📞 ${item.contact}`;
marker.bindPopup(popupContent);
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');
let currentItem = null;
function showPreview(item) {
currentItem = item;
const cityName = CITIES[item.city]?.name || item.city;
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">📍 ${cityName}</span>
${item.workHours ? `<span class="preview-tag">🕒 ${item.workHours}</span>` : ''}
`;
document.getElementById('preview-desc').textContent = item.description || 'Описание отсутствует';
document.getElementById('preview-contact').innerHTML = `
<strong>📞 Контакт:</strong><br>
<a href="tel:${item.contact}" style="color: var(--primary); text-decoration: none; font-size: 16px;">${item.contact}</a>
`;
// WhatsApp кнопка — сразу переход
document.getElementById('preview-whatsapp').onclick = () => {
const phone = item.contact.replace(/[^0-9]/g, ''); // Удаляем всё кроме цифр
const msg = encodeURIComponent(`Здравствуйте! Пишу по объявлению: ${item.title}\n${item.description}`);
const waUrl = `https://wa.me/${phone}?text=${msg}`;
window.open(waUrl, '_blank');
};
document.getElementById('preview-share').onclick = () => {
const shareData = {
title: item.title,
text: `${item.title} — ${item.description}\n${cityName}\nКонтакт: ${item.contact}`,
url: window.location.href
};
if (navigator.share) {
navigator.share(shareData).catch(() => {});
} else {
navigator.clipboard.writeText(`${item.title} — ${item.description}\n${cityName}\nКонтакт: ${item.contact}`);
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');
const mapcoordsInput = document.getElementById('mapcoords');
let tempMarker = null;
document.getElementById('fab').onclick = () => {
modal.classList.add('open');
// Подсказка: кликните на карту
showToast('🗺️ Кликните на карту, чтобы выбрать место');
};
document.getElementById('modal-close').onclick = () => {
modal.classList.remove('open');
if (tempMarker) {
map.removeLayer(tempMarker);
tempMarker = null;
}
};
// Клик по карте для выбора координат
map.on('click', (e) => {
if (!modal.classList.contains('open')) return;
const { lat, lng } = e.latlng;
mapcoordsInput.value = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
if (tempMarker) map.removeLayer(tempMarker);
tempMarker = L.marker([lat, lng]).addTo(map);
tempMarker.bindPopup('📍 Ваше объявление будет здесь').openPopup();
showToast('📍 Координаты выбраны');
});
document.getElementById('animal-form').onsubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
if (!mapcoordsInput.value) {
showToast('⚠️ Выберите место на карте!');
return;
}
// Добавляем в массив (в реальном проекте — отправка на сервер)
const [lat, lng] = mapcoordsInput.value.split(',').map(Number);
const newAnimal = {
id: Date.now(),
type: data.type,
animal: data.animal,
city: data.city,
title: data.title,
location: data.location || 'Координаты выбраны',
description: data.description,
contact: data.contact,
emoji: { dog: '🐕', cat: '🐈', bird: '🐦', other: '🐾' }[data.animal] || '🐾',
lat, lng
};
ANIMALS.push(newAnimal);
addMarkers(); // Обновить маркеры
showToast('✅ Объявление опубликовано!');
modal.classList.remove('open');
if (tempMarker) {
map.removeLayer(tempMarker);
tempMarker = null;
}
e.target.reset();
mapcoordsInput.value = '';
};
// 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 style="display:flex;justify-content:space-between;margin-top:8px;font-size:11px;color:var(--text-muted);">
<span>📍 ${r.city}</span>
<span>${r.date}</span>
</div>
</div>
</div>
`).join('');
const homeFound = RESCUED.filter(r => r.outcome === 'home_found').length;
const ownerFound = RESCUED.filter(r => r.outcome === 'owner_found').length;
const treated = RESCUED.filter(r => r.outcome === 'treated').length;
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">${homeFound}</div><div class="rb-stat-label">Нашли дом</div></div>
<div class="rb-stat"><div class="rb-stat-num">${ownerFound}</div><div class="rb-stat-label">К хозяину</div></div>
<div class="rb-stat"><div class="rb-stat-num">${treated}</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.city}, ${m.date}</div>
<div class="memorial-card-desc" style="font-size:12px;opacity:0.8;margin-top:8px;line-height:1.4;">${m.desc}</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>