Это продолжение истории про «Универсальный скачиватель», программу для Windows, которая качает видео и музыку через yt-dlp. В прошлый раз я рассказывал, как упаковывал консольную утилиту так, чтобы ей мог пользоваться любой человек. Сегодня про версию 1.4, а в ней почти нет нового функционала: я переделал внешний вид. Если кратко, то движок остался тем же (Flask на 127.0.0.1, страница в браузере, один файл index.html), а в Python поменялась одна строка с номером версии. Всё остальное — разметка, стили и немного скрипта.

Почему вообще полез в дизайн
Страница росла вместе с программой. Сначала там было поле для ссылки и кнопка. Потом появились вкладки «Музыка» и «VPN», выбор папки, переключатель темы, справка, кнопка «Помощь проекту», английский интерфейс. Каждая новая штука требовала себе место, и находила его в шапке или под полем ввода. К версии 1.3 шапка превратилась в ряд мелких кнопок, а папка загрузок жила то под одним блоком, то под другим, и на каждой вкладке в немного разном месте. Я сам иногда секунду думал, куда нажать. Для программы, которую я делал «для тех, кто не хочет разбираться», это плохой знак.
Второе: страница была центрированной колонкой шириной 980 пикселей на сером фоне (#f4f5f7) с синим акцентом, как у сотни других форм. Аккуратно, но скучно и немного похоже на админку. А это домашняя программа, которую открывают на пару минут, чтобы скачать песню в дорогу. Хотелось, чтобы выглядело как приложение, а не как форма.
Решение, до которого я дошёл, простое: всё постоянное вынести в боковую панель, а в основной части оставить только то, что относится к текущей вкладке.
Левая панель
Теперь слева закреплённая панель со скруглёнными углами. В ней по порядку: название, три вкладки («По ссылке», «Музыка», «VPN»), папка загрузок, кнопка «Помощь проекту», а в самом низу переключатель темы, справка и язык. Папка раньше была частью вкладок, и на «VPN» её просто не было, хотя там тоже иногда что-то скачивается. Теперь она на виду всегда, и менять её можно с любой вкладки.
Основной контент сдвинут вправо на ширину панели. На широком мониторе я не хотел, чтобы всё прижималось к левому краю, поэтому отступ слева считается через calc и max: панель остаётся у края, а содержимое занимает центр свободного места. Получилось одной строкой, без JS.
Самое неприятное оказалось с узкими экранами. Закреплённая панель шириной 244 пикселя на телефоне съедает половину экрана. Ниже 860 пикселей я делаю её обычным блоком сверху, вкладки выстраиваю в ряд (подписи под ними прячу, остаются заголовки), а «Помощь проекту», тема и язык идут одной строкой. На совсем узком экране, меньше 560, кнопка «Помощь проекту» занимает всю ширину отдельной строки, а подзаголовок над полем исчезает: на телефоне он только отодвигает поле вниз.

Стекло
Идея вот в чём: за интерфейсом лежит цветной фон из трёх размытых пятен (бирюзовое, голубое, жёлтое), а карточки на нём полупрозрачные и размывают то, что лежит под ними. Получается тот самый «матовый» эффект. Фон я не рисовал картинкой, это три radial-gradient на псевдоэлементе body::before, прибитом к окну, так что при прокрутке карточки скользят поверх неподвижных пятен.
Важный момент: backdrop-filter поддерживается не везде и не всегда одинаково. Программа ставится на самые разные компьютеры, браузеры на них тоже разные. Если сделать только полупрозрачность без размытия, текст на пёстром фоне читается плохо. Поэтому сначала я задаю карточке обычный сплошной цвет, и только если браузер умеет размытие, делаю её прозрачной:
.card { background: var(--card); /* сплошной цвет, работает везде */ border: 1px solid var(--line);}@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { .card { background: var(--glass); /* полупрозрачный, rgba(255, 255, 255, .55) */ -webkit-backdrop-filter: blur(22px) saturate(1.5); backdrop-filter: blur(22px) saturate(1.5); }}
Без поддержки пользователь видит просто светлые или тёмные плашки. Это не так красиво, но читается идеально, и никакой «ломаной» версии нет.
С размытием вышла и пара ловушек. Во-первых, нельзя безоглядно вкладывать стекло в стекло: карточка в карточке с backdrop-filter делает картинку мутной и заметно нагружает видеокарту на слабых ноутбуках. Внутренние элементы (плитки вариантов, строка с папкой) у меня просто чуть светлее фона карточки, без собственного размытия. Во-вторых, в диалоговых окнах стекло выключено совсем: под ними лежит затемнённая страница, размывать там нечего, а читаемость важнее. В диалогах используется сплошной цвет.
Тёмная и светлая темы
Цвета теперь живут в переменных на :root: фон и три пятна, цвет карточки, сплошной и прозрачный, линии, акцент, цвета ошибок и успеха. Тёмная тема — это тот же набор с другими значениями, и включается она двумя способами: по настройке Windows (prefers-color-scheme) или явно, атрибутом data-theme на html. Кнопка в панели переключает три состояния по кругу: «как в системе», светлая, тёмная. Выбор запоминается в localStorage.
Была одна мелочь, которая очень раздражала. Если выбрана тёмная тема, а скрипт включает её уже после загрузки страницы, на долю секунды вспыхивает белый фон. Лечится крошечным скриптом прямо в <head>, который читает сохранённое значение до того, как браузер что-либо нарисовал:
try { const t = localStorage.getItem('theme'); if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;} catch { /* хранилище недоступно: тема как в Windows */ }
Тёмная тема получилась почти чёрной с бирюзовым акцентом, пятна на фоне стали приглушёнными: зелёное, синее и тёмно-оливковое. Они едва заметны, но без них стекло в тёмной теме не работает: размывать нечего, и карточки сливаются с фоном.

Строка ввода и варианты скачивания
Главный элемент страницы — строка, в которую вставляют ссылку. Раньше это было обычное поле с кнопкой рядом. Теперь это одна большая «капсула» с кнопкой «Найти» внутри. Поле умеет расти: туда можно вставить длинную ссылку с кучей параметров или целый список ссылок, по одной в строке, и оно увеличится по высоте, вместо того чтобы прятать хвост. На узком экране капсула превращается в колонку, а кнопка растягивается на всю ширину, потому что попасть пальцем в маленькую кнопку сбоку неудобно.
Варианты скачивания (видео в разном качестве, только звук, субтитры и прочее) выводились списком. Теперь они лежат плитками, которые сами подстраиваются под ширину карточки: grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)). На широком экране получается три-четыре в ряд, на узком — одна-две, и никаких медиазапросов для этого писать не пришлось.
Плеер
Для вкладки «Музыка» в программе есть встроенный плеер, чтобы послушать песню до скачивания. Раньше он был полосой во всю ширину, прижатой к низу окна. Она закрывала последние строки списка, и под неё приходилось отдельно добавлять отступ у страницы. Теперь это тёмный «островок» в форме таблетки, который стоит над полем и едет вместе со страницей (position: sticky). Он тёмный в любой теме, и это сознательно: тёмная таблетка с кнопками хорошо видна и на светлом, и на тёмном фоне.

Тут пришлось повозиться со вторым закреплённым элементом. На вкладке «Музыка» есть полоса выбора песен (галочка «Выбрать все» и кнопки «Скачать MP3» и «M4A»), которая тоже закреплена вверху. Раньше они перекрывали друг друга, а теперь полоса сдвигается под островок, но только пока плеер реально на странице. Для этого, когда плеер появляется, я вешаю на html класс has-player, и уже по нему подстраиваются и смещение полосы, и scroll-padding-top, чтобы переход к якорю не прятал заголовок под островком.
На телефоне всё по-другому: островок закреплён внизу экрана, с отступом env(safe-area-inset-bottom) для телефонов с «подбородком», а страница получает дополнительный отступ снизу, иначе последняя карточка оказывается под плеером. Всплывающие уведомления при этом тоже поднимаются выше. Регулятор громкости на телефоне я убрал совсем: громкость там и так меняется кнопками.
Справка
У программы есть своя страница справки, довольно длинная (больше 70 килобайт HTML). Если оставить её в старой палитре (серый фон, синие ссылки), то рядом с новой главной она выглядела бы чужой. Текст я не трогал, а цвета, тёмную тему и то же стекло с запасным вариантом перенёс в неё теми же переменными. Хорошо, что у справки и раньше цвета были в переменных, иначе пришлось бы править каждый селектор.
Побочная история: кнопки, которые не работали из-за реестра
Это не про дизайн и вышло оно в версии 1.3.1, но стоит рассказать. Симптом был странный: страница открывается, всё на месте, но кнопки ничего не делают, а «Найти» просто очищает поле.
Причина оказалась в Windows. Статику Flask отдаёт через send_from_directory, а тип файла определяет стандартный модуль mimetypes, который на Windows сначала смотрит в реестр, в раздел HKEY_CLASSES_ROOT\.js, параметр Content Type. У части людей там оказывается text/plain, и кто его туда записал, на самом деле неважно. Сервер честно отдал скрипт с таким типом. А у ответов стоит заголовок X-Content-Type-Options: nosniff, который запрещает браузеру угадывать тип по содержимому. Браузер видит text/plain, отказывается выполнять и молчит. Обработчики кнопок просто не навешиваются.
Починка — задать тип вручную, не доверяя системе:
resp = send_from_directory(WEB, name, mimetype='text/javascript')
А файл переводов i18n.js я вообще встроил прямо в страницу, чтобы она не зависела от второго внешнего скрипта. Урок для себя: на Windows любой путь, в который вмешивается реестр, надо проверять на чужой машине, а не только на своей.
Что осталось как было
Всё, что под капотом: скачивание, поиск музыки, плейлисты, выбор дорожек и субтитров, расширение для браузеров, автообновление. Установленная программа сама предложит обновиться: она проверяет релиз на GitHub, сверяет SHA-256 архива и откатывается назад, если что-то пошло не так.
Скачать
Архив: https://github.com/OlegChe77/universal-downloader/releases/latest/download/universal-downloader.zip
Исходный код и список изменений: https://github.com/OlegChe77/universal-downloader
Распаковать и запустить, ничего ставить не нужно. Буду рад отзывам, особенно про узкие экраны и про слабые компьютеры, где размытие может тормозить.
ссылка на оригинал статьи https://habr.com/ru/articles/1104686/