1210 lines
65 KiB
HTML
1210 lines
65 KiB
HTML
<!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, 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);
|
||
|
||
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 marker = L.marker([lat, lng]).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>
|