/
liatana
/
ID
Обзор
Документация
Войти
/
liatana
/
ID
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
977 строк
45 KB
liatana
Create: article1.html, article2.html, article3.html, article4.html, Update: index.html
19 июн 2026, 19:26
Верифицирован
19 июн 2026, 19:26
72006fb
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"> <title>Блог об ИИ — Главная</title> <style> /* ===== ОСНОВНОЙ ДИЗАЙН ===== */ body { background: linear-gradient(135deg, #f3e8ff 0%, #e8d5f5 15%, #d5c4f0 30%, #c4d4f0 45%, #c4f0e8 55%, #d5f5d5 65%, #f0f0c4 75%, #f5e8c4 85%, #f5d5d5 100%); min-height: 100vh; padding: 20px; background-attachment: fixed; background-size: 400% 400%; animation: gradientShift 20s ease infinite; } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .page-title { text-align: center; margin-bottom: 30px; padding: 30px 20px; border-bottom: 3px solid #7c3aed; background: rgba(255, 255, 255, 0.3); backdrop-filter: blur(10px); border-radius: 20px; } .page-title h1 { font-size: 2.2rem; font-weight: 700; background: linear-gradient(135deg, #7c3aed, #ec4899, #ef4444); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .page-title p { font-size: 1.1rem; color: #4a1a6b; font-weight: 500; } .section-title { text-align: center; font-size: 2rem; font-weight: 700; margin-bottom: 30px; background: linear-gradient(135deg, #7c3aed, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .post-preview { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); border-radius: 20px; padding: 25px 30px; margin-bottom: 25px; border: 1px solid rgba(255, 255, 255, 0.3); box-shadow: 0 4px 15px rgba(124, 58, 237, 0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; } .post-preview:hover { transform: translateY(-4px); box-shadow: 0 10px 30px rgba(124, 58, 237, 0.2); } .post-preview h2 { font-size: 1.6rem; font-weight: 700; margin-bottom: 8px; background: linear-gradient(135deg, #7c3aed, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .post-preview .meta { color: #6c757d; font-size: 0.9rem; margin-bottom: 12px; } .post-preview .meta span { margin-right: 20px; } .post-preview p { color: #1e1b4b; font-size: 1rem; line-height: 1.6; } .post-preview .tag { display: inline-block; background: rgba(124, 58, 237, 0.15); color: #7c3aed; padding: 2px 14px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; margin-right: 6px; } .btn-read { background: linear-gradient(135deg, #7c3aed, #ec4899); border: none; color: white; font-weight: 600; padding: 8px 24px; border-radius: 40px; font-size: 0.9rem; transition: transform 0.2s, box-shadow 0.2s; box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3); display: inline-block; text-decoration: none; text-align: center; } .btn-read:hover { transform: scale(1.05); box-shadow: 0 8px 25px rgba(124, 58, 237, 0.4); color: white; } .container { max-width: 900px; } @media (max-width: 768px) { .page-title h1 { font-size: 1.8rem; } .post-preview { padding: 20px; } .post-preview h2 { font-size: 1.3rem; } } /* ===== СТИЛИ ДЛЯ ПОИСКОВОГО БЛОКА ===== */ .search-card { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); border-radius: 20px; padding: 25px 30px; margin-bottom: 30px; border: 1px solid rgba(255, 255, 255, 0.3); box-shadow: 0 4px 15px rgba(124, 58, 237, 0.1); } .search-card .search-input { border: 2px solid rgba(124, 58, 237, 0.2); border-radius: 50px; padding: 12px 24px; font-size: 1.1rem; background: rgba(255, 255, 255, 0.7); transition: all 0.3s ease; width: 100%; } .search-card .search-input:focus { border-color: #7c3aed; box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.15); outline: none; background: white; } .search-card .search-input::placeholder { color: #9ca3af; } .search-wrapper { position: relative; } .search-icon { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); color: #7c3aed; font-size: 1.2rem; pointer-events: none; } .result-count { color: #6c757d; font-size: 0.9rem; margin-top: 12px; margin-bottom: 0; } .result-count strong { color: #4a1a6b; } .result-count .synonym-hint { font-size: 0.85rem; color: #7c3aed; background: rgba(124, 58, 237, 0.1); padding: 2px 12px; border-radius: 20px; margin-left: 10px; } .result-item { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); border-radius: 16px; padding: 18px 24px; margin-bottom: 12px; border: 1px solid rgba(124, 58, 237, 0.08); transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.02); cursor: pointer; position: relative; } .result-item:hover { border-color: rgba(124, 58, 237, 0.25); box-shadow: 0 4px 15px rgba(124, 58, 237, 0.08); transform: translateY(-1px); } .result-item .result-text { font-size: 1rem; color: #1e1b4b; margin-bottom: 6px; line-height: 1.5; } .result-item .result-text .highlight { background: rgba(124, 58, 237, 0.15); padding: 0 4px; border-radius: 4px; font-weight: 600; color: #7c3aed; } .result-item .result-text .synonym-match { background: rgba(236, 72, 153, 0.15); padding: 0 4px; border-radius: 4px; font-weight: 600; color: #ec4899; border-bottom: 2px dotted #ec4899; } .result-item .badge-score { background: linear-gradient(135deg, #7c3aed, #ec4899); color: white; font-weight: 700; font-size: 0.75rem; padding: 4px 14px; border-radius: 50px; white-space: nowrap; } .result-item .badge-score .boost-indicator { font-size: 0.6rem; opacity: 0.7; margin-left: 4px; } .result-item .bar-track { width: 100%; height: 4px; background: rgba(124, 58, 237, 0.08); border-radius: 10px; margin-top: 8px; overflow: hidden; } .result-item .bar-fill { height: 100%; border-radius: 10px; background: linear-gradient(90deg, #7c3aed, #ec4899); transition: width 0.4s ease; width: 0%; } .result-item .meta-tags { margin-top: 6px; } .result-item .meta-tags .tag { display: inline-block; background: rgba(124, 58, 237, 0.10); color: #7c3aed; padding: 0px 12px; border-radius: 20px; font-size: 0.7rem; font-weight: 600; margin-right: 4px; } .result-item .synonym-tag { display: inline-block; background: rgba(236, 72, 153, 0.10); color: #ec4899; padding: 0px 12px; border-radius: 20px; font-size: 0.7rem; font-weight: 600; margin-right: 4px; } .result-item .article-link { display: inline-block; color: #7c3aed; font-weight: 600; font-size: 0.85rem; text-decoration: none; margin-top: 6px; transition: all 0.2s ease; } .result-item .article-link:hover { text-decoration: underline; color: #ec4899; } .result-item .article-link .arrow { transition: transform 0.2s ease; display: inline-block; } .result-item .article-link:hover .arrow { transform: translateX(4px); } .result-item .click-hint { position: absolute; right: 24px; bottom: 18px; font-size: 0.7rem; color: #9ca3af; opacity: 0; transition: opacity 0.3s ease; } .result-item:hover .click-hint { opacity: 1; } .empty-state { text-align: center; padding: 40px 20px; color: #6c757d; } .empty-state .emoji { font-size: 3rem; display: block; margin-bottom: 12px; } .empty-state p { margin-bottom: 0; font-size: 1.05rem; } .empty-state .suggestions { margin-top: 12px; font-size: 0.95rem; color: #7c3aed; } .empty-state .suggestions .suggestion-chip { display: inline-block; background: rgba(124, 58, 237, 0.1); padding: 4px 16px; border-radius: 50px; margin: 4px 6px; cursor: pointer; transition: all 0.2s ease; } .empty-state .suggestions .suggestion-chip:hover { background: rgba(124, 58, 237, 0.2); transform: scale(1.05); } .quick-preview { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); backdrop-filter: blur(8px); display: none; justify-content: center; align-items: center; z-index: 1000; padding: 20px; } .quick-preview.active { display: flex; } .quick-preview .preview-content { background: white; border-radius: 24px; padding: 35px 40px; max-width: 700px; max-height: 80vh; overflow-y: auto; position: relative; animation: slideUp 0.3s ease; } @keyframes slideUp { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .quick-preview .close-btn { position: sticky; top: 0; float: right; background: rgba(124, 58, 237, 0.1); border: none; border-radius: 50%; width: 36px; height: 36px; font-size: 1.2rem; cursor: pointer; transition: all 0.2s ease; color: #4a1a6b; z-index: 1; } .quick-preview .close-btn:hover { background: rgba(124, 58, 237, 0.2); transform: rotate(90deg); } .quick-preview .preview-title { color: #4a1a6b; font-weight: 700; margin-bottom: 12px; font-size: 1.4rem; } .quick-preview .preview-body { color: #1e1b4b; line-height: 1.7; font-size: 1rem; } .quick-preview .preview-body .highlight { background: rgba(124, 58, 237, 0.15); padding: 0 4px; border-radius: 4px; font-weight: 600; color: #7c3aed; } .quick-preview .preview-body .synonym-match { background: rgba(236, 72, 153, 0.15); padding: 0 4px; border-radius: 4px; font-weight: 600; color: #ec4899; border-bottom: 2px dotted #ec4899; } .quick-preview .preview-link { display: inline-block; margin-top: 15px; color: #7c3aed; font-weight: 600; text-decoration: none; padding: 8px 24px; border-radius: 50px; background: rgba(124, 58, 237, 0.1); transition: all 0.3s ease; } .quick-preview .preview-link:hover { background: rgba(124, 58, 237, 0.2); transform: translateX(4px); } @media (max-width: 768px) { .result-item .click-hint { display: none; } .quick-preview .preview-content { padding: 25px 20px; margin: 10px; max-height: 90vh; } } #articlesList.hidden { display: none; } </style> </head> <body> <div class="container"> <!-- ===== ЗАГОЛОВОК ===== --> <div class="page-title"> <h1>🧠 ИИ — это просто</h1> <p>Разбираемся в искусственном интеллекте без сложных терминов</p> </div> <!-- ===== ПОИСКОВЫЙ БЛОК ===== --> <div class="search-card"> <div class="search-wrapper"> <input type="text" id="queryInput" class="search-input" placeholder="🔍 Поиск по статьям с учётом синонимов..." autofocus > <span class="search-icon">🔎</span> </div> <p class="result-count" id="resultCount"> Найдено <strong>0</strong> совпадений <span class="synonym-hint" id="synonymHint" style="display:none;">🔍 учтены синонимы</span> </p> </div> <!-- ===== РЕЗУЛЬТАТЫ ПОИСКА ===== --> <div id="searchResults"> <div class="empty-state"> <span class="emoji">💡</span> <p>Начните вводить запрос, чтобы найти статьи по теме</p> </div> </div> <!-- ===== СПИСОК СТАТЕЙ ===== --> <div id="articlesList"> <h2 class="section-title">📚 Все статьи</h2> <!-- Статья 1 --> <div class="post-preview"> <h2>Что такое искусственный интеллект?</h2> <div class="meta"> <span>📅 18 июня 2026</span> <span>⏱ 4 мин чтения</span> <span class="tag">#введение</span> <span class="tag">#для начинающих</span> </div> <p> Искусственный интеллект — это способность компьютеров учиться на примерах и выполнять задачи, требующие человеческого ума. Разбираемся без сложных терминов. </p> <a href="article1.html" class="btn-read">Читать статью →</a> </div> <!-- Статья 2 --> <div class="post-preview"> <h2>Какие бывают ИИ-помощники?</h2> <div class="meta"> <span>📅 14 июня 2026</span> <span>⏱ 5 мин чтения</span> <span class="tag">#обзор</span> <span class="tag">#модели</span> </div> <p> Сегодня существует множество моделей ИИ: ChatGPT, Claude, Gemini, Qwen, Llama. У каждой свои сильные стороны. Рассказываем, какую выбрать для своих задач. </p> <a href="article2.html" class="btn-read">Читать статью →</a> </div> <!-- Статья 3 --> <div class="post-preview"> <h2>Где уже используют ИИ?</h2> <div class="meta"> <span>📅 10 июня 2026</span> <span>⏱ 4 мин чтения</span> <span class="tag">#применение</span> <span class="tag">#будущее</span> </div> <p> Искусственный интеллект уже проник в медицину, образование, производство, банки и даже искусство. Узнайте, как ИИ меняет мир вокруг нас. </p> <a href="article3.html" class="btn-read">Читать статью →</a> </div> <!-- Статья 4 --> <div class="post-preview"> <h2>Этика и ИИ: о чём важно помнить?</h2> <div class="meta"> <span>📅 6 июня 2026</span> <span>⏱ 3 мин чтения</span> <span class="tag">#этика</span> <span class="tag">#безопасность</span> </div> <p> ИИ — мощный инструмент, но пользоваться им нужно ответственно. Проверяйте факты, не доверяйте личные данные и помните, что решение всегда за вами. </p> <a href="article4.html" class="btn-read">Читать статью →</a> </div> </div> <!-- ===== ПОДВАЛ ===== --> <div style="text-align: center; color: #4a1a6b; padding: 30px 0 10px; opacity: 0.7; font-size: 0.9rem; border-top: 1px solid rgba(124, 58, 237, 0.2); margin-top: 20px;"> <p>🧠 Блог об ИИ — просто и понятно</p> <p style="font-size:0.8rem;">Все статьи доступны для чтения</p> </div> </div> <!-- ===== МОДАЛЬНОЕ ОКНО ===== --> <div class="quick-preview" id="quickPreview"> <div class="preview-content"> <button class="close-btn" onclick="closePreview()">✕</button> <div class="preview-title" id="previewTitle">Фрагмент статьи</div> <div class="preview-body" id="previewBody">Текст...</div> <a href="#" class="preview-link" id="previewLink" target="_blank">📖 Читать полную статью →</a> </div> </div> <script> // ============================================================ // ДАННЫЕ СТАТЕЙ ДЛЯ ПОИСКА // ============================================================ const articlesData = [ { id: 0, title: "Что такое искусственный интеллект?", link: "article1.html", parts: [ "Искусственный интеллект — это область компьютерных наук, которая занимается созданием систем, способных выполнять задачи, требующие человеческого интеллекта. Это не магия и не роботы-убийцы из фильмов, а просто программы, которые умеют учиться на примерах.", "Самый простой пример — голосовые помощники, такие как Алиса, Siri или Google Assistant. Они распознают вашу речь, переводят её в текст, ищут ответ в базе данных и озвучивают его. Всё это происходит за доли секунды.", "Другой пример — рекомендательные системы. Когда YouTube предлагает вам следующее видео или Spotify составляет плейлист — это работает ИИ. Алгоритмы анализируют, что вы смотрели или слушали раньше, находят похожий контент и предлагают его вам.", "Важно понимать: ИИ не думает как человек. Он ищет закономерности в огромных массивах данных и на основе них делает выводы. Поэтому иногда он может ошибаться — например, принять кошку за собаку, если на фото плохое качество." ], tags: ["введение", "для начинающих"] }, { id: 1, title: "Какие бывают ИИ-помощники?", link: "article2.html", parts: [ "Сегодня существует множество моделей ИИ: ChatGPT, Claude, Gemini, Qwen, Llama. У каждой свои сильные стороны.", "ChatGPT от OpenAI — один из самых популярных помощников. Он умеет отвечать на вопросы, писать тексты, помогать с программированием.", "Claude от Anthropic — модель с акцентом на безопасность и этику. Она хорошо подходит для работы с длинными документами.", "Gemini от Google — мультимодальная модель, которая может обрабатывать текст, изображения, видео и аудио.", "Qwen от Alibaba — мощная модель с поддержкой многих языков, включая русский.", "Llama от Meta — открытая модель, которую можно запускать на своём компьютере." ], tags: ["обзор", "модели"] }, { id: 2, title: "Где уже используют ИИ?", link: "article3.html", parts: [ "Искусственный интеллект уже проник в медицину, образование, производство, банки и даже искусство.", "В медицине ИИ помогает диагностировать болезни по снимкам, предсказывать развитие заболеваний и подбирать лечение.", "В образовании ИИ создаёт персонализированные программы обучения и проверяет домашние задания.", "В производстве ИИ оптимизирует процессы, предсказывает поломки оборудования и управляет роботами.", "В банках ИИ анализирует кредитные риски, выявляет мошенничество и консультирует клиентов.", "В искусстве ИИ генерирует музыку, пишет стихи и даже создаёт картины." ], tags: ["применение", "будущее"] }, { id: 3, title: "Этика и ИИ: о чём важно помнить?", link: "article4.html", parts: [ "Этика ИИ — важная тема. Кто отвечает за ошибки, которые совершает ИИ?", "Как гарантировать, что алгоритмы не дискриминируют людей по расовому, половому или возрастному признаку?", "Важный аспект — прозрачность алгоритмов. Пользователи должны понимать, почему система принимает то или иное решение.", "ИИ — мощный инструмент, но пользоваться им нужно ответственно. Проверяйте факты, не доверяйте личные данные и помните, что решение всегда за вами.", "Существуют международные инициативы по созданию этических стандартов для ИИ.", "Будущее этики ИИ зависит от совместной работы учёных, законодателей и общества." ], tags: ["этика", "безопасность"] } ]; // Собираем корпус для поиска const corpus = []; articlesData.forEach(article => { article.parts.forEach(part => { corpus.push(part); }); }); // ============================================================ // СЛОВАРЬ СИНОНИМОВ // ============================================================ const synonymDict = { 'искусственный интеллект': ['ии', 'ai', 'нейросети', 'машинное обучение', 'глубокое обучение', 'ml', 'chatgpt', 'gpt'], 'ии': ['искусственный интеллект', 'ai', 'нейросети', 'машинное обучение'], 'ai': ['искусственный интеллект', 'ии', 'нейросети', 'машинное обучение'], 'нейросети': ['нейронные сети', 'глубокое обучение', 'искусственный интеллект', 'ии', 'машинное обучение'], 'нейронные сети': ['нейросети', 'глубокое обучение', 'искусственный интеллект'], 'машинное обучение': ['обучение', 'ml', 'глубокое обучение', 'нейросети', 'искусственный интеллект'], 'глубокое обучение': ['машинное обучение', 'нейросети', 'нейронные сети', 'искусственный интеллект'], 'ml': ['машинное обучение', 'обучение', 'нейросети'], 'обучение': ['тренировка', 'настройка', 'подготовка', 'машинное обучение', 'ml'], 'данные': ['информация', 'массивы данных', 'наборы данных', 'датасеты', 'big data'], 'алгоритмы': ['методы', 'подходы', 'модели', 'программы', 'код'], 'модели': ['алгоритмы', 'методы', 'нейросети', 'chatgpt', 'claude', 'gemini', 'llama'], 'помощники': ['ассистенты', 'боты', 'чатботы', 'голосовые помощники'], 'чатботы': ['помощники', 'ассистенты', 'боты', 'chatgpt'], 'chatgpt': ['gpt', 'openai', 'чатбот', 'ии', 'искусственный интеллект'], 'gpt': ['chatgpt', 'openai', 'искусственный интеллект'], 'claude': ['anthropic', 'ии', 'искусственный интеллект'], 'gemini': ['google', 'ии', 'искусственный интеллект'], 'llama': ['meta', 'открытая модель', 'ии'], 'медицина': ['здравоохранение', 'диагностика', 'лечение', 'болезни'], 'образование': ['учеба', 'школа', 'обучение', 'программы обучения'], 'производство': ['заводы', 'роботы', 'оптимизация', 'оборудование'], 'банки': ['финансы', 'кредиты', 'мошенничество', 'клиенты'], 'искусство': ['творчество', 'музыка', 'картины', 'стихи', 'генерация'], 'этика': ['мораль', 'нравственность', 'этический', 'ответственность'], 'безопасность': ['защита', 'надежность', 'безопасный'], 'ответственность': ['этика', 'мораль', 'обязанность'], 'дискриминация': ['неравенство', 'предубеждения', 'справедливость'], 'прозрачность': ['понятность', 'объяснимость', 'открытость'], 'факты': ['информация', 'данные', 'правда', 'достоверность'], 'риски': ['опасности', 'угрозы', 'проблемы', 'вызовы'] }; // ============================================================ // ПОИСКОВЫЙ ДВИЖОК // ============================================================ function tokenize(text) { return text .toLowerCase() .replace(/[^а-яёa-z0-9\s]/g, " ") .split(/\s+/) .filter(Boolean); } function expandWithSynonyms(tokens) { const expanded = new Set(tokens); tokens.forEach(token => { if (synonymDict[token]) { synonymDict[token].forEach(syn => { if (syn.length <= 30) expanded.add(syn); }); } for (const [key, synonyms] of Object.entries(synonymDict)) { if (key.includes(token) && key.length > token.length + 1) { expanded.add(key); synonyms.forEach(syn => { if (syn.length <= 30) expanded.add(syn); }); } synonyms.forEach(syn => { if (syn.includes(token) && syn.length > token.length + 1) { expanded.add(syn); expanded.add(key); } }); } }); return Array.from(expanded); } function termFreq(tokens) { const tf = {}; tokens.forEach(t => tf[t] = (tf[t] || 0) + 1); return tf; } function cosineSimilarity(tfA, tfB) { const keys = new Set([...Object.keys(tfA), ...Object.keys(tfB)]); let dot = 0, magA = 0, magB = 0; keys.forEach(k => { const a = tfA[k] || 0; const b = tfB[k] || 0; dot += a * b; magA += a * a; magB += b * b; }); if (magA === 0 || magB === 0) return 0; return dot / (Math.sqrt(magA) * Math.sqrt(magB)); } function findArticleByPart(partText) { for (const article of articlesData) { if (article.parts.includes(partText)) { return article; } } return null; } // ============================================================ // РЕНДЕРИНГ // ============================================================ const resultsEl = document.getElementById("searchResults"); const articlesListEl = document.getElementById("articlesList"); const inputEl = document.getElementById("queryInput"); const countEl = document.getElementById("resultCount"); const synonymHintEl = document.getElementById("synonymHint"); function highlightWithSynonyms(text, originalTokens, expandedTokens) { let result = text; const exactMatches = new Set(); originalTokens.forEach(token => { const regex = new RegExp(`(${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); const matches = text.match(regex); if (matches) { matches.forEach(m => exactMatches.add(m.toLowerCase())); } }); const synonymMatches = new Set(); expandedTokens.forEach(token => { if (!originalTokens.includes(token)) { const regex = new RegExp(`(${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); const matches = text.match(regex); if (matches) { matches.forEach(m => { if (!exactMatches.has(m.toLowerCase())) { synonymMatches.add(m); } }); } } }); exactMatches.forEach(match => { const regex = new RegExp(`(${match.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); result = result.replace(regex, '<span class="highlight">$1</span>'); }); synonymMatches.forEach(match => { const regex = new RegExp(`(${match.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); result = result.replace(regex, '<span class="synonym-match">$1</span>'); }); return result; } function getSuggestions(query) { const suggestions = []; const words = tokenize(query); words.forEach(word => { for (const [key, synonyms] of Object.entries(synonymDict)) { if (key.includes(word) && !suggestions.includes(key) && key !== word) { suggestions.push(key); } synonyms.forEach(syn => { if (syn.includes(word) && !suggestions.includes(syn) && syn !== word) { suggestions.push(syn); } }); } }); return suggestions.slice(0, 5); } function openPreview(article, text, query) { const preview = document.getElementById('quickPreview'); document.getElementById('previewTitle').textContent = article.title; document.getElementById('previewLink').href = article.link; const qTokens = tokenize(query || ''); const expanded = expandWithSynonyms(qTokens); const highlighted = highlightWithSynonyms(text, qTokens, expanded); document.getElementById('previewBody').innerHTML = highlighted; preview.classList.add('active'); document.body.style.overflow = 'hidden'; } function closePreview() { document.getElementById('quickPreview').classList.remove('active'); document.body.style.overflow = ''; } document.getElementById('quickPreview').addEventListener('click', function(e) { if (e.target === this) closePreview(); }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closePreview(); }); function render(query) { const trimmed = query.trim(); if (!trimmed) { articlesListEl.classList.remove('hidden'); resultsEl.innerHTML = ` <div class="empty-state"> <span class="emoji">💡</span> <p>Начните вводить запрос, чтобы найти статьи по теме</p> </div> `; countEl.innerHTML = 'Найдено <strong>0</strong> совпадений'; synonymHintEl.style.display = 'none'; return; } articlesListEl.classList.add('hidden'); const originalTokens = tokenize(trimmed); const expandedTokens = expandWithSynonyms(originalTokens); const qTf = termFreq(expandedTokens); const scored = corpus.map((sentence, index) => { const sTf = termFreq(tokenize(sentence)); const score = cosineSimilarity(qTf, sTf); const boostedScore = Math.min(score * 1.15, 1); return { id: index, sentence: sentence, pct: Math.round(boostedScore * 100), rawPct: Math.round(score * 100) }; }) .filter(item => item.pct > 0) .sort((a, b) => b.pct - a.pct); const usedSynonyms = expandedTokens.some(t => !originalTokens.includes(t)); synonymHintEl.style.display = usedSynonyms ? 'inline' : 'none'; countEl.innerHTML = `Найдено <strong>${scored.length}</strong> совпадений`; if (scored.length === 0) { const suggestions = getSuggestions(trimmed); let suggestionHtml = ''; if (suggestions.length > 0) { suggestionHtml = ` <div class="suggestions"> <p>Возможно, вы искали:</p> ${suggestions.map(s => `<span class="suggestion-chip" onclick="document.getElementById('queryInput').value='${s}'; render('${s}');">${s}</span>` ).join('')} </div> `; } resultsEl.innerHTML = ` <div class="empty-state"> <span class="emoji">😕</span> <p>Ничего не найдено по запросу «${trimmed}»</p> <p style="font-size:0.9rem; color:#9ca3af; margin-top:4px;">Попробуйте другие ключевые слова</p> ${suggestionHtml} </div> `; return; } // Группируем по статьям const groupedResults = {}; scored.forEach(item => { const article = findArticleByPart(item.sentence); if (article) { if (!groupedResults[article.id]) { groupedResults[article.id] = { article: article, parts: [], maxPct: 0 }; } groupedResults[article.id].parts.push(item); if (item.pct > groupedResults[article.id].maxPct) { groupedResults[article.id].maxPct = item.pct; } } }); resultsEl.innerHTML = Object.values(groupedResults) .sort((a, b) => b.maxPct - a.maxPct) .map(group => { const article = group.article; const bestPart = group.parts.sort((a, b) => b.pct - a.pct)[0]; const highlighted = highlightWithSynonyms( bestPart.sentence, originalTokens, expandedTokens.filter(t => !originalTokens.includes(t)) ); const boostIndicator = (bestPart.pct > bestPart.rawPct) ? `<span class="boost-indicator">⬆ +${bestPart.pct - bestPart.rawPct}%</span>` : ''; const allTags = article.tags.map(t => `<span class="tag">#${t}</span>`).join(''); const articleLink = article.link; const escapedSentence = bestPart.sentence.replace(/'/g, "\\'"); const escapedQuery = trimmed.replace(/'/g, "\\'"); return ` <div class="result-item" onclick="openPreview( {title: '${article.title}', link: '${articleLink}'}, '${escapedSentence}', '${escapedQuery}' )"> <div class="d-flex justify-content-between align-items-start"> <div class="result-text">${highlighted}</div> <span class="badge-score">${bestPart.pct}% ${boostIndicator}</span> </div> <div class="bar-track"> <div class="bar-fill" style="width:${bestPart.pct}%"></div> </div> <div class="meta-tags"> ${allTags} ${usedSynonyms ? '<span class="synonym-tag">🔄 синонимы</span>' : ''} <span class="synonym-tag">📄 ${article.title}</span> </div> <a href="${articleLink}" target="_blank" class="article-link" onclick="event.stopPropagation();"> Читать полную статью <span class="arrow">→</span> </a> <span class="click-hint">👆 клик для предпросмотра</span> </div> `; }).join(""); } // ============================================================ // ИНИЦИАЛИЗАЦИЯ // ============================================================ inputEl.addEventListener("input", e => render(e.target.value)); window.addEventListener('load', () => { inputEl.focus(); }); window.openPreview = openPreview; window.closePreview = closePreview; window.render = render; </script> </body> </html>