Перейти к основному содержимому

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 для готовых однофайловых стартовых шаблонов.

Рабочий процесс​

  1. Выберите паттерн из таблицы выше на основе запроса пользователя.
  2. Начните с шаблона:
    • templates/hello-orb-flow.html — текст, обтекающий движущуюся сферу (паттерн обтекания препятствия)
    • templates/donut-orbit.html — продвинутый пример: измеренные препятствия ASCII-логотипа, перетаскиваемая проволочная сфера/куб, морфирующие поля фигур, выбираемый DOM-текст и элементы управления для разработчика
    • write_file в новый .html в /tmp/ или рабочем пространстве пользователя.
  3. Замените корпус текста на что-то осмысленное для задачи. Реальная проза, 10–100 предложений, без lorem.
  4. Настройте эстетику — шрифт, палитру, композицию, взаимодействие. Это основная работа; не пропускайте её.
  5. Проверьте локально:
    cd <директория-с-html> && python3 -m http.server 8765
    # затем откройте http://localhost:8765/<файл>.html
  6. Проверьте консоль — pretext выдаст ошибку, если prepareWithSegments вызван с некорректной строкой шрифта; Intl.Segmenter доступен во всех современных браузерах.
  7. Покажите пользователю путь к файлу, а не только код — он хочет его открыть.

Замечания по производительности​

  • prepare() / prepareWithSegments() — дорогой вызов. Делайте его один раз для каждой пары текст+шрифт. Кешируйте результат.
  • При изменении размера запускайте только layout() / layoutWithLines() — никогда не пересоздавайте prepare.
  • Для покадровых анимаций, где текст не меняется, но геометрия меняется, layoutNextLineRange в плотном цикле достаточно быстр для выполнения каждого кадра на 60fps для абзацев обычной длины.
  • При рендеринге ASCII-масок каждый кадр используйте буфер ячеек (Uint8Array/типизированные массивы), вычисляйте измеренные для каждой строки промежутки препятствий из ячеек или спроецированной геометрии, объединяйте промежутки, затем передавайте их в layoutNextLineRange перед рисованием текста.
  • Держите визуальную анимацию и анимацию верстки связанными. Если сфера превращается в куб, анимируйте (tween) как буфер отображаемых ячеек, так и промежутки препятствий с одним и тем же значением; иначе демо будет выглядеть как нарисованное, а не как физически перекомпонованное.
  • Для затуханий предпочитайте прозрачность слоя изменению интенсивности глифов или масштаба препятствий. Помещайте временные ASCII-спрайты на отдельный canvas и затухайте canvas с помощью CSS/GSAP opacity, чтобы геометрия не казалась сжимающейся.
  • Установка ctx.font на canvas на удивление медленная; устанавливайте её один раз за кадр, если шрифт не меняется, а не перед каждым вызовом fillText.

Частые ошибки​

  1. Расхождение строк шрифта CSS и canvas. ctx.font = "16px Inter" измерено, но CSS говорит font-family: Inter, sans-serif; font-size: 16px. Всё хорошо, если Inter загрузился. Если Inter 404, CSS откатывается к sans-serif, и измерения расходятся на 5–20%. Всегда preload шрифт или используйте веб-безопасное семейство.

  2. Повторный prepare внутри цикла анимации. Только layout* дешёвый. Повторный вызов prepare каждый кадр убьёт производительность. Храните подготовленный результат в области видимости модуля.

  3. Забыли про Intl.Segmenter для разделения на графемы. Эмодзи, комбинируемые символы, CJK — "é".split("") даёт два символа. Используйте new Intl.Segmenter(undefined, { granularity: "grapheme" }) при выборке отдельных видимых глифов.

  4. Чипы с break: 'never' без extraWidth. В rich-inline, если вы используете break: 'never' для атомарного чипа/упоминания, вы также должны указать extraWidth для отступов пилюли — иначе хром чипа выйдет за пределы контейнера.

  5. Использование @chenglou/pretext с unpkg и TypeScript-only точкой входа. Используйте esm.sh — он автоматически компилирует TS-экспорты в готовый для браузера ESM. unpkg вернёт 404 или отдаст сырой TS.

  6. Моноширинные запасные варианты незаметно уничтожают всю суть. Пользователи, видящие моноширинный вывод, часто имеют CSS font-family, который откатился до monospace. Проверьте фактический отображаемый шрифт через DevTools.

  7. Пропуск строк вместо регулировки ширины при обтекании фигуры. Если коридор на этой строке слишком узок для размещения строки, пропустите строку (y += lineHeight; continue;), а не передавайте крошечную maxWidth в layoutNextLineRange — pretext вернёт строки из одной графемы, которые выглядят сломанными.

  8. Отправка холодного демо. Первый кадр по умолчанию выглядит как учебный. Добавьте: виньетку, тонкие линии развёртки, фоновое автоматическое движение, одно тщательно выбранное интерактивное действие (перетаскивание, наведение, прокрутка, клик). Без этого «крутое демо 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-заметка.