Логика и подсказки:
Визуальные улучшения и анимации:
AJAX без перезагрузки:
Известное ограничение
При обновлении страницы (F5) выбранные фильтры сбрасываются, так как состояние хранится только в DOM. Для решения необходимо сохранять параметры фильтров в GET-параметрах URL — это также важно для SEO: поисковые системы смогут индексировать страницы с конкретными комбинациями фильтров как отдельные входные точки.
Адаптирован для мобильных устройств — на экранах до 960px контент выстраивается в один столбец.
Создан вариант V1 стартовой страницы в формате «Compact Grid» — все блоки собраны в компактную сетку из двух колонок. В hero-блоке появилась карусель фотографий университета и иконки для быстрой связи через мессенджеры. Доступен по адресу /applicant/v1.
Панель переключения между вариантами отображается на всех страницах сайта.
Проблема
При первом открытии страницы программ (когда кэш пуст) загрузка занимала более 1 секунды. Причина — сервис ProfileStructureService выполнял отдельный SQL-запрос к таблице profile_admin_data на каждую из 6409 строк таблицы program_subjects. Итого — до 6409 SQL-запросов при каждом холодном старте.
Решение
Все записи ProfileAdminData теперь предзагружаются одним SQL-запросом перед циклом и передаются в метод appendEduForm() как lookup-коллекция. Вместо обращения к базе на каждой итерации — мгновенный поиск по ключу в памяти.
Результат
| Метрика | Было | Стало |
|---|---|---|
| SQL-запросов (холодный кэш) | 6 409 | 1 |
| Время построения структуры | > 1 сек | ~73 мс |
Проблема
Для каждой уникальной комбинации фильтров (уровень образования + предметы ЕГЭ + формы обучения) создавался отдельный ключ кэша. Если пользователь менял хотя бы один фильтр — контроллер снова загружал все 6409 строк из базы, заново строил иерархическую структуру и сохранял под новым ключом. При активном использовании фильтров — десятки полных загрузок таблицы за сессию.
Решение
Полная структура теперь кэшируется один раз под единым ключом. Фильтрация по уровню, предметам и формам обучения выполняется из кэшированной структуры в памяти — без обращений к базе данных.
Результат
| Сценарий | Было | Стало |
|---|---|---|
| Холодный кэш (первый запрос) | 6409 строк из БД | 6409 строк из БД (123 мс, один раз) |
| Горячий кэш, без фильтров | 0 SQL, из кэша | 0 SQL, 3 мс |
| Горячий кэш + фильтр по ЕГЭ | снова 6409 строк из БД | 0 SQL, 4 мс |
| Каждая новая комбинация фильтров | снова 6409 строк из БД | 0 SQL, фильтрация из кэша |
Проблема
При загрузке страницы (особенно при Ctrl+F5) текст сначала отображался системным шрифтом, а через мгновение «перескакивал» на кастомный Helvetica Neue Cyr — это создавало визуальное «дёрганье» всей страницы.
Решение
Шрифты добавлены как ассеты Vite и подключены через <link rel="preload"> в layout. Браузер начинает скачивать шрифты параллельно с CSS, не дожидаясь парсинга @font-face. URL в preload совпадает с URL из CSS — браузер делает один запрос, а не два.
Результат
К моменту первого рендера шрифты уже в кэше — страница отображается сразу с правильной типографикой, без моргания.
Проблема
В основном layout-файле (default.blade.php) директива @vite вызывалась дважды — один раз безусловно в <head>, второй раз внутри условного блока проверки манифеста. В результате браузер получал:
<link rel="stylesheet"> — CSS применялся повторно, вызывая пересчёт стилей (re-layout / re-style)<script type="module"> — JS-код инициализировался дважды, обработчики событий навешивались повторно<link rel="preload"> и <link rel="modulepreload"> — лишние сетевые запросыВизуально это проявлялось как «моргание» при переключении между страницами — стили применялись, сбрасывались и применялись снова.
Решение
Удалён безусловный вызов @vite — оставлен единственный внутри условного блока с проверкой manifest.json. Также отключена клиентская фильтрация факультетов при клике по чекбоксу (до нажатия «Поиск»), которая дублировала серверную логику — список программ менялся мгновенно при клике, не дожидаясь отправки формы.
Результат
Каждый ассет (app.css, app.js, header.js) загружается ровно один раз. Устранено моргание при переходах, уменьшен объём сетевых запросов, обработчики событий не дублируются.
При двойном клике на кнопки предметов ЕГЭ или форм обучения браузер выделял текст внутри кнопки. Добавлен select-none — теперь быстрые клики по фильтрам не вызывают случайное выделение текста.
Проблема
На мобильных экранах (375px) контент был зажат в фиксированные 290px — по бокам оставались огромные пустые поля. На планшетах (768px) страница программ показывала мобильный фильтр вместо десктопного, карточки шли в одну узкую колонку. Футер был полностью скрыт на экранах меньше 1440px.
Решение
calc(100vw - 2rem)Проблемы
lang="en" на всех страницах — поисковые системы и скринридеры считали сайт англоязычнымfor на элементе <button> — допустим только на <label>id="burger-icon" и id="close-icon" — каждый <img> содержал id дважды<img> без атрибута alt — ошибка доступности (accessibility)type="" на кнопке «Очистить выбор» — невалидное значениеРешение
locale в config/app.php изменён на ru — теперь <html lang="ru">for="menu-toggle" с кнопок бургер-менюid на иконках бургер-менюalt="" на все декоративные <img> (Vector.svg, стрелки, hash.svg)type="" заменён на type="button"Яндекс.Метрика — исправлено
Код счётчика Яндекс.Метрики содержал две ошибки HTML-валидации:
<noscript>: в атрибуте src тега <img> использовался символ " (HTML-сущность кавычки) вместо обычной кавычки ". Из-за этого браузер воспринимал style="position:absolute; left:-9999px;" не как атрибут стиля, а как часть URL картинки. W3C-валидатор сообщал: «Bad value for attribute src — Illegal character in path segment», «Attribute position:absolute; not allowed on element img».<noscript> стоял в <head>: по спецификации HTML5 элемент <noscript> в <head> может содержать только <link>, <style> и <meta> — но не <div> и <img>. Валидатор выдавал: «Bad start tag div in noscript in head», что каскадно ломало весь документ («Stray end tag head», «Start tag body seen but an element of the same type was already open»).Что было сделано:
" заменены на обычные " — атрибуты src и style теперь парсятся корректно<script> + <noscript>) перенесён из <head> в конец <body> (перед </body>) — это допустимое и рекомендуемое размещение