Гость
🚧
В разработке
Этот раздел скоро появится.

Sekai Helper

Загрузка
⏱ Минута: 0
015304560
Варды
🏆 Вард дня
Загрузка...
Показано: 0 из 0
Режим: Просмотр
Вардов0
⏱ Ползунок времени
👁 Радиусы обзора
Фильтр
Тема
const SERVER_URL = ''; const BOOKMARK_SVG = ``; const QUELLING_SVG = ` `; const ROSHAN_ICON = ` `; const RUNE_ICON = ` `; const tg = window.Telegram ? window.Telegram.WebApp : null; let initData = ''; let telegramUserId = null; if (tg) { if (tg.initData) initData = tg.initData; if (typeof tg.disableVerticalSwipes === 'function') tg.disableVerticalSwipes(); if (typeof tg.expand === 'function') tg.expand(); if (typeof tg.setBackgroundColor === 'function') tg.setBackgroundColor('#1a1a2e'); } if (tg.initDataUnsafe && tg.initDataUnsafe.user) { telegramUserId = tg.initDataUnsafe.user.id; const user = tg.initDataUnsafe.user; // ТЕСТ: Выводим длину initData и ID пользователя const nameEl = document.getElementById('home-user-name'); if (nameEl) { nameEl.textContent = 'ID: ' + user.id + ' | initData: ' + (initData ? initData.length + ' симв.' : 'ПУСТО'); } // Имя const userNameEl = document.getElementById('home-user-name'); if (userNameEl) { userNameEl.textContent = user.first_name || user.username || 'Гость'; } // Аватарка (ФИНАЛ) const userAvatarEl = document.getElementById('home-user-avatar'); if (userAvatarEl) { const initial = (user.first_name || user.username || 'U')[0].toUpperCase(); userAvatarEl.textContent = initial; // 1. Возвращаем имя const nameEl = document.getElementById('home-user-name'); if (nameEl) nameEl.textContent = user.first_name || user.username || 'Гость'; // 2. Грузим аватарку через fetch (передаём initData в заголовке и в URL) fetch(SERVER_URL + '/user/avatar', { method: 'GET', headers: { 'X-Init-Data': initData } }) .then(r => r.json()) .then(data => { if (data && data.url) { userAvatarEl.innerHTML = ''; const img = document.createElement('img'); img.src = data.url; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; img.style.borderRadius = '50%'; userAvatarEl.appendChild(img); } }) .catch(err => console.warn('Avatar error:', err)); } } else { const userNameEl = document.getElementById('home-user-name'); if (userNameEl) userNameEl.textContent = 'Гость'; } const systemThemeQuery = window.matchMedia('(prefers-color-scheme: light)'); function getResolvedTheme(setting) { if (setting === 'system') return systemThemeQuery.matches ? 'light' : 'dark'; return setting; } function applyTheme(setting) { const resolved = getResolvedTheme(setting); document.body.setAttribute('data-theme', resolved); try { localStorage.setItem('ward_theme', setting); } catch (e) {} document.querySelectorAll('.theme-btn').forEach(b => { b.classList.toggle('active', b.dataset.theme === setting); }); if (tg && typeof tg.setHeaderColor === 'function') { tg.setHeaderColor(resolved === 'dark' ? '#1a1a2e' : '#ffffff'); } } function loadThemeSetting() { try { const saved = localStorage.getItem('ward_theme'); if (saved === 'light' || saved === 'dark' || saved === 'system') return saved; } catch (e) {} return 'system'; } applyTheme(loadThemeSetting()); document.querySelectorAll('.theme-btn').forEach(btn => { btn.addEventListener('click', function() { applyTheme(this.dataset.theme); }); }); systemThemeQuery.addEventListener('change', function() { try { const saved = localStorage.getItem('ward_theme'); if (saved === 'system') applyTheme('system'); } catch (e) {} }); const MAP_WIDTH = 2048; const MAP_HEIGHT = 2048; const bounds = [[0, 0], [MAP_HEIGHT, MAP_WIDTH]]; function isMobile() { return window.innerWidth < 768; } function getMinZoom() { return isMobile() ? -3 : -5; } function getStartZoom() { return isMobile() ? -2 : -1; } const map = L.map('map', { crs: L.CRS.Simple, minZoom: getMinZoom(), maxZoom: 2, zoomControl: false, attributionControl: false, maxBounds: bounds, maxBoundsViscosity: 1.0, tap: false, inertia: false, fadeAnimation: false, markerZoomAnimation: false }); const mapImage = 'map.jpg'; L.imageOverlay(mapImage, bounds).addTo(map); map.fitBounds(bounds); map.setZoom(getStartZoom()); window.map = map; setTimeout(() => { const overlay = document.getElementById('loading-overlay'); if (overlay) overlay.classList.add('hidden'); }, 600); window.addEventListener('resize', function() { map.setMinZoom(getMinZoom()); }); const NO_IMAGE = 'data:image/svg+xml;utf8,Скриншот не добавлен'; const WARD_ICONS = { observer: ``, sentry: ``, roshan: ROSHAN_ICON, rune: RUNE_ICON }; const VISUAL_ONLY_TYPES = ['roshan', 'rune']; let editMode = false, isAdmin = false, wards = [], wardMarkers = {}, currentFilter = 'all', searchQuery = ''; let currentSort = 'date'; let currentGroup = ''; let currentWardId = null; let isClosing = false; let listScrollPosition = 0; let viewedWards = new Set(); try { const saved = localStorage.getItem('viewed_wards'); if (saved) viewedWards = new Set(JSON.parse(saved)); } catch (e) {} // ===== ПОЛЗУНОК ВРЕМЕНИ ===== let timeSliderEnabled = false; let currentMinute = 0; try { const saved = localStorage.getItem('time_slider_enabled'); if (saved === 'true') timeSliderEnabled = true; } catch (e) {} const timeSliderContainer = document.getElementById('time-slider-container'); const timeSliderToggle = document.getElementById('time-slider-toggle'); const timeSliderInput = document.getElementById('time-slider-input'); const timeSliderValue = document.getElementById('time-slider-value'); const timeSliderMarks = document.getElementById('time-slider-marks'); const timeSliderClose = document.getElementById('time-slider-close'); timeSliderToggle.checked = timeSliderEnabled; if (timeSliderEnabled) { setTimeout(() => { timeSliderContainer.classList.add('open'); }, 400); } function toggleTimeSlider(enabled) { timeSliderEnabled = enabled; try { localStorage.setItem('time_slider_enabled', enabled); } catch (e) {} if (enabled) { timeSliderContainer.classList.add('open'); renderWards(); } else { timeSliderContainer.classList.remove('open'); renderWards(); } } timeSliderToggle.addEventListener('change', function() { toggleTimeSlider(this.checked); }); timeSliderClose.addEventListener('click', function() { timeSliderToggle.checked = false; toggleTimeSlider(false); }); timeSliderInput.addEventListener('input', function() { currentMinute = parseInt(this.value); timeSliderValue.textContent = currentMinute; updateSliderMarks(); renderWards(); }); function updateSliderMarks() { timeSliderMarks.innerHTML = ''; const wardMinutes = new Set(); wards.forEach(w => { if (w.minute !== null && w.minute !== undefined && w.minute !== '' && !isVisualOnly(w)) { wardMinutes.add(parseInt(w.minute)); } }); const grouped = []; const sorted = Array.from(wardMinutes).sort((a,b) => a-b); sorted.forEach(m => { const last = grouped[grouped.length - 1]; if (last === undefined || m - last > 2) { grouped.push(m); } }); grouped.forEach(m => { const percent = (m / 60) * 100; const mark = document.createElement('div'); mark.className = 'time-mark'; mark.style.left = percent + '%'; if (m === currentMinute) { mark.classList.add('active'); } timeSliderMarks.appendChild(mark); }); } // ===== РАДИУСЫ ОБЗОРА ===== let radiusEnabled = false; let radiusCircles = []; try { const saved = localStorage.getItem('radius_enabled'); if (saved === 'true') radiusEnabled = true; } catch (e) {} const radiusToggle = document.getElementById('radius-toggle'); radiusToggle.checked = radiusEnabled; const DOTA_MAP_SIZE = 16000; const PIXEL_RATIO = MAP_WIDTH / DOTA_MAP_SIZE; const RADIUS_SCALE = 0.85; const OBSERVER_RADIUS = 1600 * PIXEL_RATIO * RADIUS_SCALE; const SENTRY_RADIUS = 1000 * PIXEL_RATIO * RADIUS_SCALE; function clearRadiusCircles() { radiusCircles.forEach(c => map.removeLayer(c)); radiusCircles = []; } function drawRadiusCircles() { clearRadiusCircles(); if (!radiusEnabled) return; const visibleWards = getFilteredWards(); visibleWards.forEach(ward => { const type = getWardType(ward); if (isVisualOnly(ward)) return; let radius, color; if (type === 'observer') { radius = OBSERVER_RADIUS; color = '#f39c12'; } else if (type === 'sentry') { radius = SENTRY_RADIUS; color = '#4a9eff'; } else { return; } const circle = L.circle([ward.y, ward.x], { radius: radius, color: color, fillColor: color, fillOpacity: 0.08, weight: 1.5, opacity: 0.5, interactive: false }).addTo(map); radiusCircles.push(circle); }); } radiusToggle.addEventListener('change', function() { radiusEnabled = this.checked; try { localStorage.setItem('radius_enabled', radiusEnabled); } catch (e) {} drawRadiusCircles(); }); // ===== LIGHTBOX ===== const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightbox-img'); const lightboxClose = document.getElementById('lightbox-close'); let lightboxClosing = false; function openLightbox(src) { lightboxClosing = false; lightboxImg.src = src; lightbox.classList.remove('closing'); lightbox.classList.add('open'); } function closeLightbox() { if (lightboxClosing) return; lightboxClosing = true; lightbox.classList.add('closing'); setTimeout(() => { lightbox.classList.remove('open', 'closing'); lightboxImg.src = ''; lightboxClosing = false; }, 250); } lightboxClose.addEventListener('click', function(e) { e.stopPropagation(); closeLightbox(); }); lightbox.addEventListener('click', function(e) { if (e.target === lightbox || e.target === lightboxImg) closeLightbox(); }); // ===== МОДАЛКА ===== const modalOverlay = document.getElementById('modal-overlay'); const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); const modalClose = document.getElementById('modal-close'); function openModal() { isClosing = false; modalOverlay.classList.remove('closing'); modalOverlay.classList.add('open'); } function closeModal() { if (isClosing) return; isClosing = true; modalOverlay.classList.add('closing'); setTimeout(() => { modalOverlay.classList.remove('open', 'closing'); isClosing = false; currentWardId = null; }, 250); } modalClose.addEventListener('click', closeModal); modalOverlay.addEventListener('click', function(e) { if (e.target === modalOverlay) closeModal(); }); // ===== UI ===== const listBtn = document.getElementById('list-btn'); const listPanel = document.getElementById('list-panel'); const listClose = document.getElementById('list-close'); const listItems = document.getElementById('list-items'); const listSearch = document.getElementById('list-search'); const listCounter = document.getElementById('list-counter'); const listBadge = document.getElementById('list-badge'); listBtn.addEventListener('click', function(e) { e.stopPropagation(); listPanel.classList.toggle('open'); listBtn.classList.toggle('open'); renderList(); if (listPanel.classList.contains('open')) { setTimeout(() => { listItems.scrollTop = listScrollPosition; }, 50); } }); listClose.addEventListener('click', function(e) { e.stopPropagation(); listScrollPosition = listItems.scrollTop; listPanel.classList.remove('open'); listBtn.classList.remove('open'); }); listSearch.addEventListener('input', function() { searchQuery = this.value.trim().toLowerCase(); renderList(); }); document.querySelectorAll('.sort-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); currentSort = this.dataset.sort; renderList(); }); }); const settingsBtn = document.getElementById('settings-btn'); const settingsPanel = document.getElementById('settings-panel'); settingsBtn.addEventListener('click', function(e) { e.stopPropagation(); settingsPanel.classList.toggle('open'); settingsBtn.classList.toggle('open'); }); document.addEventListener('click', function(e) { if (listPanel.classList.contains('open')) { if (!listPanel.contains(e.target) && !listBtn.contains(e.target)) { listScrollPosition = listItems.scrollTop; listPanel.classList.remove('open'); listBtn.classList.remove('open'); } } if (settingsPanel.classList.contains('open')) { if (!settingsPanel.contains(e.target) && !settingsBtn.contains(e.target)) { settingsPanel.classList.remove('open'); settingsBtn.classList.remove('open'); } } }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { if (lightbox.classList.contains('open')) closeLightbox(); else if (modalOverlay.classList.contains('open')) closeModal(); else { listPanel.classList.remove('open'); listBtn.classList.remove('open'); settingsPanel.classList.remove('open'); settingsBtn.classList.remove('open'); } } }); document.querySelectorAll('.filter-btn[data-filter]').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.filter-btn[data-filter]').forEach(b => b.classList.remove('active')); this.classList.add('active'); currentFilter = this.dataset.filter; renderWards(); renderList(); }); }); const groupFilterSelect = document.getElementById('group-filter'); if (groupFilterSelect) { groupFilterSelect.addEventListener('change', function() { currentGroup = this.value; renderWards(); renderList(); }); } function authHeaders() { const h = { 'Content-Type': 'application/json' }; if (initData) h['X-Init-Data'] = initData; const urlParams = new URLSearchParams(window.location.search); const adminParam = urlParams.get('admin'); if (adminParam) h['X-Admin-Secret'] = adminParam; return h; } async function apiGetWards() { const r = await fetch(SERVER_URL + '/wards?_=' + Date.now(), { cache: 'no-store', headers: authHeaders() }); if (!r.ok) throw new Error('Ошибка загрузки: ' + r.status); return await r.json(); } async function apiGetMe() { const r = await fetch(SERVER_URL + '/me', { headers: authHeaders() }); if (!r.ok) throw new Error('Ошибка /me: ' + r.status); return await r.json(); } async function apiCreateWard(ward) { const r = await fetch(SERVER_URL + '/wards', { method: 'POST', headers: authHeaders(), body: JSON.stringify(ward) }); if (!r.ok) throw new Error('Ошибка создания: ' + r.status); return await r.json(); } async function apiUpdateWard(id, ward) { const r = await fetch(SERVER_URL + '/wards/' + id, { method: 'PUT', headers: authHeaders(), body: JSON.stringify(ward) }); if (!r.ok) throw new Error('Ошибка обновления: ' + r.status); return await r.json(); } async function apiDeleteWard(id) { const r = await fetch(SERVER_URL + '/wards/' + id, { method: 'DELETE', headers: authHeaders() }); if (!r.ok) throw new Error('Ошибка удаления: ' + r.status); return await r.json(); } async function apiLikeWard(id) { const r = await fetch(SERVER_URL + '/wards/' + id + '/like', { method: 'POST', headers: authHeaders() }); if (!r.ok) throw new Error('Ошибка лайка: ' + r.status); return await r.json(); } async function apiViewWard(id) { try { await fetch(SERVER_URL + '/wards/' + id + '/view', { method: 'POST', headers: authHeaders() }); } catch (e) {} } async function apiGetComments(wardId) { const r = await fetch(SERVER_URL + '/wards/' + wardId + '/comments?_=' + Date.now(), { cache: 'no-store', headers: authHeaders() }); if (!r.ok) throw new Error('Ошибка загрузки комментариев: ' + r.status); return await r.json(); } async function apiCreateComment(wardId, text) { const r = await fetch(SERVER_URL + '/wards/' + wardId + '/comments', { method: 'POST', headers: authHeaders(), body: JSON.stringify({ text: text }) }); if (!r.ok) { const err = await r.json().catch(() => ({})); throw new Error(err.detail || ('Ошибка ' + r.status)); } return await r.json(); } async function apiDeleteComment(commentId) { const r = await fetch(SERVER_URL + '/comments/delete?id=' + commentId, { method: 'GET', headers: authHeaders() }); if (!r.ok) { const err = await r.json().catch(() => ({})); throw new Error(err.detail || ('Ошибка ' + r.status)); } return await r.json(); } let wardOfTheDay = null; async function loadWardOfTheDay() { try { const r = await fetch(SERVER_URL + '/ward-of-the-day?_=' + Date.now(), { cache: 'no-store' }); if (!r.ok) return; const ward = await r.json(); if (!ward || !ward.id) { document.getElementById('ward-of-the-day').style.display = 'none'; return; } wardOfTheDay = ward; const likes = (ward.likes || []).length; const views = ward.views || 0; document.getElementById('wotd-title').textContent = ward.title || 'Без названия'; document.getElementById('wotd-stats').textContent = `❤️ ${likes} · 👁 ${views}`; document.getElementById('ward-of-the-day').style.display = 'block'; } catch (e) { console.error('loadWardOfTheDay error:', e); document.getElementById('ward-of-the-day').style.display = 'none'; } } window.openWardOfTheDay = function() { if (wardOfTheDay && wardOfTheDay.id) { goToWard(wardOfTheDay.id); } }; async function loadGroups() { try { const r = await fetch(SERVER_URL + '/groups'); if (!r.ok) return; const groups = await r.json(); const select = document.getElementById('group-filter'); const datalist = document.getElementById('group-list'); if (select) { select.innerHTML = '' + groups.map(g => ``).join(''); } if (datalist) { datalist.innerHTML = groups.map(g => `