Личный и рабочий блог Николая Огрызкова



Развитие сайта

Зачем была создана эта страничка?

Я думаю, всем нам иногда бывает интересно, как тот или иной человек развивался и как начинал путь. Когда объектом интереса стану я, мне бы хотелось, чтобы это было реализовано удобно для всех и с доступностью прямо с сайта.

Именно для этого и создан данный раздел на сайте. Вы можете, во-первых, по дням отследить, как и что я изучал, с самого первого дня развития в формате ленты. Во-вторых, вы можете посмотреть, как в тот или иной день выглядел сайт. На момент создания этой страницы он представлял из себя голый HTML-документ — однако кто знает, каким будет сайт в будущем? =)

Обучение по дням

День 16: знакомство с боксовой (блочной) моделью, обновление единиц измерения в CSS

Дата публикации:

Размер шрифтов на сайте

Всем пр-пр-патапим! Сегодня продолжаем работу над изучением CSS. В последний раз я останавливался на единицах измерения. Настала пора ими заняться, и выяснилось, что указывать размер шрифта в пикселях — это кринж. Почему? Дело в том, что разные пользователи по-разному настраивают размер шрифта по умолчанию в браузере. Я отношусь к тому типу людей, которые пользуются вариантом по умолчанию, но есть люди, которым необходимо видеть более большой текст на экране или которые, наоборот, предпочитают более мелкий текст. Они указывают это в настройках браузера и большинство сайтов их слушаются; вот только мой сайт не входил в их число. Дело было в том, что я указывал размеры шрифтов в пикселях — это означает, что браузер вообще не смотрит, какие там настройки у пользователя. Чтобы эти настройки оставались учтёнными, лучше использовать rem — относительную единицу измерения, которая обращается к корневому размеру шрифта, указанному именно в настройках браузера. По умолчанию у большинства стоит размер 16 пикселей, то есть, чтобы перевести из пикселей в rem, нужно разделить значения в пикселях на 16. Я так сделал, и сайт мой выглядит неотличимо для меня, но теперь учитывает настройки размера шрифта пользователей. 🌸

Фикс форм на мобильных устройствах

Просматривая свой сайт на смартфоне, я понял, что я могу пролистать немного вправо. Так быть не должно, поэтому я стал искать источник проблемы, и выяснилось, что дело в форме, где я прошу рассказать людей о своём свэге. Погуглив, как это можно исправить, я добавил несколько строк в CSS, которые регулируют длины форм и их элементов. Проверил с телефона в локальной сети — работает!

Боксовая модель… о-о-ой, даже звучит страшно!

Следующей темой на курсе выступает знакомство с Box Model; выделяется на это 20 минут. Вперёд, наверное!.. 😭

Кто такие эти ваши боксы?

Вот кто!!

Box может быть circle!

На сегодня, пожалуй, достаточно приключений. :p Теперь лучше понимаю, как отступы настраивать. 💅

День 15: у меня выдались сложные сутки

Дата публикации:

Всем пр-пр-патапим! Вчера и позавчера я возился с новым столом и потом убирался за собой. В итоге я сначала не лёг, потом вырубился в часов примерно и проснулся в . В общем, сегодня не до кода; зато у меня стол с подъёмным механизмом и идеальный кабель-менеджмент под ним!

Хотя я сделал пару косметических изменений в виде создания чуть более красивых ссылок в архиве и исправления очевидных опечаток, о которых мне любезно сообщила мью в одной из соцсетей. 😶‍🌫️

День 14: внезапная возня с иконками, обновление структуры и вредные поисковые роботы

Дата публикации:

Та же иконка, но другая

Всем пр-пр-патапим! (Да, у меня теперь такое приветствие не только в тиктоке, но и здесь. :ppp) Сегодня, сев работать над сайтом, я решил по нему пару раз пройтись. Открыв картинку в новой вкладке, выяснилось, что там иконка моего сайта не отображается! Проведя небольшой ресёрч, я обнаружил, что дело может быть в фавиконке, вернее сказать, в её отсутствии. До сегодняшнего дня в качестве иконки выступал файл icon.jpg. При указании оного через <link rel="icon" href="icon.jpg" type="image/jpeg"> в head каждого из HTML-файлов, он работал, но при открытии картинки (которая, вестимо, не имеет в себе никаких тегов) браузер обращается именно к favicon.ico. В итоге я организовал себе иконки с разными разрешениями: обычные для браузера, большие для девайсов Apple, большие для веб-приложений и классическая favicon.ico для всего остального. =) В конечном итоге в браузере она выглядит точно так же, но зато при открытии картинок будет иконка!! Правда, люди, которые захотят отсюда скачать картинку, вероятно, будут взбешены тем фактом, что я им пытаюсь подсунуть AVIF-файлы заместо привычных PNG и JPEG… но это уже совсем другая история.

Что там со структурой?

У меня на сайте реализована система архива; в конце этой страницы вы можете найти кучу ссылку на снапшоты сайта в разные дни. По устройству это было устроено незамысловато: /archive/2026/june/01. Казалось бы, идеально! Но, создав папку /july, стало понятно, что меня выводит из себя ситуация, когда в списке папок месяца идут не по порядку (угораздило букву l быть первее в алфавите!), поэтому решил, что названия папок должны начинаться с номера месяца. О решении не жалею, но, как и с обновлением иконки, пришлось пришлось для этого каждый файл в архиве обновлять. 🥲 Зато теперь, если кто-то зайдёт на гитхаб сайта, то структура будет более понятной и симпатичной. 💅

Поисковые роботы? Это вообще что?

Это роботы гугла и яндекса, которые в случае чего выводят ссылку на сайт в поиске. :> Чтобы они начали работать, нужно внести пару обновлений на сайте, на котором я арендовал хостинг — тут проблем не возникло. Зато спустя сутки я обнаружил, что Вебмастер (это яндексовский) считает, что у меня на сайте не настроено безопасное подключение, что просо не соответствовало действительности: у меня был сертификат от Let's Encrypt, и тот факт, что ваш браузер пустил вас сюда без предупреждения, является тому ярчайшим доказательством. Немного пораскинув мозгами и пошарившись по интерфейсу, стало понятно, что яндекс пытается добиться информации от http://workolai.com:80. Это незащищённое соединение, по которому у меня настроен редирект на защищённое. Кажется невероятной глупостью тот факт, что при добавлении домена в формате сайт.com яндекс по умолчанию склонен считать, что речь идёт ссылке с HTTP! Ну серьёзно, большой 26-й на дворе, рядовой пользователь через плашку с браузером об опасности соединения на такой сайт не проберётся. Вроде поставил галочку «Включить HTTPS», но стало неохота ждать, так что просто удалил workolai.com из Вебмастера и добавил https://workolai.com. Проверю завтра, какие жалобы будут уже на реальный сайт, а не на редирект. :p

С нашей конторой сами-знаете-кого всё понятно, но гугловский робот тоже дал о себе знать (пусть уже и не в таком возмущающем меня ключе). До переезда на VDS мой сайт находился на GitHub Pages — это, грубо говоря, хостинг гитхаба, который позволяет держать статичные и незамысловатые сайты доступными для всех в интернете забесплатно. Штука отличная, но проблема в том, что, во-первых, пользователи не смогут отправлять тебе формы (это настраивается отдельно и костылями), во-вторых, по умолчанию у тебя всё на домене github.io, в-третьих, все абсолютные пути ломаются, потому что, очевидно, ты владеешь своим репозиторием, а не корнем github.io. В общем, я переехал на арендованные для сайта домен и VDS. Но страничку для своего сайта в Google Search Console (панелька, через которую владельцы сайтов могут следить за выдачей сайта поисковым роботом гугла) я создавал впервые, когда сайт был ещё на GitHub Pages. Сегодня чекаю URL в GSC, и появляется следующая картина:

Скриншот с информацией о главной ссылке на сайт на GSC
Google Search Console

Понимаю, что для вас это какой-то ребус. Самая важная строчка здесь: Google-selected canonical: https://workolai.github.io/personal-site/. Прошлый домен с почти таким же содержанием попал в GSC раньше, и гугл видит, что он повторяет контент домена, который предлагается взять под контроль. Чтобы это исправить, нужно, по идее, добавить <link rel="canonical" href="https://workolai.com/"> и запросить индексирование потом у гугла. Проверим, сработает ли это.

Вообще, учитывая, на каком уровне у меня сайт, навряд ли поиск будет его кому-то выдавать. Но когда и если решат искать именно моё имя, этот сайт в выдаче будет очень кстати, и я бы хотел присматривать за этими поисковыми роботами с самого начала. 👽

Пора бы начать вспоминать, кто такой этот ваш CSS

Из нового: я обновил фоновый цвет сайта! Также я посмотрел урок про разные меры в CSS (px, rem, em, % и так далее), но не смог придумать кейс, где можно будет это применить. Завтра, думаю, обязательно этот кейс найдётся. ;) Ну а пока я пойду заменять ссылки в архиве с учётом обновления структуры. 😩

День 13: легендарное возвращение!

Дата публикации:

А что случилось?

Я начал отдыхать и в итоге проотдыхал целый месяц. xDD Зато 150 подписчиков в тиктоке набрал! Как бы то ни было, возвращаться надо (рано или поздно). Сейчас буду немного как слепой котёнок, правда, но да ладно! Для начала, хотелось бы кратко обозначить, что успело произойти за время отсутствия:

  • Я получил аттестат и подал заявление в колледж

    С получением всех результатов экзаменов мне выдали обычный синий аттестат, в котором все оценки были отличными. Синий он был в связи с тем, что я не подтвердил свои знания на ЕГЭ. Выпускной прошёл спокойно, в кафе я со всеми не ходил — пьяные лица не жалую и хорошо себя в их окружении чувствовать не могу. =) Но я прошёл медкомиссию для колледжа (бо-о-же, никому не пожелаю улицезреть эти очереди!) и подал заявление на поступление в местный агротехнический колледж через госуслуги, прикрепив справку о здоровье, СНИЛС, аттестат и фотку. Поступление будет ещё 26 дней идти, но я на данный момент первый по среднему баллу аттестата на интересующем меня направлении. Скоро нужно будет съездить и отдать им аттестат. ;)

    Автор сайта в белой футболке смотрит в камеру на белов фоне
    Моя будущее фото в студенческом билете (сделал его на камеру телефона и подправил с помощью ИИ)
  • Я начал постить вертикальный контент

    Я добавил пару друзей из реально жизни в свой тикток, и внезапно о канале узнала куча ребят из школы. Я очень рад их поддержке, но иногда неловко в связи с этим бывает. 🥲 По большей части я сейчас пощу лайфстайл, но хотелось бы перейти всё же на постинг по веб-разработке.

    TikTok-аккаунт Николая огрызкова
    Мой TikTok
  • Я продал ПК и периферию

    Я наконец-то смог продать его! За 77 000 ₽ у меня ушли ПК, монитор и мышка с клавиатурой. На полученные деньги я купил себе стол с подъёмным механизмом, штуки для кабель-менеджмента, кучу уходовой косметики и игры в стиме.

День 12: начало курса по CSS

Дата публикации:

А почему только день 12? Неужто отдыхал все остальные дни? 👀

Каюсь, друзья, отдыхал и бездельничал почти две недели! 😔✌️ Но сегодня возвращаюсь в строй с новой силой. Работать я буду с курсом по CSS на канале FreeCodeCamp от Дэйва Грэя, и идёт он (курс, не Дэйв) немногим больше 11 часов. Бо-о-оже, и что там 11 часов изучать можно?.. Как бы то ни было, разберусь, конечно, но масштаб внушительный на первый взгляд.

Выглядеть всё это будет немного странно поначалу, поэтому те правила, которые я добавляю, чтобы выполнить задачу с курса, я буду помещать в конец своего CSS-файла, а теги, на которые эти новичковские попытки в CSS, будут влиять, все будут аккуратно обёрнуты в класс experimental-css. Что ж, работаем!

Таинственный цвет проник на мой сайт вместе со странного размера текстом!

Дни 10—11: ЕГЭ, завершение работы с кнопками и формами и ещё немного лоска с помощью CSS

Дата публикации:

Так что там с ЕГЭ?

С ЕГЭ всё отлично!

Предмет Количество баллов
Русский язык 61
Математика (профильная) 58
Информатика 20*
* На момент написания статьи официального результата не было; но я сделал только 3 задания, и 20 — это самый лучший расклад. xD **
** Апдейт от : настоящий результат был 14 баллов 👽

У некоторых может возникнуть справедливый вопрос: и как же это всё отлично, если информатика де-факто не сдана, а по другим двум предметам тоже мало баллов? Дело в том, что я иду в колледж и не иду в вуз. Мне нужен только аттестат, так что я не переживаю. В колледжах не спрашивают баллы ЕГЭ. 😝 Дальше мне нужно будет получить медицинское заключение для колледжа — 3517 ₽, на минуточку! — после чего смогу подавать документы.

Больше кнопок и форм богу кнопок и форм!

Да… возвращаемся к ВОТ ЭТИМ ВОТ. 😩



Какой ваш любимый язык программирования?


Какие у вас любимые домашние питомцы?


Напишите о своём свэге

Бо-о-оже, это было ужасно! Пока запомнишь, как всё это работает, нужно будет это не один раз повторить. Впрочем, никто ничего не запоминает раз и навсегда; в пору интернета всегда есть возможность проверить и уточнить, верно? ;)

Обновление в CSS и немного в доступности

Сегодня я освоил max-width, line-height, padding и ещё некоторую красоту, которая позволила сделать сайт лучше. :> Текст больше не растягивается на всю страницу, а аккуратно располагается в центре; также он стал побольше, а межстрочный интервал приобрёл более удобочитаемый вид. 🌸

По доступности: прогнав свой сайт через Google Lighthouse, я понял, что в моментах, где я даю описание картинок и ссылки на страницы картинок в Pexels, люди со скринридерами получают набор ссылок, которые звучат абсолютно одинаково. (Они слышат что-то вроде «копирайт, [имя сделавших фотографию] на „ссылка: Pexels“». Получается какая-то каша: предлог «на» смешивается со словом «ссылка», а слово Pexels не даёт много информации.) Было придумано изящное и более корректное с точки зрения семантики решение: у тега <a> можно задавать название с помощью aria-label. Теперь человек будет слышать «копирайт, [имя сделавших фотографию]; „ссылка: Часовня тишины на Pexels“». Предлог «на» я убрал от людей без скринридеров, заменив его на длинное тире — выглядит оно даже более профессионально. B)

День 9: кнопки, формы и прочие ужасы, которые за один раз не запомнишь…

Дата публикации:

Кнопки и формы (будь они неладны!)

Всех приветствую! Хотелось бы выйти на режим ежедневных коммитов (мечтать не вредно! 😝), поэтому активно учусь дальше.

Сегодня, после наведения финального лоска в CSS — да, Джеминай мне всё предлагает и предлагает кучу симпатичного, а я всё не против и не против, — я наконец-то возвращаюсь к курсу от FreeCodeCamp по HTML! Следующая тема… о-о-ой, формы и кнопки… Я помню, как я был поражён количеству полученной информации, когда у меня была первая и менее публичная попытка в веб-разработку с курсом уже от BroCode. Надеюсь, в этот раз будет попроще. Хотя длина этого урока на таймлайне не вызывает доверия. 🥲 Попробуем?..

Важное примечание: все формы липовые и мне не приходят на данный момент!

Окей, чат, завтра мне на экзамен последний (конечно, я ничего не знаю и ничего не напишу), так что пойду уже спать. git push origin main и до завтра! 🍓

Дни 5—8: внезапная оптимизация картинок, первые шажки в CSS и ИИ

Дата публикации:

Доведения до ума раздела об истории развития сайта

Что? Да! Я решил, что такой раздел должен быть прямо на сайте с возможностью взглянуть, как всё выглядело в тот или иной день. Вы, вероятно, прямо с неё читаете данный текст. Здесь будут история по дням изучения и история в виде полноценных снапшотов — копий сайта за тот или иной день. Они находятся в самом низу. Внизу этих снапшотов была добавлена подпись о том, что это архивная версия сайта, и кнопки перехода от одного снапшота к следующему и предыдущему — Джеминай мне помог сделать это покрасивее.:>

Желание публиковаться на GitHub

Где-то 4 дня назад я решил, что было бы круто разместить свой сайт на гитхабе, потому что он позволяет опубликовать сайт бесплатно, даже не оплачивая никакого хостинга. После этого начинается самое интересное…

Во-первых, надо выбрать лицензию. ИИ предлагает как универсальный вариант лицензию от MIT, но я вспоминаю, что слышал легенды про лицензии GNU и восхищался. Погуглив, я решил, что буду брать AGPLv3. Она хороша тем, что любой человек, который решит работать с моим кодом, будет обязан опубликовать все свои изменения; иначе я буду иметь право его засудить. 🫦 Вот она: свобода кода любой ценой!

Но с такой лицензией я дважды задумался над картинками, которые были у меня на сайте, а именно шуточные скриншоты из GTA в контексте мест, которые мне бы хотелось посетить. Уж не знаю, мог бы я в будущем заинтересовать Rockstar, но, если я правильно понимаю, это одна из компаний с вредными юристами, поэтому я решил не рисковать и брать свободные для распространения фотографии с Pexels. Отлично, с интеллектуальной собственностью — будь оно проклята! — мы разобрались. Теперь можно просто вставить картинки, верно?.. Ну… не совсем.

Оптимизация размера изображений

Итак, загружать оригинал в 2 МиБ — это неприемлемо по нынешним веб-стандартам. Я поговорил об этом с Чатом — ChatGPT, — и тот предложил мне bash-скрипт для сжатия. И после этого я упал в кроличью нору… Я думал расписать подробно, как и насколько разные ИИ мне помогли в создании скрипта, но мне кажется, что это будет излишним. Больше всего мне помогли Джеминай и Клод; первую заготовку сделал Чат, Грок тоже внёс пару правок, но начал слишком быстро просить денег и ограничивая наш чат на 8 часов. Клод выглядел самым способным по коду, хотя Джеминай не отставал (по крайней мере в работе с этим конкретным bash-скриптом) и был разве что подружелюбнее. Но! С Клодом был интересный диалог:

— У меня опять горят глаза насчёт картинок. 🫦 Как это можно реализовать в нашем имеющемся скрипте? 🤔
— Нет. 😄

Серьёзно — сначала напиши хоть одну страницу. Ты не знаешь ещё какие размеры тебе нужны, как картинки будут вписаны в макет, какие брейкпоинты ты выберешь. Дописывать скрипт под размеры которые ты не знаешь — это работа вслепую.

Когда сделаешь первый макет и увидишь что картинка на мобильном занимает 480px, а на десктопе 1200px — вот тогда приходи, добавим за пять минут.

Впервые ИИ мне отказал! Дальше я всё же смог его убедить мне помочь, сказав, что у меня уже есть несколько страниц, но это было интересно. А отказ, если честно, по делу. Как бы то ни было, теперь у меня есть скрипт, который при первом запуске спрашивает, в где хранится директория img, а при втором уточняет, в какую поддиректорию загружать конкретные файлы. Дальше он спрашивает, насколько агрессивную стратегию сжатия использовать; после этого он делает своё дело, сохраняя в директорию /original оригинал изображения, а на директорию выше кидает 7 файлов (каждый меньше 250 КиБ): AVIF и WEBP для экранов смартфонов (640 px), для экранов маленьких ноутбуков и планшетов (1200 px) и для десктопа (1920 px) и JPEG (чтобы в случае чего загрузилось даже на древнейших девайсах), давая в терминале сразу вывод для HTML-файла.

Раз уж такое дело, неудобно будет не разместить прямо здесь эти картинки! (Заодно взгляну, как сделать так, чтобы картинки занимали не пол-экрана.)

Места, которые мне бы хотелось посетить:

  • Хельсинки

    Снежный вид на часовню тишины Камппи в Хельсинки, демонстрирующий современную архитектуру в зимней обстановке
    Хельсинки, Уусимаа, Финляндия
    © Ivan Dražić — Pexels
  • Берлин

    Трамваи на городской улице Берлина с заревом заката, демонстрирующие городскую жизнь и современную архитектуру
    Берлин, Германия
    © Niklas Jeromin — Pexels
  • Акихабара

    Яркая ночь в Акихабаре, Токио, с неоновыми огнями, рекламными щитами и уличной жизнью
    Акихабара, Токио, Япония
    © Şiyar AKBALIK — Pexels

Делюсь не без удовольствия кодом прямо здесь. 💫 (Надеюсь, меня не загрызут люди, которые, не знаю, стояли за созданием bash… кто знает, кто среди читателей будет? Как бы то ни было, это вайб-кодинг — не судите строго. 😝)

Открывайте и используйте на свой страх и риск! 👀
#!/bin/bash
set -euo pipefail

# Определяем, где физически лежит сам скрипт
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/.img_config"

echo "=== ULTIMATE Image Optimization Script (CLI Parallel Edition) ==="

# ==================== ФУНКЦИЯ НАСТРОЙКИ ПУТИ ====================
setup_base_dir() {
    echo "--- Настройка базовой директории сайта ---"
    echo "Введите абсолютный путь к папке 'img' вашего сайта."
    echo "Пример: /home/micu/Documents/My_site/img/"
    read -r -p "Путь: " user_path

    user_path="${user_path%/}"

    if [ ! -d "$user_path" ]; then
        echo "⚠️ Ошибка: Директория '$user_path' не существует!"
        read -r -p "Всё равно сохранить этот путь? (y/n): " confirm
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
            exit 1
        fi
    fi

    echo "$user_path" < "$CONFIG_FILE"
    echo "✓ Базовый путь успешно сохранен в $CONFIG_FILE"
}

# ==================== ОБРАБОТКА ФЛАГОВ (-c / --change-dir) ====================
if [ "${1:-}" = "-c" ] || [ "${1:-}" = "--change-dir" ]; then
    setup_base_dir
    echo "Перезапуск скрипта..."
    exit 0
fi

if [ ! -f "$CONFIG_FILE" ]; then
    echo "Похоже, это первый запуск скрипта!"
    setup_base_dir
fi

BASE_OUT_DIR=$(cat "$CONFIG_FILE")

# ==================== ВЫБОР ПОДДИРЕКТОРИИ (Поста/Дня) ====================
echo ""
echo "Текущий базовый путь: $BASE_OUT_DIR"
echo "Введите название целевой подпапки для картинок (например: day_1):"
read -r -p "Подпапка: " sub_dir

sub_dir="${sub_dir#/}"
sub_dir="${sub_dir%/}"

FINAL_DESTINATION="$BASE_OUT_DIR/$sub_dir"
mkdir -p "$FINAL_DESTINATION"

# ==================== ВЫБОР РЕЖИМА СЖАТИЯ ====================
echo ""
echo "Выберите целевой режим сжатия:"
echo "1) Hero / LCP          — очень строго (<180 KiB)"
echo "2) Content             — строго (<230 KiB)"
echo "3) Banner              — умеренно (<350 KiB)"
echo "4) Gallery             — максимально (<110 KiB)"
read -r -p "Введите номер (1-4): " mode

case $mode in
    1) TARGET_WEBP=184320; MIN_QUALITY=18 ;;
    2) TARGET_WEBP=235520; MIN_QUALITY=22 ;;
    3) TARGET_WEBP=358400; MIN_QUALITY=28 ;;
    4) TARGET_WEBP=112640; MIN_QUALITY=15 ;;
    *) echo "По умолчанию Content"; TARGET_WEBP=235520; MIN_QUALITY=22 ;;
esac

SIZES=(640 1200 1920)
shopt -s nullglob

# Экспортируем переменные, чтобы дочерние процессы их видели
export TARGET_WEBP MIN_QUALITY FINAL_DESTINATION sub_dir SCRIPT_DIR
export -a SIZES

# ==================== ФУНКЦИЯ СЖАТИЯ ОДНОГО РАЗМЕРА ====================
compress_size() {
    local work="$1" local out_webp="$2" local out_avif="$3"
    local target_w="$4" local target_h="$5"

    local avif_target=$(( TARGET_WEBP * 70 / 100 ))
    local current_w=$target_w local current_h=$target_h

    # WebP
    magick "$work" -resize "${target_w}x${target_h}" -quality 72 -define webp:method=6 -define webp:pass=10 "$out_webp"

    # Агрессивный цикл
    for pass in {1..8}; do
        [ -f "$out_webp" ] || break
        local current_size=$(stat -c %s "$out_webp")
        [ "$current_size" -le "$TARGET_WEBP" ] && break

        local quality=$(( 72 - pass * 9 ))
        [ "$quality" -lt "$MIN_QUALITY" ] && quality=$MIN_QUALITY
        current_w=$(( current_w * 90 / 100 ))
        current_h=$(( current_h * 90 / 100 ))

        if [ $(( current_w < current_h ? current_w : current_h )) -lt 320 ]; then
            magick "$work" -resize "${target_w}x${target_h}" -quality "$quality" -define webp:method=6 -define webp:pass=10 -define webp:alpha-quality=30 "$out_webp"
            break
        fi
        magick "$work" -resize "${current_w}x${current_h}" -quality "$quality" -define webp:method=6 -define webp:pass=10 -define webp:alpha-quality=30 "$out_webp"
    done

    # AVIF
    local final_w=$(magick identify -format "%w" "$out_webp" 2</dev/null || echo "$target_w")
    local final_h=$(magick identify -format "%h" "$out_webp" 2</dev/null || echo "$target_h")

    magick "$work" -strip -units PixelsPerInch -density 72 -resize "${final_w}x${final_h}" -quality 60 "$out_avif"
    if [ $(stat -c %s "$out_avif" 2</dev/null || echo 0) -gt "$avif_target" ]; then
        magick "$work" -strip -units PixelsPerInch -density 72 -resize "${final_w}x${final_h}" -quality 40 "$out_avif"
    fi
}
export -f compress_size

# ==================== ВОРКЕР: ОБРАБОТКА ОДНОГО ФАЙЛА ====================
process_single_image() {
    local input="$1"
    [ -e "$input" ] || return 0

    local name=$(basename "$input")
    local base="${name%.*}"
    local ext="${name##*.}"
    local clean_ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
    [ "$clean_ext" == "jpeg" ] && clean_ext="jpg"

    local img_dir="$FINAL_DESTINATION/$base"
    local img_originals_dir="$img_dir/original"
    mkdir -p "$img_originals_dir"

    # Создаем ИЗОЛИРОВАННУЮ временную папку для конкретно этого файла
    local tmp_work_dir=$(mktemp -d "$SCRIPT_DIR/.tmp_work_${base}_XXXXXX")
    local work="$tmp_work_dir/${base}_work.${ext}"

    # Перенаправляем вывод всего процесса в переменную, чтобы логи не перемешивались в кашу
    local log_output=""

    log_output+="\n=== Processing: $name ===\n"
    log_output+=" → Результаты полетели в: $img_dir\n"

    cp "$input" "$img_originals_dir/$name"
    cp "$input" "$work"

    local has_alpha=0
    if [[ "$clean_ext" == "png" ]]; then
        if magick identify -format "%[channels]" "$work" 2</dev/null | grep -q 'a'; then
            has_alpha=1
        fi
    fi

    if [ "$has_alpha" -eq 1 ]; then
        log_output+=" → Alpha PNG — предварительное сжатие...\n"
        pngquant --quality=40-85 --speed=1 --force --output "${work%.png}_tmp.png" "$work" && mv "${work%.png}_tmp.png" "$work" || true
        oxipng -o 6 --strip safe --quiet "$work" || true
    fi

    local src_w=$(magick identify -format "%w" "$work" 2</dev/null || echo 0)
    local src_h=$(magick identify -format "%h" "$work" 2</dev/null || echo 0)

    local max_created_w=0
    local max_created_h=0

    for size in "${SIZES[@]}"; do
        if [ "$size" -gt "$src_w" ] && [ "$size" -gt "$src_h" ]; then continue; fi

        if [ "$src_w" -ge "$src_h" ]; then
            local target_w=$size; local target_h=$(( src_h * size / src_w ))
        else
            local target_h=$size; local target_w=$(( src_w * size / src_h ))
        fi

        local out_webp="$img_dir/${base}-${size}.webp"
        local out_avif="$img_dir/${base}-${size}.avif"

        compress_size "$work" "$out_webp" "$out_avif" "$target_w" "$target_h"

        local final_w=$(magick identify -format "%w" "$out_webp" 2</dev/null || echo "$target_w")
        local final_h=$(magick identify -format "%h" "$out_webp" 2</dev/null || echo "$target_h")
        log_output+="      ✓ [${size}px] Созданы варианты WebP и AVIF [${final_w}x${final_h}]\n"

        max_created_w=$final_w
        max_created_h=$final_h
    done

    # Умный расчет размера фолбэка (Дубли удалены!)
    local fallback_w=$max_created_w
    local fallback_h=$max_created_h

    if [ "$max_created_w" -gt 1200 ] || [ "$max_created_h" -gt 1200 ]; then
        if [ "$src_w" -ge "$src_h" ]; then
            fallback_w=1200
            fallback_h=$(( src_h * 1200 / src_w ))
        else
            fallback_h=1200
            fallback_w=$(( src_w * 1200 / src_h ))
        fi
    fi

    local fallback_ext="jpg"
    [ "$has_alpha" -eq 1 ] && fallback_ext="png"

    local fallback_file="$img_dir/${base}-fallback.${fallback_ext}"

    if [ "$fallback_ext" == "png" ]; then
        magick "$work" -resize "${fallback_w}x${fallback_h}" "$fallback_file"
        log_output+="      ↳ Оптимизируем палитру уменьшенного PNG-фолбэка...\n"
        pngquant --quality=40-85 --speed=1 --force --output "${fallback_file%.png}_tmp.png" "$fallback_file" \
            && mv "${fallback_file%.png}_tmp.png" "$fallback_file" || true
        oxipng -o 6 --strip safe --quiet "$fallback_file" || true
    else
        magick "$work" -strip -units PixelsPerInch -density 72 \
            -resize "${fallback_w}x${fallback_h}" -quality 65 "$fallback_file"
    fi

    local fallback_final_size=$(stat -c %s "$fallback_file" 2</dev/null || echo 0)
    local pretty_size=$(numfmt --to=iec "$fallback_final_size")
    log_output+="      ✓ Создан дефолтный фолбэк: ${base}-fallback.${fallback_ext} [${fallback_w}x${fallback_h}] ($pretty_size)\n"

    # Удаляем оригинал из входящей папки и временную рабочую директорию
    rm -f "$input"
    rm -rf "$tmp_work_dir"

    # --- Подготовка списков существующих файлов для умного srcset ---
    local valid_avif=()
    local valid_webp=()
    for size in "${SIZES[@]}"; do
        [ -f "$img_dir/${base}-${size}.avif" ] && valid_avif+=("$size")
        [ -f "$img_dir/${base}-${size}.webp" ] && valid_webp+=("$size")
    done

    # Общая строка sizes для обоих тегов source
    local sizes_attr="sizes=\"(max-width: 640px) 100vw, (max-width: 1200px) 80vw, 1200px\""

    # Генерируем HTML блок в лог
    log_output+="\n → HTML для вашего файла:\n"
    log_output+="   <picture>\n"

    # Блок AVIF
    log_output+="     <source type=\"image/avif\" srcset=\"\n"
    local len_avif=${#valid_avif[@]}
    for ((i=0; i<len_avif; i++)); do
        local size=${valid_avif[i]}
        if [ $i -eq $((len_avif - 1)) ]; then
            # На последней итерации закрываем кавычку, переносим строку и пишем sizes
            log_output+="       img/${sub_dir}/${base}/${base}-${size}.avif ${size}w\"\n     $sizes_attr>\n"
        else
            log_output+="       img/${sub_dir}/${base}/${base}-${size}.avif ${size}w,\n"
        fi
    done

    # Блок WebP
    log_output+="     <source type=\"image/webp\" srcset=\"\n"
    local len_webp=${#valid_webp[@]}
    for ((i=0; i<len_webp; i++)); do
        local size=${valid_webp[i]}
        if [ $i -eq $((len_webp - 1)) ]; then
            # На последней итерации закрываем кавычку, переносим строку и пишем sizes
            log_output+="       img/${sub_dir}/${base}/${base}-${size}.webp ${size}w\"\n     $sizes_attr>\n"
        else
            log_output+="       img/${sub_dir}/${base}/${base}-${size}.webp ${size}w,\n"
        fi
    done

    # Блок IMG фолбэка
    log_output+="     <img src=\"img/${sub_dir}/${base}/${base}-fallback.${fallback_ext}\" width=\"$fallback_w\" height=\"$fallback_h\" alt=\"Опиши картинку здесь\" loading=\"lazy\">\n"
    log_output+="   </picture>\n"

    # Выплескиваем весь лог картинки атомарно, чтобы строки не перепутались
    echo -e "$log_output"
}
export -f process_single_image

# ==================== ДИСПЕТЧЕР ПОТОКОВ ====================
files=("$SCRIPT_DIR"/*.{jpg,jpeg,png,webp,avif,JPG,JPEG,PNG,WEBP,AVIF})

if [ ${#files[@]} -eq 0 ]; then
    echo "❌ Ошибка: В папке '$SCRIPT_DIR' не найдено файлов картинок для обработки!"
    exit 1
fi

echo "🚀 Запуск параллельной обработки (Лимит: 5 потоков)..."

MAX_JOBS=5

for input in "${files[@]}"; do
    [ -e "$input" ] || continue

    # Запускаем обработку одной картинки строго в фоновом режиме (&)
    process_single_image "$input" &

    # Считаем, сколько фоновых задач запущено прямо сейчас
    # Каждый раз, когда количество фоновых задач достигает максимума,
    # команда 'wait -n' приостанавливает цикл, ожидая завершения ЛЮБОЙ одной задачи.
    while [ "$(jobs -r | wc -l)" -ge "$MAX_JOBS" ]; do
        wait -n
    done
done

# Ждем завершения последних "хвостов" в очереди
wait

echo ""
echo "=== Все картинки обработаны параллельно и разложены по полочкам! ==="

                    

Первые шажки в CSS

Прочитав заголовок, можно подумать, что я разобрался с курсом от FreeCodeCamp по HTML, но не тут-то было! Пока я возился с ИИ со скриптом, Джеминай так и норовил мне надавать разных советов, что и как можно сделать симпатичнее; потом мне самому стало любопытно, как расстояние увеличить между ссылками в хедере, как сделать отступы у абзацев и всё-всё-всё… В итоге я теперь знаю, как отмечаются в селекторах классы, айди и теги в CSS-файле и некоторые свойства типа text-align и font-size. Не смейтесь, все с чего-то начинают! 🤓☝️

Что ж, на сегодня, кажется, всё. Пора публиковаться на гитхабе!

Так, уже этот абзац пишется после первого коммита. Выяснилось, что на Github Pages относительные пути от корня — типа /img/day_1/akihabara000/akihabara000-640.avif не работают! Это происходит потому, что гитхаб начинает рыскать не в корневой директории репозитория https://wokolai.github.io/personal-site, а в корневой директории моего сайта https://wokolai.github.io. Теперь стремительно меняю все абсолютные пути либо на голые относительные img/day_1/akihabara000/akihabara000-640.avif (идеально для сайтов в корневой папке), либо на ../../../../img/day_1/akihabara000/akihabara000-fallback.jpg (для файлов в архивной директории); выглядит последнее несуразно, по-моему, но зато работает и на реальном сервере, и на гитхабе. Также решил проверить сайт на валидаторе, как это рекомендовал делать парень с курса, который я прохожу, и нашёл у себя кучу ошибок, которые также стремительно исправил. Ну и ещё пару 💫 косметических изменений 💫 сделал, в частности убрал скругление на обводке моего диалога с Клодом — она вообще пока не нужна. Теперь, будучи более осознанным и подготовленным, делаю второй коммит!

День 4: таблицы и забота о здоровье

Дата публикации:

Таблицы

Всем привет! Я пережил экзамен по математике и только сейчас возвращаюсь к работе над сайтом и изучению HTML. В последний раз я остановился на таблицах. Не понял некоторые моменты и хочу остановиться на них поподробнее.

Покажу вам, какой ноут удалось взять на Авито за 80000 ₽. :D

ASUS TUF Gaming A15 FA507NV (модифицированный)
Процессор AMD Ryzen 5 7535HS
Встроенная видеокарта AMD Radeon 660M
Дискретная видеокарта NVIDIA GeForce RTX 4060 Laptop GPU (140 Вт)
Оперативная память DDR5 Kingston FURY Impact [KF556S40IB-16] 16 ГБ x 2
SSD WD PC SN740 SDDPNQD-512G-1002
ADATA LEGEND 860
Ёмкость аккумулятора 90 Вт*ч
Оставшаяся ёмкость аккумулятора 87 %

Если честно, доволен покупкой, как слон!

Забота о здоровье глаз и рук

Сегодня значительную часть дня я потратил на выстраивание стратегии «безвредного» досуга за ПК. Что я имею в виду? Ни для кого не секрет, что проводить сутки перед монитором не очень полезно. Это влияет и на здоровье глаз, и на здоровье рук, и на здоровье спины. Со спиной мне ещё предстоит разбираться (пока нет возможности реализовать всё это грамотно), но с глазами и руками, особенно дома, справиться можно.

Итак, начнём с глаз. Проведя небольшой ресёрч, выяснил, что корпоративному миру было даровано несколько советов:

  • Правило 20-20-20

    Незамысловатое предложение, данное в девяностые годы прошлого века оптометристом Джеффри Аншелом, гласит, что каждые 20 минут надо 20 секунд фокусировать взгляд на предмете, находящемся на расстоянии 20 футов (~6 метров). Чтобы его соблюдать, я из Flathub скачал Stretchly — утилиту, которая раз в заданные промежуток времени блокирует экран, чтобы человек мог сделать свои дела со глазами. Я настроил его так, чтобы раз в час также был перерыв на 5 минут — это необходимо для полноценной разминки.

  • Разминка для глаз

    Полная разминка для глаз, которую я теперь делаю раз в час работы за ноутом, указана далее. Как раз отточу навык создания таблиц!

    Моя разминка для глаз
    Упражнение Затрачиваемое время (в секундах)
    Пальминг
    Движения вверх-вниз
    Движения вправо-влево
    Круговые движения по часовой стрелке
    Круговые движения против часовой стрелки
    Поморгать
    Вблизь-вдаль (по 3 секунды)
    Закрыть глаза

    Делать её немного утомительно, но я исхожу из того, что она поможет замедлить негативную динамику по моей близорукости.

  • Расположение монитора (в моём контексте экрана ноутбука)

    Проведя также небольшой ресёрч, выяснилось, что верхняя ⅓ экрана должна быть на уровне глаз (или даже немного ниже). Немного поэкспериментировав с подставкой, получилось расположить ноутбук так, как надо.

  • Моргать

    Тут, пожалуй, самое сложное… В процессе работы перед экраном надо не забывать моргать. У меня это получается не очень хорошо, ведь я часто фокусируюсь на том, что делаю, а не на моргании. Мне есть куда расти!

Дальше можно поговорить о здоровье рук, и тут будет полезное предоставить некоторую историю. В период с по я любил проводить время в ритм-игре osu!, где нужно было одной рукой наводиться стилусом на кружочки и другой рукой жать две кнопки (всё это под отличную музыку). Как иногда бывает, я делал это очень неаккуратно: сильно прижимал запястья к столу, неправильно держал стилус (да, бывают разные хваты, и я выбрал один из самых неэргономичных). В итоге у меня начали болеть запястья, и я оставил игру. Потом боль почти полностью исчезла и достигла едва заметного плато, которое я не хочу ухудшать. Сейчас у меня есть сплит-клавиатура со встроенным трекболом и на крайние случаи вертикальная мышь. Эти девайсы сами по себе ставят запястья в куда более естественное положение, но, очевидно, чтобы сократить потенциальные разминки, рекомендуется делать разминки. Раз в час я обращаюсь к популярной в осу-сообществе видеоинструкции от Tykato Fitness. Удачи мне; надеюсь, не заброшу эту историю. :')

Дни 1, 2 и 3: первые потуги

В первые три дня изучение шло в формате «вставляю всё, что показывает чувак с гайда, на одну страницу и смотрю, что будет»; на третий день я решил сделать какую-никакую главную страницу, поэтому лучше всего просто взглянуть, как выглядел сайт в списке снапшотов, следующим после секции про обучение по дням.

Снапшоты сайта по дням