Начни здесь

Из записи — в читаемый сайт: курс по skills-transcript-site

Практический курс по пакету @dzhechkov/skills-transcript-site для того, кто впервые ставит его в Claude Code. Вместе с Марго ты поставишь пак навыков, разберёшь конвейер из шести шагов, поймёшь, как генератор режет транскрипт на секции, соберёшь самодостаточную страницу с поиском и тёмной темой, опубликуешь её на GitHub Pages без единого шага сборки и научишься проверять результат, а не верить ему на слово.

Знакомься: Марго

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

Содержание курса

1. Зачем превращать запись в сайт?

Что делает пакет одной фразой — и почему текст выигрывает у видео.

Ключевая мысль: статический сайт из транскрипта без шага сборки

У Марго в облаке лежат записи докладов сообщества: девяносто минут, ещё девяносто, ещё. Смотрит их примерно никто, и целый год Марго была уверена, что дело в темах.

Дело не в темах. Видео нельзя пролистать глазами, внутри него нельзя найти слово, и поисковик не знает, о чём оно. Текст умеет всё это по своей природе — надо только превратить запись в текст и положить его туда, где живут страницы.

Ровно это делает пакет @dzhechkov/skills-transcript-site: он собирает статический сайт из транскрипта без шага сборки. Разберём обещание по словам:

  • статический — на выходе обычные файлы: docs/index.html, docs/static/app.js, docs/static/style.css; браузер открывает их как есть;
  • из транскрипта — на входе текст расшифровки или ссылка на YouTube, из которой субтитры достанут за тебя;
  • без шага сборки — ни npm run build, ни React, ни Vite: положил папку docs/ в репозиторий — сайт опубликован.

И сразу главное про удобство: команды из этого курса не надо заучивать. Внутри Claude Code Марго просто говорит: «сделай сайт из вот этой расшифровки» — и навык срабатывает сам. Команды нужны, чтобы ты понимал, что происходит под капотом.

Компромисс: ты получаешь скорость и почти нулевую эксплуатацию ценой потолка сложности. Пока нужна читаемая страница с поиском — попадание в цель; как только захочется личных кабинетов и базы данных, статика закончится.

2. Ставим пак навыков

Три двери установки, сухой прогон до первой записи на диск и ссылки на пакет.

Ключевая мысль: установка пака навыков одной командой npx

Марго открывает терминал в папке проекта. Монтаж короткий — пакет опубликован в npm, и всё сводится к одной строке.

Дверей три, выбирай по привычке:

  1. npx @dzhechkov/skills-transcript-site — разовый запуск, ничего не остаётся в системе;
  2. npm install -g @dzhechkov/skills-transcript-site, затем skills-transcript-site init — глобальная команда, если ставишь часто;
  3. npx @dzhechkov/skills-transcript-site init в проекте, где уже есть @dzhechkov/keysarium — пак встанет рядом и будет виден вместе с остальной библиотекой навыков.

Перед любой из них сделай сухой прогон: npx @dzhechkov/skills-transcript-site init --dry-run покажет список файлов, которые появятся, и не тронет диск. Марго один раз пропустила этот шаг в чужом репозитории и потом полчаса выясняла, что именно там поменялось.

Полезные ссылки держи под рукой:

Требования скромные: Node.js не ниже 16 и настроенный Claude Code CLI. yt-dlp понадобится только для ссылок на YouTube — и только тогда.

Компромисс: установка пака навыков кладёт в твой .claude/ чужие файлы, и обновлять их придётся отдельной командой. Взамен навык работает одинаково у всей команды, а не живёт в голове одного человека.

3. Что именно легло в проект

Разбираем содержимое .claude/skills/transcript-site-generator/ по полочкам.

Ключевая мысль: состав пака: один навык, шесть модулей, три справочника, два примера

После установки Марго делает то, что стоит делать всегда: смотрит, что именно появилось. Всё лежит в одной папке проекта — .claude/skills/transcript-site-generator/.

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

  1. SKILL.md — оркестратор: когда навык срабатывает, что он обещает на выходе и в каком порядке читает модули.
  2. modules/00-input-analysis.md … modules/05-verification.md — шесть шагов конвейера, по файлу на шаг.
  3. references/tech-stack.md, references/data-schemas.md, references/component-catalog.md — справочники: чем собираем, какая структура у секции, какие блоки страницы бывают.
  4. examples/sample-transcript-site.md и examples/sample-text-only-site.md — два готовых примера: с видео и без него.

Зачем такое дробление, а не один длинный файл: модель читает ровно тот модуль, который нужен на текущем шаге. Это экономит контекст и делает поведение навыка предсказуемым — Марго может открыть modules/02-site-generation.md и увидеть в точности то, чем руководствовался генератор, когда собирал её страницу.

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

4. Восемь команд установщика

init, --force, --dry-run, update, remove, list, doctor — и когда что применять.

Ключевая мысль: сухой прогон и здоровье установки через doctor

У установщика восемь команд, и Марго держит в голове только две — остальные вспоминаются по смыслу.

Полный список:

  • init — поставить все компоненты (без аргументов пакет делает то же самое);
  • init --force — перезаписать то, что уже лежит на месте;
  • init --dry-run — сухой прогон: показать изменения и не тронуть диск;
  • update — обновить установленные файлы до свежей версии пакета;
  • remove — чистое удаление, без хвостов;
  • list — показать, что реально установлено сейчас;
  • doctor — проверка здоровья установки: на месте ли файлы, всё ли согласовано.

Две команды, которые стоит запомнить дословно: init --dry-run — до изменений, doctor — после. Первая отвечает на вопрос «что произойдёт», вторая — на вопрос «всё ли в порядке сейчас». Между ними живёт вся остальная жизнь пака.

Отдельно про --force. Он не спрашивает, он перезаписывает. Марго применяет его в одном случае: когда точно знает, что правила файлы навыка руками и хочет вернуть заводское состояние. Во всех остальных сначала list, потом init --dry-run, и только потом решение.

Компромисс: восемь команд — это больше, чем «поставил и забыл». Зато у каждого состояния установки есть своя команда, и ни одно из них не приходится угадывать.

5. Конвейер из шести шагов

Общая карта: что происходит от входа до проверки и где тебя спрашивают.

Ключевая мысль: конвейер из шести шагов с чекпоинтом после каждого

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

Шаги идут строго по порядку, потому что каждый следующий питается выходом предыдущего:

  1. Разбор входа — понять, что дали: текст, ссылку или оба, вытащить расшифровку и определить язык.
  2. Нарезка — разложить сплошной транскрипт на секции с заголовками и таймкодами.
  3. Сборка страницы — собрать docs/index.html со всем содержимым и SEO-разметкой.
  4. Интерактивность — сгенерировать docs/static/app.js: поиск, оглавление, тёмная тема.
  5. Публикация — положить конфигурацию GitHub Pages и README рядом.
  6. Проверка — пройти по чеклисту и сказать вслух, что сошлось, а что нет.

После каждого шага генератор останавливается и показывает чекпоинт: что сделано, какие файлы появились, и приглашение ответить «ok» или дать правку. Это не декорация. Ошибка нарезки, замеченная на втором шаге, стоит одной реплики; она же, замеченная после публикации, стоит переделки страницы целиком.

Марго проверила это на себе: на первом прогоне она проскочила чекпоинт после нарезки, а на пятом шаге обнаружила, что весь доклад уехал в одну гигантскую секцию.

Компромисс: шесть остановок — это шесть мест, где тебя отвлекают. Взамен ты правишь дёшево и рано, а не дорого и поздно.

6. Шаг 0: генератор разбирается, что ему дали

Классификация входа, добыча субтитров через yt-dlp и выбор языка интерфейса.

Ключевая мысль: определение языка интерфейса по преобладающему алфавиту

Марго приносит ссылку на девяностоминутный доклад и никакого текста. Шаг 0 обязан из этого сделать расшифровку — или честно сказать, что не может.

Сначала классификация. Генератор смотрит на вход и решает, что это:

  • youtube.com/watch?v= или youtu.be/ — ссылка на видео, расшифровку добываем;
  • путь к файлу .txt, .md, .vtt, .srt — читаем файл;
  • многострочный кусок текста длиннее ста символов — берём как есть;
  • ссылка и текст вместе — спрашиваем, а не угадываем: использовать видео только для встраивания, или вытащить расшифровку из него.

Дальше добыча субтитров. Порядок попыток жёсткий: сначала yt-dlp --write-sub (ручные субтитры, они точнее), и только если их нет — --write-auto-sub (автоматические). Если yt-dlp в системе нет, генератор не изображает успех: он говорит, что нужно поставить yt-dlp или вставить текст руками. Отсутствие квитанции — не успех.

Сырой .vtt приходится чистить: выкинуть заголовок WEBVTT, вытащить пары «таймкод + текст», убрать дубли перекрывающихся отрезков, склеить подряд идущие реплики одного спикера и вычистить служебные теги.

И наконец язык. Определение языка интерфейса идёт по преобладающему алфавиту: в первых 500 символах считаются символы по диапазонам Юникода — кириллица, латиница, иероглифы, арабица, — побеждает самый частый. Кириллица даёт русский интерфейс, латиница — английский, всё остальное — английский как запасной вариант.

Важная честная оговорка из документации: это выбор языка только для подписей интерфейса — кнопок, поиска, счётчиков. Сам текст доклада рендерится как есть. И интерфейс есть пока только на русском и английском: украинский или болгарский получат русские подписи как ближайшие доступные.

Компромисс: счёт символов — грубый способ, он ошибётся на смешанном тексте. Зато он не требует ни модели, ни сети и всегда даёт один и тот же ответ на одном и том же входе.

7. Шаг 1: как транскрипт становится секциями

Три стратегии нарезки, заголовки, спикеры и вопрос без единственного ответа.

Ключевая мысль: три стратегии нарезки: явные маркеры, смысловые сдвиги, время

Сплошная стена текста на девяносто минут не читается ни на каком экране. Задача шага 1 — превратить её в секции, по которым можно прыгать.

Генератор перебирает три стратегии нарезки по очереди, пока какая-нибудь не даст хотя бы три секции:

  1. Явные маркеры. Если в тексте уже есть структура — заголовки markdown, нумерация «1.», жирные подписи, метки спикеров «Ведущий:», разделители — берём её. Дешевле всего и точнее всего.
  2. Смысловые сдвиги. Маркеров нет — читаем абзацы и группируем по смене темы. Цель — от 5 до 15 секций, каждая на 200–2000 слов.
  3. Время. Если есть таймкоды, режем на отрезки примерно по пять минут и подвигаем границы к естественным паузам.

Заголовок для каждой секции пишется в 3–8 слов и по существу: «Часть 1» и «Продолжение» — запрещённые названия, потому что по ним нельзя выбрать, куда прыгать.

Попутно шаг 1 вытаскивает спикеров (по меткам вроде «Имя:» или «[Имя]»), проставляет таймкод начала каждой секции, считает слова и время чтения из расчёта 200 слов в минуту, а текст чистит: схлопывает лишние пробелы, чинит фразы, разорванные склейкой субтитров, и экранирует HTML-символы.

Есть и защита от крайностей: секция длиннее 3000 слов режется по границам абзацев автоматически, а если секций получилось всего одна или две — генератор предупреждает и предлагает поделить по времени.

А теперь вопрос без единственного правильного ответа, тот самый, на котором Марго застряла на полчаса: где вообще проходит граница секции? Спикер сменил тему в середине фразы — это новая секция или продолжение старой? Ответ зависит от того, как читатель будет искать: по темам — режь по смыслу; по ходу разговора — режь по времени. Машина здесь не решает за тебя, она предлагает.

Компромисс: автоматическая нарезка почти никогда не идеальна. Зато она даёт черновик за секунды, а чекпоинт после шага 1 — место, где ты его поправишь одной репликой.

8. Шаг 2: одна страница, которая знает о себе всё

Из чего собран index.html: стили без сборки, SEO-разметка и смысловая структура.

Ключевая мысль: самодостаточная страница index.html с полной SEO-разметкой

Шаг 2 выдаёт главный артефакт курса: самодостаточная страница index.html с полной SEO-разметкой. Самодостаточная — значит весь доклад, всё оформление и все метаданные лежат в одном файле; наружу торчат только два CDN-адреса.

Что внутри:

  • Tailwind CSS через CDN — тег script в head, и оформление работает без единой команды сборки. Рядом обязательная строка tailwind.config = { darkMode: 'class' }: без неё тёмная тема просто не включится.
  • Font Awesome через CDN — иконки луны и солнца, лупы, копирования, гамбургер-меню.
  • SEO-разметка целиком — Open Graph и Twitter Card для красивой карточки в мессенджере, плюс структурные данные JSON-LD: Article для текстового доклада и дополнительно VideoObject, если было видео.
  • Смысловые теги — header, nav, main, article, section, footer вместо россыпи div: и поисковику понятнее, и программе чтения с экрана.
  • Содержимое — все секции доклада с уникальными якорями, боковое оглавление на широком экране и гамбургер-меню на узком, встроенное видео в контейнере с фиксированным соотношением сторон.

Марго проверила эффект самой дешёвой проверкой: кинула ссылку в чат сообщества. Раньше прилетала голая ссылка, теперь — карточка с заголовком, описанием и обложкой видео. Работает та самая разметка Open Graph, которую легко счесть необязательной.

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

9. Шаг 3: оживляем страницу без фреймворков

Поиск, оглавление, тёмная тема и одна ловушка порядка загрузки.

Ключевая мысль: интерактивность на ванильном JavaScript без фреймворков

Шаг 3 выдаёт docs/static/app.js — и вот тут курс делает заявление, которое обычно не верят с первого раза: вся интерактивность написана на ванильном JavaScript без фреймворков, в одном файле, с нулём зависимостей.

Что в этом файле живёт:

  • Поиск по всему тексту — модалка по Ctrl/Cmd+K, задержка 300 мс перед запросом, подсветка найденного и кусочек контекста вокруг;
  • Слежение за оглавлением — активный пункт подсвечивается через IntersectionObserver, без единого обработчика прокрутки;
  • Тёмная тема — переключатель с запоминанием выбора в localStorage и подхватом системной настройки при первом визите;
  • Полоса прогресса чтения — обновляется через requestAnimationFrame, а не на каждом событии прокрутки;
  • Кнопка «наверх» — появляется после 500 пикселей прокрутки;
  • Копирование цитаты — кнопка у каждой секции, обработчик один на всю страницу через делегирование по атрибуту data-copy;
  • Мобильная навигация — гамбургер, закрытие по клику вне панели и по выбору пункта.

Отдельно — ловушка, из-за которой Марго полчаса ловила «иногда не работает». Если на странице есть видео, YouTube вызывает onYouTubeIframeAPIReady, как только загрузится его скрипт, — и это может случиться раньше, чем сработает DOMContentLoaded. Поэтому обработчик регистрируется на верхнем уровне файла, до всего остального. Спрятать его внутрь DOMContentLoaded — значит получить переход по таймкодам, который работает через раз в зависимости от скорости сети.

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

Компромисс: без фреймворка нет привычной сборки состояния, и всё держится на аккуратности с идентификаторами блоков. Взамен файл читается целиком за один присест, не устаревает вместе с чужими версиями и грузится мгновенно.

10. Шаг 4: публикация из папки docs

Рабочий процесс GitHub Pages, два способа публикации и типичная ошибка с путём.

Ключевая мысль: публикация из папки docs без шага сборки

Сайт готов и лежит на диске. Шаг 4 отвечает на вопрос «как его увидят люди», и ответ у него один: публикация из папки docs без шага сборки.

Генератор кладёт рядом .github/workflows/deploy.yml. Что в нём важно понимать:

  1. Запуск — по отправке в ветку main и вручную через workflow_dispatch.
  2. Права — pages: write и id-token: write: без них GitHub откажет в публикации.
  3. Группа параллелизма pages с отменой предыдущего запуска — два одновременных выкладывания не подерутся.
  4. Загрузка артефакта с path: 'docs' — именно эта строка говорит, какую папку публиковать.
  5. Никакого шага сборки в рабочем процессе нет вообще: публиковать нечего собирать.

Потом одна ручная настройка в репозитории: Settings → Pages, источник «GitHub Actions». Есть и второй способ, попроще: источник «Deploy from a branch», ветка main, папка /docs — тогда рабочий процесс необязателен. Марго выбрала первый, потому что в журнале Actions видно, когда и что выложилось.

И грабли, на которые она наступила: она попросила вывести сайт в папку site/ вместо docs/, а path: 'docs' в рабочем процессе остался прежним. Публикация прошла успешно и выложила пустоту. Сменил папку вывода — поменяй путь в рабочем процессе.

Попутно шаг 4 создаёт или обновляет README.md: описание, ссылка на живой сайт, список возможностей и ссылка на исходное видео.

Компромисс: GitHub Pages бесплатен и не требует обслуживания, но привязывает тебя к GitHub и к публичной раздаче статики. Для архива докладов сообщества это ровно то, что нужно.

11. Шаг 5: проверка вместо веры

Чеклист из восьми групп и лестница проверок от самой дешёвой к самой дорогой.

Ключевая мысль: проверка вывода по чеклисту, а не на веру

Последний шаг существует ровно потому, что «выглядит нормально» — это не результат. Шаг 5 — проверка вывода по чеклисту, а не на веру.

Чеклист идёт по восьми группам:

  1. Файлы на месте — docs/index.html больше 10 КБ, docs/static/app.js больше 5 КБ, style.css, deploy.yml, README.md существуют.
  2. Структура страницы — объявление типа документа, lang, кодировка, viewport, непустой заголовок, описание, Open Graph, Twitter Card, JSON-LD, подключения Tailwind, Font Awesome и app.js.
  3. Содержимое — все якоря секций уникальны, число секций совпадает с итогом шага 1, нет пустых секций и не осталось ни одного {placeholder} или [TODO].
  4. Поведение — на месте escapeHtml и escapeRegex, слежение за оглавлением через IntersectionObserver, а для видео — обработчик готовности плеера на верхнем уровне.
  5. Доступность — ссылка «к содержимому» перед шапкой, роли banner и main, подпись у бокового меню, роль диалога у поиска, доступные подписи у кнопок-иконок.
  6. Отзывчивость — гамбургер скрыт на широком экране, панель выезжает на узком, текст читается без горизонтальной прокрутки на ширине 320 пикселей.
  7. Тёмная тема — у каждого светлого класса есть парный тёмный, и конфигурация Tailwind задана.
  8. SEO и печать — заголовки в разметке совпадают, а style.css содержит правила печати.

Поверх чеклиста — две детерминированные проверки, которые ничего не стоят. node --check docs/static/app.js ловит синтаксическую ошибку до читателя. grep -c '</html>' docs/index.html обязан вернуть ровно 1: не ноль (файл оборвался) и не два (генератор дописал вторую копию). Ровно так Марго нашла обрезанный файл после большого доклада — глазами она бы этого не увидела, потому что верх страницы выглядел безупречно.

Твоя очередь решать. Полный чеклист — это восемь групп и десятки пунктов, и проходить их все каждый раз никто не будет. Выбери три пункта, которые ты будешь проверять всегда, и обоснуй выбор: какая поломка тебе обойдётся дороже всего — незакрытый тег, сломанный поиск или отсутствующая ссылка «к содержимому»? Правильного ответа тут нет, есть твой контекст.

Компромисс: чеклист длинный и его легко пролистать не читая. Зато каждая его строка — это чья-то уже случившаяся поломка, а не гипотеза.

12. Грабли, которые уже собрали за тебя

Список известных ошибок и стратегия работы с очень большим транскриптом.

Ключевая мысль: антипаттерны генератора и стратегия большого транскрипта

Самая приятная страница документации этого пакета — таблица, в которой перечислены антипаттерны генератора: не советы, а список уже случившихся поломок с признаком и лечением.

Вот те, что стоят внимания:

  • React или Vite для простого доклада — лишняя сложность там, где хватает статики.
  • Доклад, разложенный по нескольким HTML-страницам — вместо одной страницы с якорями; поиск ломается, а навигация становится перезагрузкой.
  • Забытый тег Tailwind или строка darkMode — страница без оформления либо с мёртвым переключателем темы.
  • Меньше трёх секций — навигации нет, значит, весь смысл сайта потерян.
  • Секция длиннее 3000 слов — стена текста внутри страницы против стены текста.
  • Поиск без сообщения «ничего не найдено» — пустой экран читается как поломка.
  • Встроенное видео фиксированной ширины — на телефоне уезжает за край; нужен контейнер с соотношением сторон.
  • Нет запасного пути, если yt-dlp не установлен — падение вместо внятного отказа.

Отдельно — правило, которое спасает чужие репозитории: папка вывода никогда не перезаписывается молча. Если docs/ уже существует, генератор спрашивает: перезаписать, отменить или писать в другую папку.

И финальная история, ради которой Марго вообще дочитала документацию. Её самый длинный доклад дал транскрипт на сорок тысяч слов, а страница на выходе оборвалась на середине. Для таких случаев есть стратегия большого транскрипта — писать не одним куском, а в четыре приёма: сначала оболочка страницы, потом секции пачками по 5–10, потом закрывающие теги, потом проверка целостности. Порог — примерно 30 000 слов, то есть около 150 КБ готового HTML.

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

Частые вопросы

У меня нет YouTube-ссылки, только текстовый файл. Пакет вообще нужен?

Да, это основной сценарий. Ссылка нужна только для встроенного плеера и переходов по таймкодам. На вход принимаются .txt, .md, .srt и .vtt, и весь остальной конвейер работает одинаково.

Обязательно ли ставить yt-dlp?

Нет. yt-dlp нужен ровно в одном случае — когда вы подаёте ссылку на YouTube и хотите, чтобы генератор сам достал субтитры. Если его нет, шаг 0 честно скажет об этом и предложит вставить текст расшифровки вручную.

Как посмотреть, что установка изменит в проекте, ничего не сломав?

Запустите npx @dzhechkov/skills-transcript-site init --dry-run: это сухой прогон, он печатает список файлов и не трогает диск. После установки состояние показывает doctor, а список компонентов — list.

Почему тёмная тема не переключается?

В девяти случаях из десяти в head отсутствует строка tailwind.config = { darkMode: 'class' }. Без неё классы dark: в разметке ничего не значат. Это первый пункт в списке антипаттернов.

Я вывел сайт не в docs/, а в другую папку — почему по ссылке пусто?

Путь к папке указан ещё и в .github/workflows/deploy.yml в строке path: 'docs'. Смените папку вывода — поменяйте и его, иначе рабочий процесс успешно опубликует пустоту.

Транскрипт очень длинный, страница обрывается. Что делать?

Начиная примерно с 30 000 слов используйте стратегию большого транскрипта: сначала оболочка страницы, затем секции пачками по 5–10, затем закрывающие теги, затем проверка целостности. Контрольная команда — grep -c '</html>' docs/index.html, ответ должен быть ровно 1.

Нужно ли запоминать все команды из курса?

Нет. Внутри Claude Code достаточно попросить словами: «сделай сайт из этой расшифровки» или «покажи, что установлено». Команды в курсе нужны, чтобы вы понимали, что происходит под капотом, и могли проверить результат сами.

Бесплатный интерактивный курс · aicoding.space

Лицензии шрифтов

Onest

Copyright 2021 The Onest Project Authors (https://github.com/googlefonts/onest)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

JetBrainsMono

Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.