Pretext
Используется при создании креативных браузерных демо с @chenglou/pretext — DOM-независимая верстка текста для ASCII-арта, типографический обход препятствий, текст-как-геометрия в играх, кинетическая типографика и генеративное искусство на основе текста. По умолчанию создает однофайловые HTML-демо.
Метаданные навыка
| Источник | Встроенный (установлен по умолчанию) |
| Путь | skills/creative/pretext |
| Версия | 1.0.0 |
| Автор | VibeOS |
| Лицензия | MIT |
| Платформы | linux, macos, windows |
| Теги | creative-coding, typography, pretext, ascii-art, canvas, generative, text-layout, kinetic-typography |
| Связанные навыки | p5js, claude-design, excalidraw, architecture-diagram |
Справочник: полный SKILL.md
Ниже приведено полное определение навыка, которое VibeOS загружает при его активации. Это те инструкции, которые видит агент, когда навык активен.
Креативные демо Pretext
Обзор
@chenglou/pretext — это TypeScript-библиотека размером 15 КБ без зависимостей от Чэн Лу (React core, ReasonML, Midjourney) для DOM-независимого измерения и верстки многострочного текста. Она делает одну вещь: принимает (text, font, width) и возвращает переносы строк, ширину каждой строки, позиции каждой графемы и общую высоту — всё через измерение на canvas, без перекомпоновки.
Звучит как рутина. Но это не так. Благодаря скорости и геометричности это креативный примитив: можно перекомпоновывать абзацы вокруг движущегося спрайта на 60fps, создавать игры, где геометрия уровней состоит из настоящих слов, управлять ASCII-логотипами через прозу, разбивать текст на частицы с точными начальными позициями каждой графемы или создавать многострочные UI-элементы с авто-подгонкой без лишних вызовов getBoundingClientRect.
Этот навык нужен, чтобы VibeOS мог создавать крутые демо — такие, которые публикуют в X. Смотрите pretext.cool и chenglou.me/pretext для сообщества демо-примеров.
Когда использовать
Используйте, когда пользователь просит:
- «демо pretext» / «крутую штуку с pretext» / «текст как X»
- Текст, обтекающий движущуюся фигуру (геройские секции, редакторские макеты, анимированные длинные страницы)
- Эффекты ASCII-арта с использованием настоящих слов или прозы, а не моноширинных растров
- Игры, где игровое поле / препятствия / кирпичи сделаны из текста (Тетрис из букв, Арканоид из прозы)
- Кинетическая типографика с физикой на уровне глифов (разрушение, разброс, стая, поток)
- Типографическое генеративное искусство, особенно с нелатинскими или смешанными шрифтами
- Многострочный UI с авто-подгонкой (минимальная ширина контейнера, вмещающая текст)
- Всё, что требует знания переносов строк до рендеринга
Не используйте для:
- Статических SVG/HTML-страниц, где CSS уже решает задачу верстки — просто используйте CSS
- Редакторов форматированного текста, общих движков инлайн-форматирования (pretext намеренно узконаправлен)
- Преобразования изображения в текст (используйте навыки
ascii-art/ascii-video) - Чистого генеративного искусства на canvas без текста — используйте
p5js
Креативный стандарт
Это визуальное искусство, отображаемое в браузере. Pretext возвращает числа; вы рисуете результат.
- Не отправляйте демо «hello world». Шаблон
hello-orb-flow.html— это отправная точка. Каждое готовое демо должно содержать осмысленный цвет, движение, композицию и одну визуальную деталь, которую пользователь не просил, но оценит. - Темные фоны, теплые тона, продуманная палитра. Классический янтарь на черном (ЭЛТ / терминал) работает, но также подойдут холодный белый на угольном (редакторский стиль) и обесцвеченные пастельные тона (ризограф). Выберите один вариант и придерживайтесь его.
- Пропорциональные шрифты — это суть. Вся фишка Pretext в том, что он «не моноширинный» — используйте это. Применяйте Iowan Old Style, Inter, JetBrains Mono, Helvetica Neue или вариативный шрифт. Никогда не используйте шрифт по умолчанию без засечек.
- Реальный текст, не lorem ipsum. Корпус текста должен что-то значить. Короткие манифесты, поэзия, реальный исходный код, найденный текст, собственный README библиотеки — никогда не используйте
lorem ipsum. - Превосходный первый кадр. Никаких состояний загрузки, пустых кадров. Демо должно выглядеть готовым к публикации с момента открытия.
Стек
Одно самодостаточное HTML-файл на демо. Без этапа сборки.
| Уровень | Инструмент | Назначение |
|---|---|---|
| Ядро | @chenglou/pretext через CDN esm.sh | Измерение текста + верстка строк |
| Рендеринг | HTML5 Canvas 2D | Рендеринг глифов, покадровая композиция |
| Сегментация | Intl.Segmenter (встроенный) | Разделение на графемы для эмодзи / CJK / комбинируемых символов |
| Взаимодействие | Сырые DOM-события | Мышь / сенсор / колесо — без фреймворков |
<script type="module">
import {
prepare, layout, // сценарий 1: простая высота
prepareWithSegments, layoutWithLines, // сценарий 2a: строки фиксированной ширины
layoutNextLineRange, materializeLineRange, // сценарий 2b: потоковая / переменная ширина
measureLineStats, walkLineRanges, // статистика без выделения строк
} from "https://esm.sh/@chenglou/pretext@0.0.6";
</script>
Фиксируйте версию. На момент написания — @0.0.6. Проверьте npm на предмет последней версии, если поведение демо отличается.
Два сценария использования
Почти всё сводится к одной из двух форм. Изучите обе.
Сценарий 1 — измерение, затем рендеринг с CSS/DOM
const prepared = prepare(text, "16px Inter");
const { height, lineCount } = layout(prepared, 320, 20);
Вы всё ещё позволяете браузеру рисовать текст. Pretext просто сообщает, какой высоты будет блок при заданной ширине, без чтения DOM. Используйте для:
- Виртуализированных списков, где строки содержат переносимый текст
- Masonry с точной высотой карточек
- Проверок «помещается ли эта метка?» на этапе разработки
- Предотвращения сдвига макета при загрузке удаленного текста
Синхронизируйте font и letterSpacing точно с вашим CSS. Формат ctx.font canvas (например, "16px Inter", "500 17px 'JetBrains Mono'") должен совпадать с отображаемым CSS, иначе измерения будут расходиться.
Сценарий 2 — измерение и рендеринг самостоятельно
const prepared = prepareWithSegments(text, FONT);
const { lines } = layoutWithLines(prepared, 320, 26);
for (let i = 0; i < lines.length; i++) {
ctx.fillText(lines[i].text, 0, i * 26);
}
Здесь и начинается творческая работа. Вы управляете рисованием, поэтому можете:
- Рендерить на canvas, SVG, WebGL или любую систему координат
- Применять преобразования для каждого глифа (поворот, дрожание, масштаб, прозрачность)
- Использовать метаданные строк (ширина, позиции графем) как геометрию
Для потока с переменной шириной строки (текст вокруг фигуры, текст в кольце, текст в непрямоугольной колонке):
let cursor = { segmentIndex: 0, graphemeIndex: 0 };
let y = 0;
while (true) {
const lineWidth = widthAtY(y); // ваша функция: какой ширины коридор на этой высоте y?
const range = layoutNextLineRange(prepared, cursor, lineWidth);
if (!range) break;
const line = materializeLineRange(prepared, range);
ctx.fillText(line.text, leftEdgeAtY(y), y);
cursor = range.end;
y += lineHeight;
}
Это самый важный паттерн во всей библиотеке. Именно он открывает возможность «текста, обтекающего перетаскиваемый спрайт» — демо, ставшее вирусным в X.
Полезные вспомогательные функции
measureLineStats(prepared, maxWidth)→{ lineCount, maxLineWidth }— самая широкая строка, т.е. ширина многострочного авто-подгоняемого блока.walkLineRanges(prepared, maxWidth, callback)— итерация по строкам без выделения строк. Используйте для статистики/физики над графемами, когда символы не нужны.@chenglou/pretext/rich-inline— та же система, но для абзацев со смешанными шрифтами / чипами / упоминаниями. Импортируйте из подпути.
Рецепты демо-паттернов
Сообщество (см. references/patterns.md) группируется вокруг нескольких сильных паттернов. Выберите один и импровизируйте — не изобретайте новую категорию, если не просили.
| Паттерн | Ключевой API | Пример идеи |
|---|---|---|
| Обтекание препятствия | layoutNextLineRange + функция ширины для каждой строки | Редакторский абзац, расступающийся вокруг перетаскиваемого спрайта курсора |
| Игра «текст-как-геометрия» | layoutWithLines + прямоугольники коллизий для каждой строки | Арканоид, где каждый кирпич — это измеренное слово |
| Разрушение / частицы | walkLineRanges → (x,y) для каждой графемы → физика | Предложение, взрывающееся на буквы при клике |
| ASCII-типографика препятствий | layoutNextLineRange + измеренные для каждой строки промежутки препятствий | Битмапный ASCII-логотип, морфинг фигур и перетаскиваемые проволочные объекты, заставляющие текст открываться вокруг их реальной геометрии |
| Редакторская многоколоночная верстка | layoutNextLineRange для каждой колонки + общий курсор | Анимированный журнальный разворот с выносными цитатами |
| Кинетический текст | layoutWithLines + преобразование для каждой строки со временем | Заставка «Звёздных войн», волна, отскок, глитч |
| Многострочный авто-подгон | measureLineStats | Карточка с цитатой, автоматически подстраивающаяся под самый узкий контейнер |
Смотрите templates/donut-orbit.html и templates/hello-orb-flow.html для готовых однофайловых стартовых шаблонов.
Рабочий процесс
- Выберите паттерн из таблицы выше на основе запроса пользователя.
- Начните с шаблона:
templates/hello-orb-flow.html— текст, обтекающий движущуюся сферу (паттерн обтекания препятствия)templates/donut-orbit.html— продвинутый пример: измеренные препятствия ASCII-логотипа, перетаскиваемая проволочная сфера/куб, морфирующие поля фигур, выбираемый DOM-текст и элементы управления для разработчикаwrite_fileв новый.htmlв/tmp/или рабочем пространстве пользователя.
- Замените корпус текста на что-то осмысленное для задачи. Реальная проза, 10–100 предложений, без lorem.
- Настройте эстетику — шрифт, палитру, композицию, взаимодействие. Это основная работа; не пропускайте её.
- Проверьте локально:
cd <директория-с-html> && python3 -m http.server 8765
# затем откройте http://localhost:8765/<файл>.html - Проверьте консоль — pretext выдаст ошибку, если
prepareWithSegmentsвызван с некорректной строкой шрифта;Intl.Segmenterдоступен во всех современных браузерах. - Покажите пользователю путь к файлу, а не только код — он хочет его открыть.
Замечания по производительности
prepare()/prepareWithSegments()— дорогой вызов. Делайте его один раз для каждой пары текст+шрифт. Кешируйте результат.- При изменении размера запускайте только
layout()/layoutWithLines()— никогда не пересоздавайте prepare. - Для покадровых анимаций, где текст не меняется, но геометрия меняется,
layoutNextLineRangeв плотном цикле достаточно быстр для выполнения каждого кадра на 60fps для абзацев обычной длины. - При рендеринге ASCII-масок каждый кадр используйте буфер ячеек (
Uint8Array/типизированные массивы), вычисляйте измеренные для каждой строки промежутки препятствий из ячеек или спроецированной геометрии, объединяйте промежутки, затем передавайте их вlayoutNextLineRangeперед рисованием текста. - Держите визуальную анимацию и анимацию верстки связанными. Если сфера превращается в куб, анимируйте (tween) как буфер отображаемых ячеек, так и промежутки препятствий с одним и тем же значением; иначе демо будет выглядеть как нарисованное, а не как физически перекомпонованное.
- Для затуханий предпочитайте прозрачность слоя изменению интенсивности глифов или масштаба препятствий. Помещайте временные ASCII-спрайты на отдельный canvas и затухайте canvas с помощью CSS/GSAP opacity, чтобы геометрия не казалась сжимающейся.
- Установка
ctx.fontна canvas на удивление медленная; устанавливайте её один раз за кадр, если шрифт не меняется, а не перед каждым вызовомfillText.
Частые ошибки
-
Расхождение строк шрифта CSS и canvas.
ctx.font = "16px Inter"измерено, но CSS говоритfont-family: Inter, sans-serif; font-size: 16px. Всё хорошо, если Inter загрузился. Если Inter 404, CSS откатывается к sans-serif, и измерения расходятся на 5–20%. Всегдаpreloadшрифт или используйте веб-безопасное семейство. -
Повторный prepare внутри цикла анимации. Только
layout*дешёвый. Повторный вызовprepareкаждый кадр убьёт производительность. Храните подготовленный результат в области видимости модуля. -
Забыли про
Intl.Segmenterдля разделения на графемы. Эмодзи, комбинируемые символы, CJK —"é".split("")даёт два символа. Используйтеnew Intl.Segmenter(undefined, { granularity: "grapheme" })при выборке отдельных видимых глифов. -
Чипы с
break: 'never'безextraWidth. Вrich-inline, если вы используетеbreak: 'never'для атомарного чипа/упоминания, вы также должны указатьextraWidthдля отступов пилюли — иначе хром чипа выйдет за пределы контейнера. -
Использование
@chenglou/pretextсunpkgи TypeScript-only точкой входа. Используйтеesm.sh— он автоматически компилирует TS-экспорты в готовый для браузера ESM.unpkgвернёт 404 или отдаст сырой TS. -
Моноширинные запасные варианты незаметно уничтожают всю суть. Пользователи, видящие моноширинный вывод, часто имеют CSS
font-family, который откатился доmonospace. Проверьте фактический отображаемый шрифт через DevTools. -
Пропуск строк вместо регулировки ширины при обтекании фигуры. Если коридор на этой строке слишком узок для размещения строки, пропустите строку (
y += lineHeight; continue;), а не передавайте крошечную maxWidth вlayoutNextLineRange— pretext вернёт строки из одной графемы, которые выглядят сломанными. -
Отправка холодного демо. Первый кадр по умолчанию выглядит как учебный. Добавьте: виньетку, тонкие линии развёртки, фоновое автоматическое движение, одно тщательно выбранное интерактивное действие (перетаскивание, наведение, прокрутка, клик). Без этого «крутое демо pretext» воспринимается как «внутреннее воспроизведение README».
Контрольный список проверки
- Демо — это один самодостаточный
.htmlфайл — открывается двойным кликом или черезpython3 -m http.server -
@chenglou/pretextимпортирован черезesm.shс фиксированной версией - Корпус текста — реальная проза, не lorem ipsum, и соответствует концепции демо
- Строка шрифта, переданная в
prepare, точно соответствует CSS-шрифту -
prepare()/prepareWithSegments()вызывается один раз, не каждый кадр - Тёмный фон + продуманная палитра — не белый canvas по умолчанию
- Как минимум одно интерактивное действие (перетаскивание / наведение / прокрутка / клик) или фоновое автоматическое движение
- Протестировано локально с
python3 -m http.serverи подтверждено отсутствие ошибок в консоли - 60fps на ноутбуке среднего класса (или задокументировано плавное снижение производительности)
- Одна деталь «сверх усилий», которую пользователь не просил
Справочник: Демо сообщества
Клонируйте их для вдохновения / паттернов (все под лицензией MIT-ish, ссылки с pretext.cool):
- Pretext Breaker — арканоид со словами-кирпичами —
github.com/rinesh/pretext-breaker - Tetris × Pretext —
github.com/shinichimochizuki/tetris-pretext - Dragon animation —
github.com/qtakmalay/PreTextExperiments - Somnai editorial engine —
github.com/somnai-dreams/pretext-demos - Bad Apple!! ASCII —
github.com/frmlinn/bad-apple-pretext - Drag-sprite reflow —
github.com/dokobot/pretext-demo - Alarmy editorial clock —
github.com/SmisLee/alarmy-pretext-demo
Официальная песочница: chenglou.me/pretext — аккордеон, пузырьки, динамическая верстка, редакторский движок, сравнение выравнивания, masonry, markdown-чат, rich-заметка.