Разбираем Reflect API и его основные методы для чтения, изменения, проверки и удаления свойств, вызова функций, создания экземпляров, работы с дескрипторами и получения собственных ключей объекта. Reflect применяется для метапрограммирования, низкоуровневой работы с объектами и реализации Proxy-логики с явным результатом операций.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤7🔥7
scheduler.yield() — разбиваем долгую JS-задачу, чтобы не блокировать интерфейс!
Длительная синхронная работа на
Классический способ разбить такую работу — планировать продолжение через
Это принципиально отличается от
Делать
Поддержка
🔥
📣 Code Ready | #практика
Длительная синхронная работа на
main thread блокирует выполнение других задач. Даже обычный цикл может превратиться в длительную задачу, если обработка элементов достаточно тяжёлая:function processItems(items) {
for (const item of items) {
renderItem(item);
}
}Классический способ разбить такую работу — планировать продолжение через
setTimeout(). В современных браузерах для добровольной передачи управления планировщику есть более специализированный механизм — scheduler.yield():async function processItems(items) {
for (let i = 0; i < items.length; i++) {
renderItem(items[i]);
if ((i + 1) % 100 === 0) {
await scheduler.yield();
}
}
}scheduler.yield() приостанавливает текущую async-функцию и планирует её продолжение как отдельную задачу, давая браузеру возможность выполнить другую ожидающую работу на main thread:await scheduler.yield();
// выполнение продолжится позже
Это принципиально отличается от
await Promise.resolve(): resolved Promise продолжает выполнение через microtask queue и не обеспечивает полноценную передачу управления event loop для выполнения следующей задачи:await Promise.resolve();
// продолжение выполняется как microtask
Делать
yield() после каждой итерации не стоит: каждая передача управления создаёт дополнительные накладные расходы. Размер порции работы зависит от стоимости конкретных операций, поэтому 100 или 200 итераций — лишь пример, а не универсальное значение.if ((i + 1) % CHUNK_SIZE === 0) {
await yieldToMain();
}Поддержка
scheduler.yield() пока не универсальна, поэтому стоит предусмотреть запасной вариант:function yieldToMain() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}scheduler.yield() полезен для кооперативного планирования длительной работы на main thread: обработки больших наборов данных и постепенного обновления сложного UI. Для тяжёлых вычислений, интенсивно использующих процессор, Worker остаётся предпочтительным решением: yield() не переносит вычисления с main thread, а только разбивает их выполнение во времени.Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤7🔥6
В этой статье:
• Разберётесь, почему Flexbox и Grid не гарантируют визуально идеальное выравнивание;• Узнаете, как метрики шрифтов и высота строки влияют на положение текста;• Посмотрите, почему иконки рядом с текстом часто «съезжают» и как это исправлять на практике.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥9🤝5
::file-selector-button — псевдоэлемент, представляющий кнопку внутри поля <input type="file">. Позволяет стилизовать её так же, как любую кнопку интерфейса: менять фон, бордеры, скругления, состояния наведения/фокуса и другие стили.С его помощью можно настроить:
• background — цвет фона;
• color — цвет текста;
• border — рамку кнопки;
• padding — внутренние отступы;
• font — оформление текста.
Для лучшей доступности обеспечьте заметные стили фокуса (
outline, outline-offset).Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤7🔥4
Stacking context — управление слоями интерфейса через CSS-контексты!
Например,
Если
Stacking context создаётся не только через
В реальных проектах чаще всего проблемы возникают у модальных окон, dropdown, popover и tooltip-компонентов, когда один из родителей создаёт собственный stacking context:
В такой ситуации увеличение
Отдельный случай —
Теперь
Для глобальных интерфейсных слоёв используют отдельный DOM-слот вне основного дерева приложения. Такой подход лежит в основе portal-механизма современных UI-библиотек:
Также необходимо учитывать, что не все проблемы перекрытия связаны со stacking context. Например,
При отладке проблем со слоями необходимо проверять всю цепочку родителей: наличие stacking context, ограничения
🔥
📣 Code Ready | #практика
z-index не является глобальной системой нумерации слоёв страницы. Браузер строит дерево stacking context, где каждый контекст отрисовывается независимо. Значение z-index сравнивается только внутри текущего контекста и не позволяет дочернему элементу напрямую влиять на соседние контексты.Например,
z-index: 999999 у вложенного элемента не гарантирует, что он окажется выше элемента с меньшим значением z-index, если родительский stacking context находится ниже в порядке отрисовки:.card {
position: relative;
z-index: 1;
}
.modal {
position: absolute;
z-index: 999999;
}
.header {
position: relative;
z-index: 10;
}Если
.modal находится внутри .card, браузер сначала сравнивает .card и .header в родительском stacking context, а уже затем учитывает внутренние слои .card. Высокий z-index дочернего элемента не может изменить положение родительского контекста.Stacking context создаётся не только через
position и z-index. Многие CSS-свойства меняют модель композиции слоёв и создают отдельный контекст наложения:.component {
opacity: 0.99;
transform: translateZ(0);
filter: blur(5px);
isolation: isolate;
contain: paint;
}В реальных проектах чаще всего проблемы возникают у модальных окон, dropdown, popover и tooltip-компонентов, когда один из родителей создаёт собственный stacking context:
.layout {
transform: translate3d(0, 0, 0);
}
.dropdown {
position: absolute;
z-index: 999999;
}В такой ситуации увеличение
z-index не помогает, потому что элемент остаётся внутри ограниченного контекста .layout.Отдельный случай —
position: fixed. Обычно такой элемент привязан к viewport, но некоторые свойства родителей изменяют его containing block:.page {
transform: translateZ(0);
}
.modal {
position: fixed;
inset: 0;
z-index: 10000;
}Теперь
modal рассчитывает позицию относительно .page, а не viewport. Поэтому проблема решается не изменением z-index, а изменением структуры DOM.Для глобальных интерфейсных слоёв используют отдельный DOM-слот вне основного дерева приложения. Такой подход лежит в основе portal-механизма современных UI-библиотек:
<body>
<div id="app"></div>
<div id="overlay-root"></div>
</body>
Также необходимо учитывать, что не все проблемы перекрытия связаны со stacking context. Например,
overflow: hidden может обрезать дочерний элемент независимо от его z-index:.container {
overflow: hidden;
}
.dropdown {
position: absolute;
z-index: 1000;
}При отладке проблем со слоями необходимо проверять всю цепочку родителей: наличие stacking context, ограничения
overflow, изменённый containing block и фактическое расположение элементов в DOM.z-index — это только один параметр системы композиции браузера. В сложных интерфейсах реальный порядок отображения определяется структурой stacking context.Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥5🤝4❤1
This media is not supported in your browser
VIEW IN TELEGRAM
В репозитории собраны материалы по всему циклу разработки: от проектирования архитектуры и базы данных до создания REST API, frontend-приложения на React, авторизации, Docker-развертывания и подготовки технической документации. Отличный материал для изучения разработки.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍7❤5
Псевдокласс
:checked в CSS применяется к отмеченным элементам формы — например, checkbox и radio. Он позволяет менять их оформление в зависимости от текущего состояния.Примеры использования:
• менять цвет выбранного элемента;
• создавать кастомные чекбоксы и радиокнопки;
• стилизовать связанные элементы через CSS-селекторы;
• создавать простые переключатели без JavaScript.
По задумке должен работать и с
<option>, но поскольку выпадающий список сильно отличается от системы к системе и от браузера к браузеру, то пока работает только в браузере Chrome на Windows.Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍11🤝3🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Сайт с большой подборкой статей, руководств и справочных материалов для веб-разработчиков. Основное внимание уделено HTML, CSS, JavaScript и современной вёрстке — от базовых принципов до практических приёмов, которые используются в проектах. На сайте можно найти материалы по Flexbox и Grid, адаптивной вёрстке, типографике, доступности, оптимизации сайтов, работе с изображениями, CSS-анимациям и др.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤11🤝3👍2😁1
This media is not supported in your browser
VIEW IN TELEGRAM
В репозитории собраны вопросы по CSS и CSS-фреймворкам, которые помогают проверить не только знание синтаксиса, но и понимание более сложных нюансов верстки и стилизации. Удобно использовать для самопроверки и поиска тем, которые стоит повторить перед техническим интервью.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍7🤝4
Не используй overflow: hidden, если нужно только обрезать контент!
Но у него есть побочный эффект: элемент становится контейнером прокрутки, даже если полос прокрутки нет. Это может влиять на поведение
Если прокрутка вообще не нужна, есть более точное значение.
Контент по-прежнему обрезается, но контейнер прокрутки не создаётся. Поэтому
Разница особенно заметна в такой структуре.
Есть ещё полезная деталь: расстояние, на котором происходит обрезка, можно увеличить отдельно.
Это удобно, когда содержимое нужно обрезать, но хочется сохранить несколько пикселей для визуального эффекта вроде тени.
🔥
📣 Code Ready | #совет
overflow: hidden часто ставят просто для обрезки того, что выходит за границы блока..wrapper {
overflow: hidden;
}Но у него есть побочный эффект: элемент становится контейнером прокрутки, даже если полос прокрутки нет. Это может влиять на поведение
position: sticky, программную прокрутку и поиск ближайшего скролл-контейнера.Если прокрутка вообще не нужна, есть более точное значение.
.wrapper {
overflow: clip;
}Контент по-прежнему обрезается, но контейнер прокрутки не создаётся. Поэтому
sticky внутри такого блока может ориентироваться на внешний скролл-контейнер, а не останавливаться на .wrapper.Разница особенно заметна в такой структуре.
<div class="wrapper">
<aside class="sidebar">...</aside>
</div>
.wrapper { overflow: clip; }
.sidebar {
position: sticky;
top: 16px;
}Есть ещё полезная деталь: расстояние, на котором происходит обрезка, можно увеличить отдельно.
.wrapper {
overflow: clip;
overflow-clip-margin: 8px;
}Это удобно, когда содержимое нужно обрезать, но хочется сохранить несколько пикселей для визуального эффекта вроде тени.
overflow: clip широко доступен в современных браузерах. Важно только не считать его полной заменой hidden: если элемент действительно должен программно прокручиваться, нужен overflow: hidden или другое прокручиваемое значение.Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥9❤5
This media is not supported in your browser
VIEW IN TELEGRAM
Правило
@view-transition позволяет управлять поведением View Transition API при навигации между документами. С его помощью можно включить плавные переходы между страницами без необходимости вручную запускать их через JS.Основные параметры:
• navigation: auto — включает переходы при навигации между страницами;
• types — задаёт типы переходов, которые затем можно использовать для разных анимаций.
@view-transition полезно для многостраничных сайтов: смену страниц можно сделать визуально плавной, а сами анимации настроить через CSS-псевдоэлементы View Transition.Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥10🤝4❤2
<template> — храним HTML-разметку без её отображения на странице
Когда структура элемента известна заранее, собирать её вручную через
Ту же структуру можно описать декларативно непосредственно в HTML:
Браузер парсит содержимое
Содержимое шаблона доступно через свойство
Поэтому искать элементы шаблона необходимо внутри
Для создания независимого экземпляра содержимое шаблона можно импортировать в текущий документ с глубоким копированием:
Аргумент
Один шаблон можно использовать многократно, каждый раз получая независимую DOM-структуру:
У
Есть важный нюанс с событиями: обработчики, зарегистрированные через
Обработчик необходимо назначить уже созданному экземпляру либо использовать делегирование событий:
На практике
🔥
📣 Code Ready | #практика
Когда структура элемента известна заранее, собирать её вручную через
createElement() не всегда удобно: разметка смешивается с логикой и увеличивает объём js. Для таких случаев в HTML существует <template> — элемент для хранения готовой DOM-заготовки без её отображения на странице:const card = document.createElement('article');
const title = document.createElement('h2');
const button = document.createElement('button');
card.className = 'card';
title.className = 'card__title';
button.className = 'card__button';
card.append(title, button);Ту же структуру можно описать декларативно непосредственно в HTML:
<template id="card-template">
<article class="card">
<h2 class="card__title"></h2>
<button class="card__button">
Открыть
</button>
</article>
</template>
Браузер парсит содержимое
<template> как HTML, но не отображает его и не включает в активное дерево документа:<template>
<h2>Карточка пользователя</h2>
</template>
Содержимое шаблона доступно через свойство
content, которое представляет собой DocumentFragment:const template = document.querySelector(
'#card-template'
);
console.log(template.content);
// DocumentFragment
Поэтому искать элементы шаблона необходимо внутри
content, а не через сам <template>:template.querySelector('.card');
// null
template.content.querySelector('.card');
// <article class="card">...</article>Для создания независимого экземпляра содержимое шаблона можно импортировать в текущий документ с глубоким копированием:
const fragment = document.importNode(
template.content,
true
);
document.body.append(fragment);
Аргумент
true означает глубокое копирование вместе со всеми вложенными узлами. До вставки экземпляр можно заполнить данными, не изменяя исходный шаблон:const fragment = document.importNode(
template.content,
true
);
fragment.querySelector('.card__title').textContent =
'JavaScript';
document.body.append(fragment);
Один шаблон можно использовать многократно, каждый раз получая независимую DOM-структуру:
function createCard(title) {
const fragment = document.importNode(
template.content,
true
);
fragment.querySelector('.card__title').textContent =
title;
return fragment;
}
document.body.append(
createCard('JavaScript'),
createCard('TypeScript'),
createCard('Node.js')
);<template> не следует путать с обычным скрытым контейнером: hidden скрывает элемент визуально, но его содержимое остаётся частью документа:<div hidden>
<video src="video.mp4" autoplay></video>
</div>
У
<template> содержимое хранится отдельно и становится частью активного DOM только после создания и вставки экземпляра:<template id="video-template">
<video src="video.mp4" autoplay></video>
</template>
Есть важный нюанс с событиями: обработчики, зарегистрированные через
addEventListener(), при копировании DOM-структуры не переносятся:const button =
template.content.querySelector('.card__button');
button.addEventListener('click', handler);
const fragment = document.importNode(
template.content,
true
);
Обработчик необходимо назначить уже созданному экземпляру либо использовать делегирование событий:
const button =
fragment.querySelector('.card__button');
button.addEventListener('click', handler);
На практике
<template> подходит для карточек, строк таблицы, элементов списка, уведомлений и других повторяемых частей интерфейса с заранее известной структурой:function createUser(user) {
const fragment = document.importNode(
template.content,
true
);
fragment.querySelector('.card__title').textContent =
user.name;
return fragment;
}<template> позволяет хранить HTML-заготовку вне активного DOM и создавать из неё независимые экземпляры по мере необходимости, оставляя структуру в HTML, а работу с данными и поведением — в js.Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍12🔥6
Шпаргалка по основным возможностям Iterator API: ручное управление итерацией, досрочное завершение и передача ошибок, создание iterable-объектов и ленивые преобразования через Iterator Helpers. Полезная база для углубления знаний об итераторах, генераторах и обработке последовательностей данных.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍7❤6
ResizeObserver — отслеживание размеров элементов на уровне разметки!
Размер DOM-элемента не всегда связан с размером окна браузера. Компонент может менять свои размеры из-за изменения родительского контейнера, перестроения
Например, открытие sidebar может уменьшить рабочую область компонента с 1100px до 900px, при этом размер окна останется прежним.
Событие
Компонент получает информацию о собственном размере и не зависит от того, где он находится: внутри модального окна, боковой панели или полноэкранного блока.
При работе с
Браузер может обнаружить такой цикл и ограничить дальнейшую доставку уведомлений.
Обычно измерение и изменение интерфейса разделяют, например перенося обновление DOM в следующий кадр отрисовки:
Наблюдатель должен иметь собственный жизненный цикл. Создание новых экземпляров при каждом обновлении компонента приводит к лишним подпискам:
Для современных браузеров доступны более точные способы измерения через
🔥
📣 Code Ready | #практика
Размер DOM-элемента не всегда связан с размером окна браузера. Компонент может менять свои размеры из-за изменения родительского контейнера, перестроения
flex или grid-разметки, открытия боковой панели или изменения структуры интерфейса.window.resize отслеживает только изменение размеров viewport и не позволяет узнать, что изменился конкретный элемент.window.addEventListener(
'resize',
() => {
updateLayout();
}
);
Например, открытие sidebar может уменьшить рабочую область компонента с 1100px до 900px, при этом размер окна останется прежним.
Событие
resize не сработает, хотя компоненту уже нужен перерасчёт. Для отслеживания размеров конкретных DOM-элементов используется ResizeObserver:const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width;
renderChart(width);
});
observer.observe(chart);Компонент получает информацию о собственном размере и не зависит от того, где он находится: внутри модального окна, боковой панели или полноэкранного блока.
При работе с
ResizeObserver важно учитывать возможность цикла изменений. Если внутри обработчика менять размер наблюдаемого элемента, это может вызвать повторное изменение и новый вызов наблюдателя:const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width;
element.style.width = `${width + 10}px`;
});Браузер может обнаружить такой цикл и ограничить дальнейшую доставку уведомлений.
Обычно измерение и изменение интерфейса разделяют, например перенося обновление DOM в следующий кадр отрисовки:
const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width;
requestAnimationFrame(() => {
updateUI(width);
});
});requestAnimationFrame помогает разделить чтение размеров и изменение DOM, но не исправляет ситуацию, когда логика постоянно изменяет размеры наблюдаемого элемента.Наблюдатель должен иметь собственный жизненный цикл. Создание новых экземпляров при каждом обновлении компонента приводит к лишним подпискам:
const observer = new ResizeObserver(callback);
observer.observe(element);
// очистка
observer.disconnect();
Для современных браузеров доступны более точные способы измерения через
contentBoxSize и borderBoxSize:const observer = new ResizeObserver(([entry]) => {
const width =
entry.borderBoxSize?.[0]?.inlineSize ??
entry.borderBoxSize?.inlineSize ??
entry.contentRect.width;
updateLayout(width);
});borderBoxSize учитывает padding и border, поэтому подходит для случаев, когда расчёты должны соответствовать фактическому размеру блока.ResizeObserver не заменяет CSS-механизмы адаптивности. Если изменение касается только отображения, лучше использовать container queries:@container (min-width: 600px) {
.card {
display: grid;
}
}ResizeObserver нужен там, где размер элемента влияет на логику приложения: перерасчёт графиков, размеры canvas, виртуализация списков и другие операции, требующие данных о размере DOM-элемента в js.ResizeObserver позволяет синхронизировать логику приложения с реальными размерами элементов и является более точным инструментом для компонентной архитектуры, чем отслеживание только изменения окна.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍8🤝5❤3
В этой статье:
• Разберётесь, почему один и тот же код может вести себя по-разному в Chromium, Safari и Firefox;• Узнаете, как на отображение сайта влияют ОС, WebView, масштабирование, шрифты и особенности браузерных движков;• Посмотрите на примеры багов и способы снизить вероятность проблем ещё до релиза.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤6👍5
overscroll-behavior — управление scroll chaining во вложенных контейнерах!
Модальные окна, боковые панели и чаты часто содержат собственные области прокрутки, независимые от основного документа.
Чтобы содержимое стало прокручиваемым, элемент должен быть scroll container. Обычно для этого ограничивают его размер и задают соответствующее значение
Когда прокрутка достигает границы контейнера, дальнейший пользовательский ввод может продолжить прокрутку в родительском scroll container. Этот механизм называется scroll chaining:
Если интерфейс прокручивается только по вертикальной оси, точнее ограничить действие свойства этой осью:
Такой вариант подходит для чатов, меню, сайдбаров, модальных окон и других компонентов с независимой вертикальной прокруткой:
Свойство поддерживает три основных значения:
Ограничение overscroll behavior также может влиять на браузерные действия на границе прокрутки, включая pull-to-refresh и жестовую навигацию в средах, где они поддерживаются.
Поведение можно контролировать и на уровне корневого scroll container, однако глобальное ограничение следует применять только при наличии конкретного требования интерфейса:
Важно отличать управление scroll chaining от background scroll lock. Если при открытой модалке документ вообще не должен прокручиваться, блокировку фонового скролла необходимо реализовывать отдельно.
Для прокручиваемого содержимого модального окна достаточно следующей конфигурации:
После достижения границы
🔥
📣 Code Ready | #практика
Модальные окна, боковые панели и чаты часто содержат собственные области прокрутки, независимые от основного документа.
<div class="modal">
<div class="modal__content">
<!-- длинный контент -->
</div>
</div>
Чтобы содержимое стало прокручиваемым, элемент должен быть scroll container. Обычно для этого ограничивают его размер и задают соответствующее значение
overflow:.modal__content {
max-height: 70vh;
overflow-y: auto;
}Когда прокрутка достигает границы контейнера, дальнейший пользовательский ввод может продолжить прокрутку в родительском scroll container. Этот механизм называется scroll chaining:
.modal__content {
max-height: 70vh;
overflow-y: auto;
overscroll-behavior: contain;
}overscroll-behavior: contain предотвращает scroll chaining за пределы текущего контейнера, сохраняя обычную прокрутку внутри него и локальные overscroll-эффекты.Если интерфейс прокручивается только по вертикальной оси, точнее ограничить действие свойства этой осью:
.sidebar {
height: 100vh;
overflow-y: auto;
overscroll-behavior-y: contain;
}Такой вариант подходит для чатов, меню, сайдбаров, модальных окон и других компонентов с независимой вертикальной прокруткой:
.chat {
overflow-y: auto;
overscroll-behavior-y: contain;
}Свойство поддерживает три основных значения:
auto сохраняет стандартное поведение, contain останавливает scroll chaining, а none дополнительно подавляет локальные overscroll-эффекты:.element {
overscroll-behavior: auto;
}
.element {
overscroll-behavior: contain;
}
.element {
overscroll-behavior: none;
}Ограничение overscroll behavior также может влиять на браузерные действия на границе прокрутки, включая pull-to-refresh и жестовую навигацию в средах, где они поддерживаются.
Поведение можно контролировать и на уровне корневого scroll container, однако глобальное ограничение следует применять только при наличии конкретного требования интерфейса:
html,
body {
overscroll-behavior-y: none;
}
overscroll-behavior не создаёт scroll container самостоятельно. Оно определяет поведение уже существующей области прокрутки при достижении её границ:.panel {
max-height: 500px;
overflow-y: auto;
overscroll-behavior-y: contain;
}Важно отличать управление scroll chaining от background scroll lock. Если при открытой модалке документ вообще не должен прокручиваться, блокировку фонового скролла необходимо реализовывать отдельно.
Для прокручиваемого содержимого модального окна достаточно следующей конфигурации:
.modal__content {
max-height: min(700px, 80vh);
overflow-y: auto;
overscroll-behavior-y: contain;
}После достижения границы
.modal__content вертикальная прокрутка не передаётся родительскому scroll container.overscroll-behavior декларативно контролирует scroll chaining без перехвата wheel и touchmove через js. Для независимых вертикальных scroll containers в большинстве случаев достаточно overscroll-behavior-y: contain.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥4🤝3
Большинство собирает такую базу годами.
Здесь все отсортировали и разбили в одном канале
(первое сообщение в закрепе)
Please open Telegram to view this post
VIEW IN TELEGRAM