748 lines
30 KiB
HTML
748 lines
30 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="#0066CC">
|
||
<meta name="description" content="VibeMap — карта студентов и проектов Vibe42. Найди единомышленников, поделись проектом.">
|
||
<title>VibeMap — карта студентов Vibe42</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="VibeMap">
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||
<style>
|
||
:root {
|
||
--kt-blue: #0066CC;
|
||
--kt-red: #E4002B;
|
||
--kt-dark: #1a202c;
|
||
--kt-light: #f5f7fa;
|
||
--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(--kt-blue) 0%, #004c99 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; }
|
||
.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); }
|
||
.stats-btn {
|
||
background: rgba(255,255,255,0.15); border: none; color: white;
|
||
padding: 8px 14px; border-radius: 10px; cursor: pointer;
|
||
display: flex; align-items: center; gap: 8px; font-size: 13px;
|
||
transition: all 0.3s;
|
||
}
|
||
.stats-btn:hover { background: rgba(255,255,255,0.25); transform: translateY(-1px); }
|
||
.stats-count {
|
||
background: var(--kt-red); color: white;
|
||
padding: 2px 8px; border-radius: 12px; font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
.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(--kt-dark); color: white; }
|
||
|
||
/* FILTER BAR */
|
||
.filter-bar {
|
||
position: fixed; top: 70px; left: 0; right: 0;
|
||
background: white; padding: 12px 16px; z-index: 999;
|
||
display: flex; gap: 8px; overflow-x: auto;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
}
|
||
.chip {
|
||
background: #f0f0f0; border: none; padding: 8px 14px;
|
||
border-radius: 20px; font-size: 13px; cursor: pointer;
|
||
white-space: nowrap; transition: all 0.3s;
|
||
display: flex; align-items: center; gap: 6px;
|
||
}
|
||
.chip:hover { background: #e0e0e0; }
|
||
.chip.active { background: var(--kt-blue); color: white; }
|
||
.chip .dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: var(--kt-blue);
|
||
}
|
||
.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: 64px; height: 64px; border-radius: 50%;
|
||
background: linear-gradient(135deg, var(--kt-red) 0%, #c40025 100%);
|
||
color: white; border: none; font-size: 32px;
|
||
cursor: pointer; box-shadow: 0 4px 20px rgba(228,0,43,0.4);
|
||
z-index: 1000; transition: all 0.3s;
|
||
}
|
||
.fab:hover { transform: scale(1.1); box-shadow: 0 6px 30px rgba(228,0,43,0.5); }
|
||
|
||
/* AI FAB */
|
||
.ai-fab {
|
||
position: fixed; bottom: 110px; right: 30px;
|
||
width: 52px; height: 52px; border-radius: 50%;
|
||
background: linear-gradient(135deg, var(--kt-blue) 0%, #004c99 100%);
|
||
color: white; border: none; font-size: 24px;
|
||
cursor: pointer; box-shadow: 0 4px 15px rgba(0,102,204,0.3);
|
||
z-index: 1000; transition: all 0.3s;
|
||
}
|
||
.ai-fab:hover { transform: scale(1.05); }
|
||
.ai-fab-pulse {
|
||
position: absolute; top: 50%; left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 100%; height: 100%; border-radius: 50%;
|
||
background: inherit; animation: pulse 2s infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.7; }
|
||
100% { transform: translate(-50%, -50%) scale(1.6); opacity: 0; }
|
||
}
|
||
|
||
/* AI PANEL */
|
||
.ai-panel {
|
||
position: fixed; bottom: 180px; right: 30px;
|
||
width: 340px; max-height: 450px;
|
||
background: white; border-radius: 16px;
|
||
box-shadow: 0 8px 40px 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(--kt-blue), #004c99);
|
||
color: white; padding: 14px 16px;
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
}
|
||
.ai-head-info { display: flex; align-items: center; gap: 10px; }
|
||
.ai-head-avatar { font-size: 24px; }
|
||
.ai-head-name { font-weight: 700; font-size: 14px; }
|
||
.ai-head-status { font-size: 11px; opacity: 0.9; display: flex; align-items: center; gap: 4px; }
|
||
.ai-status-dot { width: 6px; height: 6px; background: #2a9d8f; border-radius: 50%; }
|
||
.ai-panel-close {
|
||
background: rgba(255,255,255,0.2); border: none;
|
||
color: white; width: 28px; height: 28px; border-radius: 50%;
|
||
cursor: pointer; font-size: 18px;
|
||
}
|
||
.ai-messages {
|
||
flex: 1; overflow-y: auto; padding: 16px;
|
||
display: flex; flex-direction: column; gap: 12px;
|
||
background: #f8f9fa;
|
||
}
|
||
.ai-msg {
|
||
max-width: 85%; padding: 12px 16px; border-radius: 14px;
|
||
font-size: 14px; 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(--kt-blue); color: white;
|
||
align-self: flex-end; border-bottom-right-radius: 4px;
|
||
}
|
||
.ai-suggestions {
|
||
display: flex; gap: 8px; padding: 12px 16px;
|
||
overflow-x: auto; background: white;
|
||
border-top: 1px solid #e2e8f0;
|
||
}
|
||
.ai-sug {
|
||
background: #f0f4f8; color: var(--kt-blue);
|
||
padding: 8px 14px; border-radius: 20px;
|
||
font-size: 12px; white-space: nowrap; cursor: pointer;
|
||
border: 1px solid transparent; transition: all 0.3s;
|
||
}
|
||
.ai-sug:hover { border-color: var(--kt-blue); background: white; }
|
||
.ai-input-row {
|
||
display: flex; gap: 8px; padding: 12px 16px;
|
||
background: white; border-top: 1px solid #e2e8f0;
|
||
}
|
||
.ai-input-row input {
|
||
flex: 1; padding: 10px 14px; border: 1px solid #e2e8f0;
|
||
border-radius: 24px; font-size: 14px; outline: none;
|
||
}
|
||
.ai-input-row input:focus { border-color: var(--kt-blue); }
|
||
.ai-input-row button {
|
||
background: var(--kt-blue); color: white; border: none;
|
||
width: 40px; height: 40px; border-radius: 50%;
|
||
cursor: pointer; font-size: 16px;
|
||
transition: background 0.3s;
|
||
}
|
||
.ai-input-row button:hover { background: #004c99; }
|
||
|
||
/* 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: 300px; 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(--kt-blue), #004c99);
|
||
color: white; padding: 20px 16px;
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
}
|
||
.drawer-logo { font-size: 20px; font-weight: 800; }
|
||
.drawer-close {
|
||
background: rgba(255,255,255,0.2); border: none;
|
||
color: white; width: 32px; height: 32px; border-radius: 50%;
|
||
cursor: pointer; font-size: 20px;
|
||
}
|
||
.drawer-nav { flex: 1; padding: 16px 0; }
|
||
.drawer-nav button {
|
||
width: 100%; padding: 14px 20px; text-align: left;
|
||
background: none; border: none; font-size: 15px;
|
||
color: var(--text); cursor: pointer;
|
||
display: flex; align-items: center; gap: 12px;
|
||
transition: background 0.2s;
|
||
}
|
||
.drawer-nav button:hover { background: #f0f4f8; }
|
||
.drawer-footer {
|
||
padding: 20px; border-top: 1px solid #e2e8f0;
|
||
font-size: 12px; 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: 70vh; 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 20px 20px; }
|
||
.preview-title { font-size: 20px; font-weight: 700; margin-bottom: 12px; }
|
||
.preview-meta { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
|
||
.preview-tag {
|
||
background: #f0f4f8; color: var(--kt-blue);
|
||
padding: 4px 10px; border-radius: 12px; font-size: 12px;
|
||
}
|
||
.preview-desc { color: var(--text-muted); line-height: 1.6; margin-bottom: 16px; }
|
||
.preview-contact {
|
||
background: #f0f4f8; padding: 12px; border-radius: 10px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.preview-actions { display: flex; gap: 12px; }
|
||
.preview-btn {
|
||
flex: 1; padding: 12px; border: none; border-radius: 10px;
|
||
font-weight: 600; cursor: pointer; transition: all 0.3s;
|
||
}
|
||
.preview-btn.primary { background: var(--kt-blue); color: white; }
|
||
.preview-btn.primary:hover { background: #004c99; }
|
||
.preview-btn.secondary { background: #f0f0f0; color: var(--text); }
|
||
.preview-btn.secondary:hover { background: #e0e0e0; }
|
||
|
||
/* 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: 16px;
|
||
max-width: 500px; width: 100%; max-height: 90vh;
|
||
overflow-y: auto; padding: 24px;
|
||
}
|
||
.modal-title { font-size: 22px; font-weight: 700; margin-bottom: 20px; }
|
||
.form-group { margin-bottom: 16px; }
|
||
.form-group label {
|
||
display: block; margin-bottom: 6px; font-weight: 600;
|
||
font-size: 14px; color: var(--text-muted);
|
||
}
|
||
.form-group input, .form-group textarea, .form-group select {
|
||
width: 100%; padding: 12px; border: 1px solid #e2e8f0;
|
||
border-radius: 10px; font-size: 14px; outline: none;
|
||
}
|
||
.form-group input:focus, .form-group textarea:focus { border-color: var(--kt-blue); }
|
||
.form-actions { display: flex; gap: 12px; margin-top: 20px; }
|
||
|
||
/* TOAST */
|
||
.toast {
|
||
position: fixed; bottom: 30px; left: 50%;
|
||
transform: translateX(-50%) translateY(100px);
|
||
background: var(--kt-dark); color: white;
|
||
padding: 14px 24px; border-radius: 10px;
|
||
z-index: 5000; transition: transform 0.3s;
|
||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||
}
|
||
.toast.show { transform: translateX(-50%) translateY(0); }
|
||
|
||
/* MASCOT */
|
||
.mascot {
|
||
position: fixed; bottom: 20px; left: 20px;
|
||
width: 100px; height: 100px; z-index: 999;
|
||
cursor: pointer; transition: transform 0.3s;
|
||
}
|
||
.mascot:hover { transform: scale(1.05); }
|
||
.mascot-bubble {
|
||
position: absolute; bottom: 100px; left: 50%;
|
||
transform: translateX(-50%);
|
||
background: white; padding: 12px 16px;
|
||
border-radius: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.15);
|
||
font-size: 13px; white-space: nowrap;
|
||
opacity: 0; transition: opacity 0.3s;
|
||
pointer-events: none;
|
||
}
|
||
.mascot:hover .mascot-bubble { opacity: 1; }
|
||
|
||
/* PWA INSTALL */
|
||
.pwa-install {
|
||
position: fixed; bottom: 20px; left: 50%;
|
||
transform: translateX(-50%);
|
||
background: linear-gradient(135deg, var(--kt-blue), #004c99);
|
||
color: white; padding: 12px 20px; border-radius: 30px;
|
||
z-index: 998; display: flex; align-items: center; gap: 10px;
|
||
border: none; cursor: pointer; box-shadow: 0 4px 15px rgba(0,102,204,0.3);
|
||
}
|
||
.pwa-close {
|
||
background: rgba(255,255,255,0.2); border: none;
|
||
color: white; width: 24px; height: 24px; border-radius: 50%;
|
||
cursor: pointer; font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.topbar { padding: 10px 12px; }
|
||
.logo-text .tagline { display: none; }
|
||
.ai-panel { width: calc(100% - 30px); right: 15px; bottom: 180px; }
|
||
.fab { bottom: 20px; right: 20px; width: 56px; height: 56px; }
|
||
.ai-fab { bottom: 90px; right: 20px; }
|
||
.mascot { width: 80px; height: 80px; bottom: 15px; left: 15px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- TOP BAR -->
|
||
<header class="topbar">
|
||
<div class="logo">
|
||
<span class="logo-emoji">🚀</span>
|
||
<div class="logo-text">
|
||
<div>VibeMap</div>
|
||
<div class="tagline">Карта студентов Vibe42</div>
|
||
</div>
|
||
</div>
|
||
<button class="stats-btn" id="open-stats">
|
||
<span>📊</span>
|
||
<span>Проектов</span>
|
||
<span class="stats-count" id="project-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="website">🌐 Сайты</button>
|
||
<button class="chip" data-cat="bot">🤖 Боты</button>
|
||
<button class="chip" data-cat="app">📱 Приложения</button>
|
||
<button class="chip" data-cat="presentation">📊 Презентации</button>
|
||
<button class="chip" data-cat="course">📚 Курсы</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="ИИ-помощник">
|
||
<span>💬</span>
|
||
<span class="ai-fab-pulse"></span>
|
||
</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">Vibe42 Помощник</div>
|
||
<div class="ai-head-status"><span class="ai-status-dot"></span> Онлайн</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">
|
||
👋 Привет! Я ИИ-помощник Vibe42. Спроси меня о чём угодно:
|
||
<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">🚀 VibeMap</div>
|
||
<button class="drawer-close" id="drawer-close">×</button>
|
||
</div>
|
||
<nav class="drawer-nav">
|
||
<button data-nav="map">🗺️ Карта</button>
|
||
<button data-nav="projects">📁 Все проекты</button>
|
||
<button data-nav="stats">📊 Статистика</button>
|
||
<button data-nav="about">ℹ️ О платформе</button>
|
||
<button data-nav="install">📲 Установить</button>
|
||
<button data-nav="feedback">💬 Обратная связь</button>
|
||
</nav>
|
||
<div class="drawer-footer">
|
||
<div>VibeMap © 2025</div>
|
||
<div>Сделано с ❤️ для студентов Казахстана</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<!-- PREVIEW BOTTOM SHEET -->
|
||
<div class="preview" id="preview">
|
||
<div class="preview-handle"></div>
|
||
<div class="preview-content">
|
||
<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 primary" id="preview-open">Открыть проект</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="project-form">
|
||
<div class="form-group">
|
||
<label>Название проекта</label>
|
||
<input type="text" name="title" required placeholder="Мой первый сайт">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Категория</label>
|
||
<select name="category">
|
||
<option value="website">🌐 Сайт</option>
|
||
<option value="bot">🤖 Telegram-бот</option>
|
||
<option value="app">📱 Приложение</option>
|
||
<option value="presentation">📊 Презентация</option>
|
||
<option value="course">📚 Курс</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Город</label>
|
||
<select name="city">
|
||
<option value="astana">Астана</option>
|
||
<option value="almaty">Алматы</option>
|
||
<option value="shymkent">Шымкент</option>
|
||
<option value="karaganda">Караганда</option>
|
||
<option value="aktobe">Актобе</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Ссылка на проект</label>
|
||
<input type="url" name="url" placeholder="https://pages.git.vibe42.kz/...">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Описание</label>
|
||
<textarea name="description" rows="4" placeholder="Расскажите о проекте..."></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Контакт (Telegram/Email)</label>
|
||
<input type="text" name="contact" placeholder="@username или email">
|
||
</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>
|
||
|
||
<!-- 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="#0080ff"/>
|
||
<stop offset="100%" stop-color="#0066CC"/>
|
||
</radialGradient>
|
||
</defs>
|
||
<circle cx="50" cy="50" r="45" fill="url(#mg)"/>
|
||
<circle cx="35" cy="42" r="8" fill="white"/>
|
||
<circle cx="65" cy="42" r="8" fill="white"/>
|
||
<circle cx="35" cy="42" r="4" fill="#1a202c"/>
|
||
<circle cx="65" cy="42" r="4" fill="#1a202c"/>
|
||
<circle cx="37" cy="40" r="2" fill="white"/>
|
||
<circle cx="67" cy="40" r="2" fill="white"/>
|
||
<ellipse cx="50" cy="60" rx="12" ry="8" fill="#fff5e2"/>
|
||
<path d="M45 58 Q50 63 55 58" stroke="#1a202c" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||
<circle cx="25" cy="55" r="5" fill="#ff6b6b" opacity="0.4"/>
|
||
<circle cx="75" cy="55" r="5" fill="#ff6b6b" opacity="0.4"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- PWA INSTALL -->
|
||
<button class="pwa-install" id="pwa-install" hidden>
|
||
<span class="pwa-icon">📲</span>
|
||
<span class="pwa-text">Установить как приложение</span>
|
||
<span class="pwa-close" id="pwa-install-close">×</span>
|
||
</button>
|
||
|
||
<!-- Leaflet -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script>
|
||
// ДАННЫЕ ПРОЕКТОВ (мок-данные для примера)
|
||
const PROJECTS = [
|
||
{ id: 1, title: "Сайт-визитка", category: "website", city: "astana", url: "https://pages.git.vibe42.kz/Medet/site1", description: "Мой первый сайт на Vibe42", contact: "@medet" },
|
||
{ id: 2, title: "Telegram-бот", category: "bot", city: "almaty", url: "https://t.me/mybot", description: "Бот для записи на курсы", contact: "@user123" },
|
||
{ id: 3, title: "Презентация", category: "presentation", city: "astana", url: "#", description: "Презентация по вайбкодингу", contact: "student@mail.kz" },
|
||
{ id: 4, title: "Интернет-магазин", category: "app", city: "shymkent", url: "#", description: "Прототип магазина одежды", contact: "@shop" },
|
||
{ id: 5, title: "Курс по HTML", category: "course", city: "karaganda", url: "#", description: "Бесплатный курс для начинающих", contact: "@teacher" },
|
||
];
|
||
|
||
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([49.8, 71.5], 5);
|
||
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;
|
||
|
||
PROJECTS.forEach(p => {
|
||
if (filter !== 'all' && p.category !== filter) return;
|
||
const city = CITIES[p.city];
|
||
if (!city) return;
|
||
|
||
const marker = L.marker([city.lat, city.lng]).addTo(map);
|
||
marker.bindPopup(`<b>${p.title}</b><br>${p.description}`);
|
||
marker.on('click', () => showPreview(p));
|
||
markers.push(marker);
|
||
});
|
||
|
||
document.getElementById('project-count').textContent = markers.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 || 'all';
|
||
addMarkers(filter);
|
||
});
|
||
});
|
||
|
||
// Preview
|
||
const preview = document.getElementById('preview');
|
||
function showPreview(project) {
|
||
document.getElementById('preview-title').textContent = project.title;
|
||
document.getElementById('preview-meta').innerHTML = `
|
||
<span class="preview-tag">${project.category}</span>
|
||
<span class="preview-tag">${CITIES[project.city]?.name}</span>
|
||
`;
|
||
document.getElementById('preview-desc').textContent = project.description;
|
||
document.getElementById('preview-contact').textContent = `Контакт: ${project.contact}`;
|
||
document.getElementById('preview-open').onclick = () => window.open(project.url, '_blank');
|
||
preview.classList.add('open');
|
||
}
|
||
document.getElementById('preview-close').onclick = () => preview.classList.remove('open');
|
||
|
||
// Modal
|
||
const modal = document.getElementById('modal');
|
||
document.getElementById('fab').onclick = () => modal.classList.add('open');
|
||
document.getElementById('modal-close').onclick = () => modal.classList.remove('open');
|
||
document.getElementById('project-form').onsubmit = (e) => {
|
||
e.preventDefault();
|
||
showToast('✅ Проект опубликован!');
|
||
modal.classList.remove('open');
|
||
};
|
||
|
||
// Drawer
|
||
const drawer = document.getElementById('drawer');
|
||
document.getElementById('open-menu').onclick = () => drawer.hidden = false;
|
||
document.getElementById('drawer-close').onclick = () => drawer.hidden = true;
|
||
document.getElementById('drawer-backdrop').onclick = () => drawer.hidden = true;
|
||
|
||
// AI Panel
|
||
const aiPanel = document.getElementById('ai-panel');
|
||
document.getElementById('ai-fab').onclick = () => aiPanel.hidden = false;
|
||
document.getElementById('ai-panel-close').onclick = () => aiPanel.hidden = true;
|
||
|
||
const aiMessages = document.getElementById('ai-messages');
|
||
const aiForm = document.getElementById('ai-form');
|
||
const aiInput = document.getElementById('ai-input');
|
||
|
||
aiForm.onsubmit = (e) => {
|
||
e.preventDefault();
|
||
const msg = aiInput.value.trim();
|
||
if (!msg) return;
|
||
|
||
aiMessages.innerHTML += `<div class="ai-msg user">${msg}</div>`;
|
||
aiInput.value = '';
|
||
|
||
setTimeout(() => {
|
||
const responses = {
|
||
'Как создать сайт?': '📝 Открой Vibe42, создай index.html в папке проекта. Используй design.md как основу!',
|
||
'Где найти проекты?': '🗺️ Смотри на карту! Фильтры сверху помогут найти нужную категорию.',
|
||
'Как опубликовать?': '🚀 Нажми кнопку "Опубликовать" в интерфейсе Vibe42. Ссылка будет: pages.git.vibe42.kz/...'
|
||
};
|
||
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 = ['Нажми + чтобы добавить проект!', 'Исследуй карту!', 'Vibe42 — это круто! 🚀', 'Создавай, публикуй, вдохновляй!'];
|
||
mascotBubble.textContent = messages[clickCount % messages.length];
|
||
};
|
||
|
||
// Toast
|
||
function showToast(msg) {
|
||
const toast = document.getElementById('toast');
|
||
toast.textContent = msg;
|
||
toast.classList.add('show');
|
||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||
}
|
||
|
||
// PWA Install
|
||
const installBtn = document.getElementById('pwa-install');
|
||
let deferredPrompt = null;
|
||
|
||
window.addEventListener('beforeinstallprompt', (e) => {
|
||
e.preventDefault();
|
||
deferredPrompt = e;
|
||
installBtn.hidden = false;
|
||
});
|
||
|
||
installBtn.onclick = async () => {
|
||
if (deferredPrompt) {
|
||
deferredPrompt.prompt();
|
||
await deferredPrompt.userChoice;
|
||
deferredPrompt = null;
|
||
installBtn.hidden = true;
|
||
}
|
||
};
|
||
|
||
document.getElementById('pwa-install-close').onclick = () => {
|
||
installBtn.hidden = true;
|
||
};
|
||
|
||
// City selector
|
||
document.getElementById('city').onchange = (e) => {
|
||
const city = CITIES[e.target.value];
|
||
if (city) map.flyTo([city.lat, city.lng], 11);
|
||
};
|
||
|
||
// Stats button
|
||
document.getElementById('open-stats').onclick = () => {
|
||
showToast(`📊 Всего проектов: ${PROJECTS.length}`);
|
||
};
|
||
</script>
|
||
</body>
|
||
</html>
|