Files
2025-etpgrf/README.md

410 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# etpgrf — единый типограф для веба / effortless typography for web
[![PyPI version](https://badge.fury.io/py/etpgrf.svg)](https://badge.fury.io/py/etpgrf)
[![Python Version](https://img.shields.io/pypi/pyversions/etpgrf.svg)](https://pypi.org/project/etpgrf/)
[![License](https://img.shields.io/pypi/l/etpgrf.svg)](https://pypi.org/project/etpgrf/)
# Типограф для веба
Экранная типографика для веба — способствует повышению читабельности текста в интернете,
приближая его к печатной типографике.
## Репозитории / Repositories
Исходный код доступен на нескольких площадках:
* [Gitea](https://git.cube2.ru/erjemin/2025-etpgrf) (Основной self-hosted)
* [GitHub](https://github.com/erjemin/etpgrf) (Главное зеркало)
* [GitVerse](https://gitverse.ru/erjemin/etpgrf) (Зеркало на GitVerse)
## Демострация / Demo
Работа etpgrf-типографа представлена по адресу: [typograph.cube2.ru](https://typograph.cube2.ru/). Подготовьте верстку
вашего текста, сайтов, статей и постов к публикации в интернете за один клик.
## Установка
```bash
pip install etpgrf
```
## Быстрый старт
```python
import etpgrf
# Создаем типограф с настройками по умолчанию
typo = etpgrf.Typographer(langs='ru')
# Обрабатываем текст
result = typo.process(text="\"Пример текста для типографирования!\" - сказал он.")
print(result)
```
### Кодировки и html-мнемоники
Внутри типографа используется кодировка UTF-8. Но при использовании может быть другие кодировки (например,
для русскоязычных текстов все ещё могут использовать Windows-1251). При таких кодировках, для отображения в браузерах
некоторых специфических символов (например, кавычек, тире, стрелочек, математических символов) используют
html-мнемоники (например, `—` для длинного тире, `«` для открывающей кавычки-ёлочки и т.д.).
tpgrf имеет три режима работы с кодировками:
- Режим `unicode` — весь вывод осуществляется в кодировке UTF-8. ВЕСЬ! Включая невидимые символы, типа неразрывных и нулевых
пробелов, мягких переносов и т.д. Это не всегда удобно зато типографированый текст (строки) будет максимально
компактен и занимать меньше места в памяти. В этом режиме в html-мнемоники преобразуются только опасные символы:
* `&lt;` — знак меньше `<`;
* `&gt;` — знак больше `>`;
* `&amp;` — амперсанд `&`;
* `&quot;` — двойные кавычки `"`;
* `&apos;` — одинарные кавычки (апостроф) `'`.
- Режим `mixed` — вывод осуществляется в кодировке UTF-8, но наиболее критичные символы заменяются на html-мнемоники.
Они невидимы или неотличимы друг от друга на экране:
* `&shy;` — мягкий перенос (Soft Hyphen);
* `&nbsp;` — неразрывный пробел (Non-Breaking Space);
* `&ensp;` — полужирный пробел (En Space)
* `&emsp;` — широкий пробел (Em Space)
* `&numsp;` — цифровой пробел;
* `&puncsp;` — пунктуационный пробел;
* `&thinsp;` — межсимвольный пробел;
* `&hairsp;` — пробел "толщина волоса" (Hair Space);
* `&NegativeThinSpace;` — негативный пробел (Negative Space);
* `&zwj;` — пробел нулевой ширины (без объединения) (Zero Width Non-Joiner);
* `&zwnj;` — нулевая ширина (с объединением) (Zero Width Joiner);
* `&lrm;` — изменение направления текста на слева-направо (Left-to-Right Mark);
* `&rlm;` — изменение направления текста на направо-налево (Right-to-Left Mark);
* `&dash;` — дефис (Hyphen);
* `&MediumSpace;` — средний пробел (Medium Mathematical Space);
* `&NoBreak;` — неразрывный пробел (No-Break Space);
* `&InvisibleTimes;` — невидимый знак умножения (Invisible Times) для семантической разметки математических
выражений;
* `&InvisibleComma;` — невидимая запятая (Invisible Comma) для семантической разметки математических выражений.
- Режим `mnemonic` — применяются все возможные html-мнемоники (кроме русских букв) и символов первой половины ASCII
(плюс, минус, знак равенства, знаки препинания и т.д.).
Переключение режимов осуществляется с помощью параметра `mode` при конфигурировании типографа:
```python
# Задаем конфигурацию типографа
typo_mixed_mode = etpgrf.Typographer(mode='mixed')
# Обработка текста
result = typo_mixed_mode.process(text="Этот текст будет обработан в режиме mixed.")
```
### ВАЖНО:
1. Если в тексте уже есть html-мнемоники, они будут преобразованы в unicode, и после обработки типографом
будут заменены на html-мнемоники, соответствующие текущему режиму работы типографа.
2. Некоторым символам соответствуют несколько html-мнемоник. Например, `→` (стрелочка влево) может кодироваться
как `&rarr;`, `&srarr;`, `&rightarrow`, `&RightArrow;` и `&ShortRightArrow;`. Типограф будет использовать самое
короткое из них (для компактности), а значит:
* если в исходном тексте были html-мнемоники, то они будут заменены на более короткие;
* если html-мнемоники использовались как элементы семантической разметки (например, для математических выражений),
то после замены на более короткие html-мнемоники, текст может потерять такую семантику. Например _F = A ⋂ B_:
`F = A &Intersection; B` будет преобразовано в `F = A &xcap; B`;
3. Мнемоники для русских букв не используются в типографе. Все мнемоники русских букв будут преобразованы в русские
буквы и останутся в тексте в виде русских букв.
4. Все исходные html-мнемоники, которые превращаются в два unicode-символа будут превращены обратно в мнемоники каждый
как отдельный символ. Например, множество собственное другого подмножества `&varsubsetneq;` в unicode отображается
двумя символами `\u228a\ufe00` и превратится в `&subne;\ufe00`. Символ `\ufe00` — это невидимый символ, cелектор
варианта начертания (Variant Selector), который изменяет начертание предыдущего символа и для него нет
html-мнемоники. К счастью, в стандарте таких мнемоник (превращающихся в два символа) исчезающе мало и они крайне
редко применляются в тексте, поэтому это не должно вызывать проблем.
### Переносы слов
Обычно в основе переносов слов лежит фонетический принцип — деление по слогам и морфемный принцип — деление по морфемам
(приставки, корни, суффиксы, окончания). В типографе etpgrf реализован эвристический подход к переносу слов,
основанный на фонетических правилах. Он не является строгим и не учитывает все нюансы языка, но обеспечивает вполне
приемлемое качество для большинства случаев. Особенно если "неразрывные" блоки задать достаточно длинными (и именно
это и требуется от хорошего типографа, ведь перенос трех-четырех букв слова на новую строку почти не улучшит
читабельность и внешний вид текста).
Настройки по умолчанию для переноса слов (в `etpgrf.defaults`):
* Длина слова которое не подлежит переносам (`MAX_UNHYPHENATED_LEN`) — 12 символов.
* Длина части слова, которое недопустимо переносить или оставлять на строке ("хвост", "сироты")
(`MIN_TAIL_LEN`) — 5 символов
Управление этими параметрами осуществляется через переопределение. Например:
```python
# Меняем настройки по умолчанию для переносов
etpgrf.defaults.etpgrf_settings.hyphenation.MAX_UNHYPHENATED_LEN = 8
etpgrf.defaults.etpgrf_settings.hyphenation.MIN_TAIL_LEN = 4
```
Или через параметры конфигурации переносов типографа:
```python
# Определяем пользовательские правила переносов
hyphen_settings = etpgrf.Hyphenator(langs='ru', max_unhyphenated_len=8)
# Передаем их в типограф
typo_hyp = etpgrf.Typographer(langs='ru', mode='mnemonic', hyphenation=hyphen_settings)
# Обработка текста с переносами
result = typo_hyp.process(text="Электрофоретическое исследование характеризуется квинтэссенциальной значимостью!")
```
Результат обработки текста с переносами будет выглядеть так:
```html
Электрофо&shy;ретическое исследование характе&shy;ризуется квинтэс&shy;сенциальной значимостью!
```
### Предлоги, союзы и частицы
Правилом хорошего тона в любой типографике считается, когда короткие слова, такие как предлоги, союзы и частицы,
не остаются в конце строки в одиночестве («висеть»). Это ухудшает читаемость.
Типограф `etpgrf` автоматически решает эту проблему, «приклеивая» такие слова к последующему слову с помощью
неразрывного пробела (`&nbsp;`).
* `в доме``в&nbsp;доме`
* `и сказал``и&nbsp;сказал`
Это правило работает для коротких слов в русском, старорусском и английском языках.
Кроме того, обрабатываются и постпозитивные частицы (например, `ли`, `же`, `бы`), которые, наоборот, для улучшения
читабельности, «приклеиваются» к предыдущему слову:
* `сказал бы``сказал&nbsp;бы`
### Кавычки
В текстах кавычки бывают двух видов: «ёлочки» (для русского языка) и “лапки” (для английского языка). В типографе
реализована автоматическая замена кавычек на соответствующие типографские символы в зависимости от языка текста.
Большинство типографов при обработке кавычек находят парные (и определяют вложенность). В etpgrf же реализован
другой подход. Он ищет и обрабатывает кавычки, которые находятся рядом со словами. То есть какие-то буквы следуют
слева или справа от кавычки.
Преобразование рядом с цифрами (например, когда обозначаются дюймы (`17"`) или секунды (`3' 25"`)) не производится. Также
не обрабатываются кавычки окруженные пробелами. Все кавычки которые в исходном тексте уже были оформлены в виде
«ёлочек» или “лапок” — тоже не обрабатываются.
ВАЖНО1: По правилам орфографии перед закрывающей кавычкой разрешены только определенные знаки препинания:
вопросительный (?), восклицательный (!) знаки и многоточие (…). Такие конструкции используются для цитат. Это учтено
в etpgrf, и кавычки будут обработаны: `Она воскликнула: "Какая красота!"` будет преобразовано в `Она воскликнула:
«Какая красота!»`. По правилам пунктуации, точка `.` перед закрывающей кавычкой не допускается, но существуют
исключения, когда перед кавычкой стоит сокращение (например, `т. д.`, `и т. п.`). В таких случаях точка сохраняется:
`Он сказал: "Это важно, и т. д."``Он сказал: «Это важно, и т. д.»`. **Типограф допускает точку перед закрывающей
кавычкой.**
ВАЖНО2: Если в настройке типографа указано несколько языков (`langs='ru+en'`), то кавычки будут преобразованы по правилам
для языка который идет первым в списке. Например, для `langs='ru+en'` кавычки будут преобразованы в «ёлочки»,
Если при типорафировании преобразование не требуется, то можно обработку кавычек можно отключить с помощью
параметра `quotes=False`:
```python
# Задаем конфигурацию типографа без кавычек
typo_no_quotes = etpgrf.Typographer(langs='ru', quotes=False)
# Обработка текста без кавычек
result = typo_no_quotes.process(text='Этот "текст" будет обработан без кавычек.')
```
### Компоновка (тире, диапазоны, инициалы, единицы измерения, сокращения и т.п.)
После того как псевдографика заменена на правильные символы, в дело вступает модуль компоновки (layout), который
отвечает за расстановку неразрывных и тонких пробелов. Он применяет несколько важных правил для улучшения читаемости.
#### Тире
По правилам русской типографики, длинное тире (—) должно отбиваться пробелами от соседних слов. Чтобы тире не "повисло"
в начале строки и визуально не смешивалось с диалогами, etpgrf заменяет пробел перед тире на неразрывный (&nbsp;).
* `слово — слово``слово&nbsp;— слово`
В английской типографике, наоборот, тире пишется слитно. Типограф учитывает это при указании языка `langs='en'`.
* `word — word``word—word`
Если минус или диапазон стоят между числами (арабскими или римскими), то это считается обозначением числового диапазона
(или отрицательным числом, или математическим выражением), и никаких изменений не производится. Неважно есть пробелы
вокруг тире/минуса или нет. Если между цифрами тире, то это тоже считается диапазоном и неразрывные пробелы не ставятся:
`1941 — 1945``1941 — 1945`, `-10 — -5``-10 — -5`,
Если минус стоит перед числом (например, `-5`), то это считается отрицательным числом, и перед ним ставится неразрывный
пробел: `от -5 до +5``от&nbsp;-5 до +5`.
#### Инициалы и акронимы
Чтобы инициалы не отрывались друг от друга и от фамилии при переносе строки, типограф расставляет между ними
специальные пробелы:
* Неразрывный пробел (`&nbsp;`) ставится между фамилией и инициалом/инициалами (`А. Пушкин``А.&nbsp;Пушкин`).
Неважно стоят ли инициалы перед фамилией или после неё. Важно наличие точки и буквы (инициала), написанного
с заглавной буквы.
* Тонкая шпация (&thinsp;) ставится между самими инициалами, если они написаны слитно, для улучшения внешнего вида
(`Пушкин А. С. ``Пушкин&nbsp;А.&thinsp;С.`). Число инициалов не ограничено (`J.R.R. Tolkien`
`J.&thinsp;R.&thinsp;R.&nbsp;Tolkien`), наличие или отсутствие пробелов между инициалами в исходном тексте неважно.
* Акронимы, написанные через точку (не слитно, например, **Н.Л.О.**), разделяются так же, как инициалы, через тонкую шпацию
(`Н.Л.О.``Н.&thinsp;Л.&thinsp;О.`). Наличие или отсутствие пробелов между буквами в исходном тексте неважно.
Это правило может давать побочные эффекты (в частности, тонкая шпация не является неразрывным пробелом, и в длинных
акронимах может привести к разрыву строки). Поэтому его обработку можно отключить с помощью параметра
`process_initials_and_acronyms`:
```python
typo = etpgrf.Typographer(process_initials_and_acronyms=False)
result = typo.process("А. С. Пушкин") # Останется без изменений
```
#### Единицы измерения
Типограф предотвращает отрыв единиц измерения от чисел, ставя между ним и предшествующей цифрой неразрывный пробел.
Это работает для:
* **Простых единиц:** `100 км.``100&nbsp;км.`, `-5 °C``-5&nbsp;°C`'
* **Составных единиц:** `120 кв.м.``120&nbsp;кв.&thinsp;м.`, `50 тыс. руб.``50&nbsp;тыс.&thinsp;руб.` Пробелы
(есть они или нет) между составными частями единицы изменения не важны. Между частями составной единицы измерения
ставится тонкая шпация (`&thinsp;`).
* **Единиц с предлогом:** `№ 5``№&nbsp;5`, `§ 7``§&nbsp;7`, `$ 100``$&nbsp;100`
* **Чисел, записанных и арабскими, и римскими цифрами:** `V в.н.э.``V&nbsp;в.&thinsp;н.&thinsp;э.`
* Если между единицами изменений есть математические символы (например, умножение или деление):
`10 км / ч``10&nbsp;км/ч` (неважно есть пробелы вокруг `/` или нет). Распознаются и другие символы:
`·`, `*`, `×`, `÷`.
Библиотека "знает" множество стандартных единиц для русского и английского языков. Но не все. Вы можете расширить этот
список, передав свои кастомные единицы через параметр `process_units`:
```python
# Передаем список
typo = etpgrf.Typographer(process_units=['бочек', 'вёдер'])
# Можно передавать и с помощзью строки через пробелы
typo = etpgrf.Typographer(process_units='бочек вёдер аршин сажен')
result = typo.process("Нужно 10 бочек.") # -> "Нужно 10&nbsp;бочек."
```
Если нужно отключить распознавание и обработку единиц измерения:
```python
typo = etpgrf.Typographer(process_units=False)
result = typo.process("100 км/ч") # Останется без изменений
```
#### Сокращения
Типограф также обрабатывает распространённые русскоязычные сокращения, чтобы они корректно отображались и не разрывались
при переносе строк. Правила делятся на два типа:
* Финальные сокращения. Сокращения, которые обычно стоят в конце фразы (например, и т. д., и т. п.),
обрабатываются особым образом: их части «склеиваются» тонкой шпацией, а перед всей конструкцией ставится неразрывный
пробел, чтобы она не «повисла» на новой строке. `...и так далее, и т. д.``...и так далее, и&nbsp;т.&thinsp;д.`
Это правило работает независимо от того, как сокращение было написано в исходном тексте (т.д. или т. д.).
* Препозиционные сокращения. Сокращения, которые стоят перед другим словом (например, и. о. директора, т. е. сказать),
также «склеиваются» внутри, но неразрывный пробел ставится после них, чтобы привязать их к последующему слову.
`Назначить и. о. директора``Назначить и.&thinsp;о.&nbsp;директора`
Библиотека знает небольшой набор самых распространённых сокращений. Но не все, а некоторые принципиально невозможны
к обработке. Например, сокращение `пр.` может оказаться как финальным (в значении «и так далее»), так и препозиционным
(в значении «профессор» или «проспект»). Так же типограф не обрабатывает сокращения, связанные с адресами (ул., д.,
кв., пл., наб. ...) так как они могут быть как финальными, так и препозиционными.
### Висячая типографика
Висячая типографика — это приём из классической вёрстки, когда некоторые знаки препинания (кавычки, скобки, иногда
ре и маркеры списков) выносятся на левое (и иногда и по правому) поле текста. Это создаёт идеально ровный край не по
формальным границам знаков, а по оптическому краю — по первым буквам строк. Текст выглядит гораздо аккуратнее и
профессиональнее.
Интернет публикации (да и бумажные издания) практически игнорируют висячую типографику. Но иногда это отличный
инструмент для акцентной типографики: крупные заголовки, цитаты, выносы, подписи к иллюстрациям, оформленные с помощью
висячей типографики, выглядят гораздо эффектнее. В современном CSS есть свойство `hanging-punctuation`, которое должно
делать всё это автоматически. Но на сегодняшний день (конец 2025) его поддержка браузерами практически нулевая (кроме
Safari), поэтому на него полагаться нельзя. Поэтому в типографе etpgrf реализация висячей типографики осуществляется
через оборачивание висячих символов в специальные HTML-теги с CSS-классами.
Оборачивая "висячий" символ или слово в `<span>` и применяя к нему, например, отрицательный `text-indent` или
`margin-left` (`<span style="margin-left:-0.44em">&laquo;</span>`), мы можем сместить сам символ, но нужно ещё и
сохранить расстояние до соседнего слова. Поэтому типограф оборачивает не только сам висячий символ, но и ближайшее слово
(до пробела или границы узла), а также, при необходимости, окружающий пробел. Сама визуальная компенсация оформляется через
отрицательные `margin`/`padding` в CSS-классах — никаких `position:absolute`, чтобы не нарушать поток текста.
По умолчанию эта функция висячей типографики **отключена**. Чтобы её включить, нужно задать параметр
`hanging_punctuation` при конфигурировании типографа (по умолчанию `hanging_punctuation=None`):
```python
typo = etpgrf.Typographer(hanging_punctuation='left')
```
Параметр `hanging_punctuation` может принимать следующие значения:
* `None` или `False` — функция отключена (по умолчанию);
* `'left'` или `True` — включены только левые висячие символы (выравнивание по левому краю);
* `'right'` — включены только правые висячие символы (выравнивание по правому краю).
Значения `'both'` **недоступно**, потому что совмещение левой и правой выверки одновременно приводит к конфликтам
с размещением пробелов и делает невозможным контролировать визуальное выравнивание (см. блок про `text-justify`).
Также через `hanging_punctuation` можно задать список тегов, внутри которых висячая типографика будет применяться
(всегда в режиме `'both'`). Это нерекомендованный способ, потому что он предполагает знание структуры HTML и неизбежно
выпадает из общей логики вложенности и пробельных узлов.
### Как работает оборачивание
Процессор висячей типографики запускается после всех текстовых преобразований и работает с деревом BeautifulSoup. Он ищет
последовательности «пробел + висячий символ» для левого выравнивания и «слово + висячий символ + пробел» для правого,
чтобы обернуть нужные фрагменты в пары `<span>` и не допустить «сиротства» символов. Порядок действий можно описать так:
* Для `hanging_punctuation='left'`:
* если символ стоит в начале текстового узла (без пробелов слева), оборачивается только сам символ и следующее
слово (`<span class="etp-laquo">«АукЫон»</span>`);
* если перед символом внутри узла есть пробел, то пробел оборачивается в `<span class="etp-sp-laquo"> </span>`, а
символ вместе со словом — в `<span class="etp-laquo">...</span>`;
* если пробел оказалось в соседнем узле, то он тоже оборачивается в `etp-sp-*`, чтобы не нарушить последовательность;
* если компенсирующий пробел является "непереносимым пробелом" (или любым другим: шпацией, em-пробелом и т.п.), то тогда, для правильного выравнивания, оборачивается он, например: `<span class="etp-sp-laquo">&nbsp;</span><span class="etp-laquo">«АукЫон»</span>`.
* Для `hanging_punctuation='right'`:
* слово с висячим символом оборачивается в соответствующий класс (`.etp-raquo`, `.etp-rpar` и т.д.);
* пробел сразу после символа получает класс `etp-sp-raquo`, `etp-sp-rpar` и т.д., чтобы сохранить переносную ширину и
аккуратно компенсировать смещение;
Пример вывода для `'left'`:
```html
Завтра концерт группы<span class="etp-sp-laquo"> </span><span class="etp-laquo">«АукЫон»</span>
```
### CSS для висячих символов
Предлагаемый CSS теперь работает только с `margin` и `padding`, без `position:absolute`. Пробелы получают собственные
классы, поэтому их компенсация контролируется отдельно, а не встроена в сам висячий символ. Убедитесь, что эти стили
подключены к странице и не конфликтуют с `text-justify`, который вытягивает пробелы по всей строке и разрушает аккуратное
выравнивание.
```css
/* --- ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ --- */
.etp-laquo { margin-left: -0.44em; }
.etp-ldquo, .etp-bdquo { margin-left: -0.4em; }
.etp-lsquo { margin-left: -0.22em; }
.etp-lpar, .etp-lsqb, .etp-lcub { margin-left: -0.25em; }
/* компенсирующие пробелы для левых висячих символов */
.etp-sp-laquo { padding-right: 0.44em; }
.etp-sp-ldquo, .etp-sp-bdquo { padding-right: 0.4em; }
.etp-sp-lsquo { padding-right: 0.22em; }
.etp-sp-lpar, .etp-sp-lsqb, .etp-sp-lcub { padding-right: 0.25em; }
/* --- ПРАВЫЕ ВИСЯЧИЕ СИМВОЛЫ --- */
.etp-raquo { padding-right: 0.44em; margin-left: -0.44em; }
.etp-rdquo { padding-right: 0.4em; margin-left: -0.4em; }
.etp-rsquo { padding-right: 0.22em; margin-left: -0.22em; }
.etp-rpar, .etp-rsqb, .etp-rcub { padding-right: 0.25em; margin-left: -0.25em; }
/* компенсирующие пробелы для правых висячих символов */
.etp-sp-raquo { margin-left: -0.44em; }
.etp-sp-rdquo { margin-left: -0.4em; }
.etp-sp-rsquo { margin-left: -0.22em; }
.etp-sp-rpar, .etp-sp-rsqb, .etp-sp-rcub { margin-left: -0.25em; }
```
*Комментарии:* Двухстороннее выравнивание текстового блока с помощью стиля `text-justify` в принципе плохо совместим концепцией типографики — он растягивает или сжимает пробелы по всей строке (а это пробелы между словами) и уже этими, переменными, пробелами, делает текст трудночитаемым. Если же вы используете `text-justify` для выравнивания текста по ширине, то, чтобы сохранить оптимальную читаемость текста, включать висячую типографику не рекомендуется.
## Известные особенности и ограничения
При обработке сложного HTML-кода типограф стремится сохранить структуру документа, но некоторые пограничные случаи могут обрабатываться не так, как ожидается. В частности:
* **Обработка на стыке тегов:** Правила, требующие анализа контекста (например, расстановка неразрывных пробелов у тире или единиц измерения), могут работать некорректно, если анализируемые части текста разделены тегами . Например, конструкция `$<b>100</b>` не будет обработана (между $ и 100 не будет вставлен неразрывный пробел), так как типограф не видит их как соседние элементы.
* **"Ремонт" HTML:** Библиотека использует `BeautifulSoup` для парсинга, который может "чинить" невалидный HTML (например, закрывать незакрытые теги). Это может привести к неожиданным изменениям в структуре, если исходный код был некорректен. Так же может меняться порядок атрибутов тега.
Мы знаем об этих особенностях и работаем над улучшением алгоритмов для более точной обработки сложных случаев.
## P.S.
Если вам нравится этот проект, можете поддержать отправив любую сумму на мой Т-банк
[по ссылке](https://tbank.ru/cf/27hMw1BTFMs) или, для приверженцев децентрализованного будущего,
через Toncoin (TON) (адрес кошелька `UQApEkzNMYOg5qesWwlyfGFf4ayFyki5Mrpcd2yadgS2_1cx`)
## Credits
**Разработка:**
Проект разработан Sergei Erjemin при активном участии различных LLM в роли pair-programmer.