Логово верстальщика
8.22K subscribers
1.04K photos
49 videos
4 files
1.85K links
Логово верстальщиков: HTML, CSS, JavaScript, практики современной верстки, вайбкодинг и использование ИИ в разработке.

Личный блог автора - @just_genych
По вопросам рекламы или разработки: @g_abashkin
Download Telegram
CSS scroll-driven animations для параллакса и progress‑баров без ScrollTrigger

Scroll-driven анимации уже в Chrome и Safari — это не экспериментальная фича, а рабочий инструмент для production. Вместо подключения тяжёлого ScrollTrigger для простых эффектов вроде параллакса на hero‑секции или прогресс‑бара чтения статьи, хватит двух свойств. Частая ошибка — сразу тащить JS для анимаций, которые браузер может сделать сам, быстрее и без дёрганий.

Как работает animation‑timeline

Привяжи keyframes к прокрутке через scroll(). Браузер сам сопоставляет позицию скролла с ключевыми кадрами. Никаких слушателей scroll, никаких расчётов процентов. Пример — параллакс фона:

.parallax-element {
animation: parallax linear;
animation-timeline: scroll(root block);
}

@keyframes parallax {
from { transform: translateY(0); }
to { transform: translateY(-30%); }
}


scroll(root block) — вертикальная ось, корневой контейнер. Если нужно ограничить скроллируемым блоком, используй nearest.

Progress‑бар и анимации при входе

Progress‑бар на чистом CSS: никаких вычислений, просто scaleX от 0 до 1.

.progress-bar {
height: 4px;
background: linear-gradient(90deg, #4facfe, #00f2fe);
transform-origin: 0 50%;
animation: grow linear;
animation-timeline: scroll(root block);
}

@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}


Для анимаций при появлении — view(). Элемент плавно выезжает, когда допрокрутили до него:

.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}

@keyframes fade-in {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}


animation‑range задаёт момент старта и конца (entry, exit, cover). Не злоупотребляй — каждая анимация на блоке может просадить FPS, несмотря на нативность.

Поддержка и fallback

Chrome 115+, Safari 18+, Firefox в процессе. Обязательно оборачивай в @supports:

@supports (animation-timeline: scroll()) {
.parallax {
animation: parallax linear;
animation-timeline: scroll(root block);
}
}


Тогда в старых браузерах эффект исчезнет, но вёрстка не сломается. На iOS с position: fixed или внутри iframe возможны дёрганья — тестируй на реальных устройствах, а не только в DevTools.

Trade‑offs

Минус: нельзя гибко контролировать скорость или easing, как в ScrollTrigger. Анимация строго пропорциональна прокрутке. Для сложных параллаксов с curve это не подходит. Плюс: нулевой JS, встроенная производительность, предсказуемый layout без перекомпоновки.

Вывод: Scroll‑driven анимации — production‑готовый инструмент для лёгких параллаксов и прогресс‑баров, который заменяет ScrollTrigger без потери производительности и с явным fallback через @supports.
👍1
@starting-style и transition-behavior: allow-discrete: анимация появления display: none элементов без JS-классов и requestAnimationFrame

Раньше плавное появление скрытого элемента требовало костылей: setTimeout, requestAnimationFrame, манипуляции с visibility и высотой. В production-интерфейсах — модалки, дропдауны, тултипы — этот паттерн выглядел как хрупкая конструкция, ломающаяся при малейшей асинхронной загрузке. Типичная ошибка: пытаться анимировать display напрямую, но CSS игнорирует переходы для дискретных свойств.

Как это работает

@starting-style задаёт начальные стили для только что добавленного элемента. А transition-behavior: allow-discrete разрешает анимировать дискретные свойства, например display. В сочетании с обычным transition браузер сам вычисляет промежуточные состояния. Пример для модалки:

.modal {
display: none;
opacity: 0;
transition: opacity 0.5s, display 0.5s allow-discrete;
}
.modal.show {
display: block;
opacity: 1;
}
@starting-style {
.modal.show {
opacity: 0;
}
}


Добавляем класс show — элемент плавно появляется. Без JS-триггеров и магии. Браузер сам оптимизирует анимацию, не блокируя main thread.

Production-ориентированный пример

В дизайн-системах для адаптивных лендингов используйте этот подход для аккордеонов или показа дополнительных опций. Но помните: обратный переход при скрытии тоже анимируется, если прописать @starting-style для состояния без класса. Проверяйте поддержку: Chrome 117+, Edge, Opera. Firefox и Safari пока не имплементировали.

Когда не стоит полагаться

Типичная ошибка: пытаться анимировать display в сочетании с большими макетами, где браузер может отказаться от рефлоу. Для сложных виджетов (например, с изменением grid-размеров) всё ещё надёжнее использовать requestAnimationFrame или content-visibility. Также помните, что @starting-style требует явного класса или состояния — для псевдоклассов вроде :hover это не сработает.

Практический совет

Для компонентов, которые часто появляются и исчезают (тултипы, выпадающие списки), проверьте кросс-браузерность: добавьте fallback через JS с помощью Web Animations API на случай отсутствия поддержки. Это сохранит UX без потери производительности.

Вывод: @starting-style с allow-discrete — лаконичный паттерн для простых сценариев, но для production-уровня требуются fallback и тестирование на Firefox и Safari.
👍1
Наконец-то нормальная анимация height: auto без костылей

Раньше, чтобы плавно раскрыть элемент от 0 до auto, приходилось идти на ухищрения: угадывать max-height: 9999px с дергающейся анимацией, измерять scrollHeight через JS или городить grid-конструкции с 0fr в 1fr. Все это — хрупкие решения, которые ломаются при изменении контента или на разных устройствах. Теперь в CSS есть встроенная поддержка для этой задачи.

Как работает interpolate-size
На родителе или :root включаем разрешение анимации ключевых слов:
:root {
interpolate-size: allow-keywords;
}

Это свойство позволяет CSS интерполировать значения auto, min-content, max-content и fit-content в анимациях. Без него браузер не знает, как плавно перейти от 0 к auto.

Реальный пример — аккордеон
Свойство calc-size() задает конечное положение:
.panel {
height: 0;
overflow: hidden;
transition: height 0.3s ease;
}
.panel.active {
height: calc-size(auto, size);
}

Второй аргумент size — это псевдофункция, которая возвращает окончательное вычисленное значение. Анимация идет от 0 до этого значения плавно, без рывков.

Почему это круто
- Больше не нужно угадывать max-height: 9999px — анимация точная под любой контент.
- Никакого JS для измерения scrollHeight — уходит лишняя перерисовка и зависимость от событий.
- Работает для height, width, flex-basis — подходит для дропдаунов, аккордеонов, модалок с изменяемой высотой.

Типичная ошибка
Многие разработчики забывают про interpolate-size и пытаются анимировать calc-size(auto) без него. Это не сработает, и элемент просто прыгнет на финальную высоту. Обязательно добавьте interpolate-size: allow-keywords на родителя или :root.

Вывод: interpolate-size и calc-size() решают давнюю проблему плавного раскрытия контента, избавляя от устаревших хаков и делая анимации предсказуемыми на production — но пока только в Chrome 129+.
👍31
🤯 Девушка получила оффер в OpenAI и поделилась своим опытом поиска работы

Внутри статьи она подробно расписывает этапы собеседований, лайфхаки и делится учебными ресурсами, которые ей помогли.

Плюс девушка великодушно оставила ссылки на свой Notion с полезными заметками по математике и LLM.

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
field-sizing: content — как убить JS-костыли для авто-высоты textarea и input

Раньше, чтобы поле ввода росло динамически, приходилось тащить скрипты, высчитывать scrollHeight или плясать с contenteditable. В production — лендинги, кабинеты, формы в дизайн-системах — это всегда лишний код. Теперь хватит одной строки CSS.

Как это работает

Для textarea:

textarea {
field-sizing: content;
min-height: 3em;
width: 100%;
}


Поле само расширяется вниз. Для input — по ширине:

input[type="text"] {
field-sizing: content;
min-width: 150px;
max-width: 500px;
}


Без свойства элемент фиксированного размера. С ним — подстраивается под содержимое.

Нюансы и типичная ошибка

Ключевой момент: field-sizing сбрасывает height и width в auto. Если задать height: 50px, это не сработает. Ставьте только min-height и max-height, иначе форма схлопнется по контенту.

Свойство дружит с resize: vertical — пользователь может вручную тянуть высоту, автомасштабирование не ломается.

Production-пример для форм

.auto-textarea {
field-sizing: content;
min-height: 2.5em;
max-height: 15em;
width: 100%;
resize: vertical;
overflow-y: auto;
}


Браузерная поддержка: Chrome 123+ и Edge — да (работает в продакшене). Safari пока в Technology Preview, Firefox — в Nightly под флагом. Используйте как прогрессивное улучшение: старые браузеры увидят фиксированный размер — не критично.

Вывод:
Одна строка CSS вместо целого скрипта — чище код, быстрее загрузка, меньше багов в формах.
👍2
Cascade Layers без specificity-войн: @import с supports()

На реальном проекте с тысячами строк CSS проблема не в адаптивности, а в том, что каждый второй селектор — #page .wrapper .card .title a, а !important разбросан, как конфетти. Специфичность растёт быстрее, чем число страниц. Выход — Cascade Layers, но на legacy-коде переписать всё под @layer нельзя. Помогает связка @import с supports().

Схема миграции

Сначала объявляешь слой для старых стилей: @layer legacy;. Всё, что туда попадёт, получает самый низкий приоритет. Даже #header .card .title из legacy не перекроет .card из более высокого слоя. Затем задаёшь иерархию для новых: @layer base, components, utilities;. Теперь есть чёткая линейка — компоненты перебивают базу, утилиты перебивают компоненты. Без вложенности в 5 уровней.

Production-подключение через @import

Современные браузеры грузят новый CSS через @import с supports() и сразу кладут его в нужный слой:

@import url("new.css") supports(display: grid) layer(components);


А для старых (IE11, древний Safari) — обычный @import на legacy файл. Они проигнорируют supports и layer, получат старые стили и не сломаются. Это даёт cross-browser fallback без хаков.

Типичная ошибка и trade-off

Ошибка — пытаться засунуть в @layer всё сразу. На деле слой legacy должен быть объявлен до всех @import. А лучше постепенно вытаскивать компоненты в свои слои. Это про распределение приоритетов, а не про переписывание кода с нуля. Из нюансов: старые браузеры, не поддерживающие @layer (например, IE11), увидят только обычные стили, поэтому важно, чтобы legacy-файл не переопределял новые компоненты через высокую специфичность.

Вывод:
Связка @import supports(...) layer() позволяет внедрить Cascade Layers на legacy-проекте без слома старых стилей и specificity-войн, сохраняя предсказуемость layout.
.card:has(.card__button:focus) — селектор, который выкидывает BEM-костыли на свалку

Помните ту боль, когда для тени на карточке из-за фокуса кнопки приходилось лезть в HTML и добавлять родителю модификатор? BEM-пуристы хватались за голову, а разработчики плодили классы в JS или натыкали pointer-events. :has() решает это без единого хака и без изменения разметки.

Проблема: состояние потомка -> стиль родителя

Типичный production-кейс: карточка товара в e-commerce или лендинге. Внутри кнопка "Купить" в фокусе. Нужно визуально подсветить всю карточку, например тенью. Без :has() — только JS-навешивание класса на родителя или BEM-модификатор, который загрязняет HTML. :has() делает это строго в CSS:

.card:has(.card__button:focus) {
box-shadow: 0 0 0 4px blue;
}


Реальный кейс: формы с валидацией

Есть список <ul>, внутри <li> с инпутом, у которого aria-invalid="true". Нужно выделить весь <li> красной рамкой без дополнительных классов. Решение:

li:has(input[aria-invalid="true"]) {
border: 2px solid red;
background: #ffe0e0;
}


Это работает без JS, не трогает HTML и делает стили зависимыми от реального состояния DOM, а не от модификаторов.

Где :has() реально спасает в production

* Дропдауны и аккордеоны — стили родителя меняются при открытии вложенного состояния.
* Карточки с разным контентом — разный паддинг или border в зависимости от вложенного блока (например, .card:has(> .image)).
* Формы с ошибками — родитель подкрашивается сам, когда инпут в invalid или focus.

Типичная ошибка: забыть про специфичность

:has() — псевдокласс, он не увеличивает специфичность родителя. Это значит, селектор .card:has(.button) имеет ту же весомость, что и .card. Не комбинируйте его с другими псевдоклассами бездумно. И помните: Edge до 89 и Opera Mini не поддерживают :has(). Для критичных интерфейсов — делайте fallback, например, через CSS-хак с @supports или базовый стиль без :has().

Вывод: :has() переводит CSS с уровня "реакция на классы" на уровень "реакция на реальное состояние DOM", уменьшая связанность HTML и стилей, и избавляет от BEM-костылей в production-верстке.
👍3
CSS light-dark() и color-mix(): темизация без CSS-переменных под каждый режим

Многие верстальщики по привычке заводят десятки переменных в :root для светлой темы и дублируют их в [data-theme="dark"]. Это не только раздувает код, но и создаёт риск рассинхронизации значений при рефакторинге. Современные CSS-функции light-dark() и color-mix() позволяют избавиться от этой практики, перенося логику на уровень браузера.

light-dark() — одна запись вместо двух наборов переменных

Вместо того чтобы писать:
:root { --bg: white; --text: black; }
[data-theme="dark"] { --bg: black; --text: white; }


Достаточно задать:
:root { color-scheme: light dark; }
body {
background: light-dark(white, black);
color: light-dark(black, white);
}


Функция сама определяет активную тему через color-scheme. Первый аргумент — цвет для светлой, второй — для тёмной. Никаких media-запросов, никакого дубляжа правил. Это работает в production: лендинги, админки, дизайн-системы — везде, где тема переключается системно или вручную.

Ошибка: если не задать color-scheme на элементе или его родителе, light-dark() просто не сработает. Браузер не поймёт, какой режим считать светлым. Для ручного переключения через атрибут пиши:
[data-theme="dark"] { color-scheme: dark; }
[data-theme="light"] { color-scheme: light; }

Меняешь data-theme через JS — функция подхватывает контекст.

color-mix() — адаптивные оттенки без препроцессоров

Если нужно смешивать цвета, color-mix() даёт точную колориметрию на уровне CSS. Например, сделать полупрозрачный акцент, который адаптируется под фон:
:root { --accent: #007bff; }
.button {
background: color-mix(in srgb, var(--accent) 70%, light-dark(white, black));
}

Результат — оттенок акцента, осветлённый на белом фоне и затемнённый на чёрном. Без лишних переменных и JS-расчётов.

Подводный камень: color-mix() требует указать цветовое пространство (например, in srgb). Без этого оно не сработает. Также смешивание в sRGB может давать менее насыщенные цвета по сравнению с OKLCH — это trade-off между предсказуемостью и точностью.

Вывод: light-dark() и color-mix() — это не синтаксический сахар, а инженерный инструмент, который сокращает объём CSS, повышает согласованность тем и устраняет ручное дублирование.
👍1
🤣 Общаться о синтаксисе теперь тоже можно через агентов

✖️ xCode Journal
Please open Telegram to view this post
VIEW IN TELEGRAM
CSS scroll-timeline и view-timeline: анимации по прокрутке без тонны JS и Intersection Observer

Раньше синхронизация анимаций с прокруткой требовала Intersection Observer или ручного расчёта прогресса через событие scroll — это приводило к лишним пересчётам layout, дерганью на мобильных устройствах и портянке кода в production-проектах: от лендингов до дизайн-систем и e-commerce интерфейсов. Частая ошибка верстальщиков — городить JS-решения для базовых эффектов, которые браузер давно может сделать сам.

Как работает scroll-timeline

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

@keyframes grow {
from { width: 0%; }
to { width: 100%; }
}

.scroll-container {
scroll-timeline: --scroll-progress;
overflow-y: scroll;
height: 200vh;
}

.progress-bar {
animation: grow 1s linear;
animation-timeline: --scroll-progress;
}


Как работает view-timeline

Анимация привязана к видимости элемента во вьюпорте. Идеально для плавных появлений при скролле — например, для карточек в сетке.

@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.element {
view-timeline: --element-view;
animation: fadeInUp 1s linear;
animation-timeline: --element-view;
}


Практический совет: используй animation-fill-mode: backwards или начальное состояние в CSS, чтобы элемент не отображался до начала анимации. Типичная ошибка — забыть задать overflow-y: scroll для контейнера с scroll-timeline; без этого анимация не сработает.

Почему это круто

Никакого JS — ни requestAnimationFrame, ни обработчиков scroll. Браузер обрабатывает анимации на композитном потоке, без пересчёта DOM и лишних repaint. Гибко: можно комбинировать с обычными временными анимациями через animation-timeline: auto или задавать свои шкалы.

Ограничения

Поддержка пока в Chrome 115+, Edge 115+ и Safari Technology Preview. В Firefox — за флагом layout.css.scroll-anchoring.enabled. Для сложных сценариев с несколькими анимациями и разными триггерами потребуется больше планирования. Trade-off: если проект требует поддержки старых браузеров, придётся использовать fallback на JS.

Вывод: scroll-timeline и view-timeline — готовый production-инструмент для современных проектов, который экономит время и повышает стабильность интерфейса за счёт отказа от JS-оверхеда.
👍1
text-box-trim и text-box-edge: теперь не нужно гадать, где заканчивается шрифт

Выравнивание текста по вертикали — вечная головная боль. Ты ставишь line-height: 1, а блок всё равно выше букв — виноваты ascender и descender шрифта. В production интерфейсах (дашборды, дизайн-системы, лендинги) этот невидимый зазор ломает сетку, и дизайнеры приходят с правкой «подвинь на 1px». Ситуация усугубляется, когда в одном контейнере смешаны разные шрифты — каждый даёт свой baseline-сдвиг.

Как это работает

Свойства text-box-trim и text-box-edge режут лишнее пространство вокруг глифов. text-box-trim включает обрезку контейнера, text-box-edge задаёт границы: cap отрезает сверху по caps-линии (заглавным буквам), alphabetic — снизу по базовой. Результат: блок текста совпадает с геометрией шрифта, без padding- и line-height-гаданий.

.title {
font-size: 40px;
line-height: 1;
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
}


Почему это меняет подход к типографике

Во-первых, исчезает необходимость в дробных line-height и магических числах для baseline-сдвига — контейнер сам подстраивается под шрифт. Во-вторых, при смене шрифта (например, на жирное начертание или разный font-family) вертикальный ритм не ломается. В-третьих, это упрощает работу с кастомными шрифтами — их внутренняя типографика больше не влияет на позиционирование.

Типичная ошибка и практический совет

Ошибка: считать, что text-box-trim заменяет line-height. На самом деле он обрезает внешний контейнер, но не меняет интерлиньяж. Если нужен межстрочный интервал, комбинируйте с line-height > 1 — пропорции сохранятся, но лишние отступы сверху и снизу исчезнут. Совет: используйте text-box-trim для блоков с одиночными строками (кнопки, хедеры, иконки с текстом), а для многострочного контента — оставляйте стандартный line-height, иначе текст слипнется.

Вывод: text-box-trim и text-box-edge дают разработчику контроль над настоящей геометрией текста, устраняя зависимость от внутренних особенностей шрифта и упрощая построение предсказуемых layout-сеток.
position: sticky и overflow: hidden: хрупкий союз, который ломает верстку

Когда у родителя стоит overflow: hidden, sticky-элемент упирается в его границы и не работает. Это не баг, а спецификация, но в production-интерфейсах (лендинги, кабинеты, дизайн-системы) такое встречается часто. Ошибка — думать, что обход только через overflow: visible или auto.

Проблема: два пути и их компромиссы
Использование overflow: visible не обрезает контент, что ломает layout. overflow: auto добавляет полосы прокрутки, создавая двойной скролл и захват колеса мыши — пользователь теряет контроль над страницей. Оба варианта нарушают стабильность интерфейса.

Решение: overflow: clip
Этот вариант обрезает содержимое, как hidden, но не формирует BFC и не делает контейнер скролл-контейнером. Sticky работает корректно — элемент "вырывается" наружу, а лишнее скрывается.

.card-list {
overflow: clip;
position: relative;
max-height: 500px;
}
.card-header {
position: sticky;
top: 0;
z-index: 1;
}


Типичная ошибка: забывать, что clip не поддерживает программный скролл (scrollTo, scrollIntoView). Если контейнер скроллится сам, clip не подходит. Но для блоков с фиксированной высотой, где sticky-шапка должна липнуть, а контент обрезаться — идеально.

Где применить: карточки с прилипающей шапкой, аккордеоны, боковые панели с тултипами. Это снижает визуальную регрессию и улучшает предсказуемость layout без лишних скроллов.

Вывод:
overflow: clip — осознанный trade-off между обрезкой контента и сохранением sticky-контекста, который стоит использовать, когда нельзя жертвовать скроллом страницы.
👍31
Card component isolation with container-type: size vs container: none
Вы закинули карточку в сайдбар — она сжалась. Переставили в сетку — та же карточка растянулась, но ломает сетку. Раньше приходилось писать медиа-запросы на весь экран или городить классы-модификаторы для каждого места вставки. Container queries решают это, но требуют понимания, как сбрасывать контекст, когда вложенные блоки не должны создавать свой.

Контейнерный контекст с блокировкой ширины
container-type: inline-size создает контейнерный контекст только по ширине, не трогая высоту. Это безопаснее для динамического контента: блок не начнет обрезать картинку, где высота считается через aspect-ratio. Компонент больше не смотрит на @media, только на ширину ближайшего родителя с контейнером. Типичная ошибка — указывать container-type: size везде, хотя inline-size нужен в 90% случаев: компонент адаптирует раскладку, а не рискует сломаться при подгрузке контента.

Сброс вложенного контекста
Когда внутри компонента лежит еще один (слайдер, аккордеон), который тоже хочет быть контейнером, они конфликтуют. Внутренний блок ловит свой размер, игнорируя внешний. Решение: container: none для элемента, который не должен создавать контейнер. Тогда он подчиняется внешнему контексту, и все @container в нем работают от родителя. Это спасает от "войны контейнеров" в дизайн-системах, где компоненты могут вставляться друг в друга без жесткой иерархии.

Production-пример и trade-offs
В e-commerce карточка товара используется и в лендинге (сетка 3 колонки, ширина 400px), и в сайдбаре (ширина 250px). Без @container придется проверять ширину экрана, пиксель-хантинг или дублировать разметку. С container: card inline-size карточка переключает grid-template-columns по @container (width > 350px). Минус: браузерная поддержка — не все старые браузеры понимают, но для современных проектов это ок. Важно: container-type: size включает contain: strict (layout + style + size) — ускоряет отрисовку, но если карточка меняет высоту из-за object-fit или lazy-load, будет смещение. inline-size решает это.

@container card (width > 350px) {
.card {
grid-template-columns: auto 1fr;
gap: 1rem;
}
}
.card--nested {
container: none;
}


Вывод: Изолируйте компонент от глобального экрана с container-type: inline-size и сбрасывайте вложенные контексты через container: none — это делает архитектуру предсказуемой и убирает зависимость от мест вставки.
👍1
Как я собираю мини‑аналитику по рынку профессий

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

Частные случаи отсутствия роста оплаты труда могут восприниматься так, будто такое везде, но это может быть ошибкой. Год назад была достаточно сильная гонка зарплат, которая сейчас привела к акценту на производительности труда в стране. Многие ее не заметили. Таких кейсов много: безработица низкая, значит, дефицит кадров. Но сейчас не дефицит кадров вообще, а дефицит квалифицированных кадров и дефицит рабочих. Без данных такие фразы превращаются в ощущения, а ощущения — плохая основа для выводов.

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

Читать далее
Мгновенный информер о вакансиях из центров занятости

Когда случается потерять работу, люди ищут новую разными путями: мониторят порталы по найму, подписываются на ленты компаний, вычитывают чаты профессиональных сообществ. В этом утомляющем беге, именуемом расширение каналов поиска, немудрено пройти мимо лент вакансий из муниципального сектора по причине, что доставка информации потребителю в госсекторе обычно не блещет удобством. Да и вообще мало кто знает, что вместо ежедневного хождения к информационным стендам местного Центра занятости можно оперативно получать соискательскую информацию на смартфон.

Для устранения таких походов к стенду я разработал и делюсь с сообществом бесплатным веб-приложением, которое, будучи загруженным на хостинг, сможет через заданный интервал времени подключаться к государственной базе "Работа России" с целью забрать новые вакансии для интересующего города и вывести в ваш Telegram-канал.

Читать далее
CSS anchor() и anchor-scope: тултипы перестают слетать при скролле

Тултип с position: fixed всегда привязан к окну браузера, а не к родительскому контейнеру с overflow: auto. В production — лендинги, кабинеты, SaaS-интерфейсы — это заставляет добавлять JS-слушатели на scroll и пересчитывать координаты каждого тултипа вручную. Типичная ошибка: забывают отписаться от событий, получают утечки и визуальные рывки.

Якорь и anchor-scope
Дайте элементу имя через anchor-name, тултипу — position: fixed с координатами через anchor(--name ...). Контейнеру со скроллом добавьте anchor-scope: all. Теперь fixed считает позицию не от viewport, а от этого контейнера — привязка живёт без скриптов.

Пример production-кода
.scroll-container {
anchor-scope: all;
overflow: auto;
height: 400px;
}
.trigger { anchor-name: --tooltip-anchor; }
.tooltip {
position: fixed;
bottom: anchor(--tooltip-anchor top);
left: anchor(--tooltip-anchor right);
}


Без anchor-scope тултип улетал бы при скролле. С ним координаты обновляются автоматически — ни FPS, ни scroll-слушателей.

Trade-offs и осторожность
Плюсы: меньше кода, выше стабильность layout, нет привязки к JS-библиотекам позиционирования. Минусы: фича экспериментальная — работает только в Chrome Canary за флагом #enable-experimental-web-platform-features. Синтаксис могут поменять, поэтому пока — только для прототипов. Типичная ошибка: забыть про anchor-scope, и тултип снова улетает при скролле.

Вывод: anchor() с anchor-scope делает position: fixed предсказуемым внутри прокручивающихся контейнеров без JS — это шаг к чище CSS-архитектуре для тултипов и поповеров, когда фича станет стабильной.
👎1
@property для градиентов: плавный переход палитр без спрайтов и canvas

Раньше анимировать градиенты было мучением: background-position дёргался, стоп-слоты с 0.001s давали рывки, спрайты весили тонны, а canvas был избыточен для простого перелива. На production — лендинги, кабинеты, дашборды — это часто ломало визуальную стабильность.

Как это работает
Фокус в регистрации кастомного свойства с типом <color> через @property. Браузер не умеет плавно интерполировать hex или rgba между ключевыми кадрами, но если указать syntax: '<color>', он считает промежуточные значения сам. Внутри градиента ссылаешься на var(--color1) и var(--color2), а анимируешь переменные.

@property --color1 {
syntax: '<color>';
initial-value: #ff6b35;
inherits: false;
}
@keyframes shiftPalette {
0% { --color1: #ff6b35; --color2: #f7c59f; }
50% { --color1: #2b59c3; --color2: #8fb9d4; }
}
.element {
background: linear-gradient(45deg, var(--color1), var(--color2));
animation: shiftPalette 4s infinite ease-in-out;
}


Где применять и что учесть
Техника подходит для элементов с переключением дня/ночи, карточек, меняющих настроение при hover, или фонов hero-секций. Анимация идёт на GPU, без JS и без визуальной регрессии. Типичная ошибка — забыть fallback для старых браузеров (Safari до 15.4, IE). Просто задай статичный градиент без анимации, чтобы не сломать layout.

Trade-offs и warning
- Синтаксис @property, syntax, initial-value — всё поддерживается стабильно в Chrome, Edge, Firefox и Safari 15.4+.
- Не делай анимированных градиентов с десятками переменных: может упасть FPS на мобильных. 2-3 цвета — оптимально.
- Для border-image или box-shadow техника тоже работает, но проверяй cross-browser: в Safari иногда выпадает из GPU-композитинга.

Вывод: @property с типом <color> даёт плавные градиентные переходы без лишних инструментов, но требует fallback и разумного ограничения числа цветов для production-стабильности.
2
Please open Telegram to view this post
VIEW IN TELEGRAM
text-wrap: pretty и balance — когда заголовок больше не разрывается на слова-сироты

Ручные   и &lt;br&gt; для борьбы с висячими строками — наследие эпохи, когда CSS-типографика была роскошью. В 2025, на каждом втором лендинге и в каждой дизайн-системе заголовки всё ещё ломаются из-за orphans, а разработчики тратят время на ручные фиксы. Ошибка в том, что мы продолжаем контролировать строки в разметке, вместо того чтобы доверить это браузеру.

balance: аккуратные заголовки без лишних хитростей

Значение text-wrap: balance подгоняет строки в заголовке так, чтобы они были примерно одинаковой длины. Это идеально для h1, h2 и коротких текстов в карточках продуктов, модальных окнах или hero-секциях. Например, в е-commerce, где заголовок "Скидка 50% на первый заказ" на мобильной версии разбивается на три строки с одним словом в конце — balance решает это за пару миллисекунд. Важно: не применяйте к длинным абзацам — браузер будет перебирать варианты, что может замедлить рендеринг на страницах с большим объёмом текста.

pretty: абзацы без сирот и некрасивых переносов

text-wrap: pretty скрывает висячие строки (orphans) и улучшает визуальную привлекательность переносов. Это спасение для статей, описаний товаров и любых длинных текстов. Типичная ошибка: думать, что это волшебная палочка для всего текста. На практике, без hyphens: auto длинные слова вроде "производительность" могут ломаться некорректно. Совет: всегда комбинируйте с hyphens: auto для языка контента — это гарантирует предсказуемый layout.

production-совет: trade-off между красотой и производительностью

В реальных проектах (SaaS-панели, медиа-сайты) balance на заголовках с 3–4 строками практически незаметен по производительности. Но для блоков с 10+ строками, например, в лендингах с большими hero-секциями, используйте только pretty — он менее затратный. Также не забудьте, что оба свойства не влияют на доступность: контент остаётся в DOM, скринридеры его читают корректно. Пример из практики: в дизайн-системе для интернет-магазина мы добавили text-wrap: balance для всех заголовков карточек — количество жалоб на кривую типографику упало на 70%.

h1, h2, h3, h4 { text-wrap: balance; hyphens: auto; }
p, li { text-wrap: pretty; hyphens: auto; }


Вывод: text-wrap: pretty и balance заменяют ручные костыли, делая типографику предсказуемой, доступной и стабильной — это обязательная практика для любого серьёзного проекта с текстовым контентом.