Read in:
Русский

Одна папка, девять дверей: как собрано колесо позиционирования

О чём это: колесо позиционирования — страница на весь экран, где каждая вершина — один способ пользоваться trip2g. У неё нет отдельного бэкенда и нет сборки. Это папка заметок и один файл макета. Здесь — как она устроена и где система макетов закончилась.

У trip2g нет одной фразы для лифта, и я перестал её искать. Поэтому вместо фразы мне нужна была страница, где двери стоят рядом: сайт из Obsidian, блог в Telegram, платная база эксперта, память для агентов и остальное. Выбираешь вершину, колесо поворачивает её наверх, в центре — объяснение.

Одна заметка — одна точка

Каждая вершина — markdown-заметка в ru/circle/, а её английский двойник лежит в en/circle/:

title: "Блог в Telegram из Obsidian"
label: "блог в telegram"
order: 2
motion_visualization: channel
link: "[[ru/user/telegram-blog-from-obsidian]]"
related:
  - "[[ru/user/telegram]]"
lang: ru
lang_redirect: "[[en/circle/channel]]"

Тело заметки — те два-четыре предложения, что видны в центре. link — главная статья, related — мелкие ссылки под ней. lang и lang_redirect связывают двойников, как у любой двуязычной заметки на этом сайте.

Добавить точку — значит добавить заметку. Убрать — удалить файл. У многоугольника столько сторон, сколько заметок. Переход с восьми точек на девять не потребовал ничего менять в колесе; только новой анимации понадобилась своя строка со скриптом (об этом ниже).

Макет, который перечисляет свою папку

Заметка страницы ru/circle/_index.md говорит layout: mesh/circle. Макет находит свою папку по пути самой заметки и берёт из неё все заметки:

{{ points := nvs.ByGlob(dir + "*.md").SortByMeta("order").SortBy("Title").All() }}

Потом пропускает всё, у чего нет label, чтобы сама страница не стала вершиной. Ссылки ищутся по пути и печатаются с заголовком целевой заметки, так что сменил заголовок статьи — сменилась и подпись ссылки на колесе.

Текст для поисковиков, скрипт для колеса

Все девять описаний есть в HTML, который отдаёт сервер. Скрипт только переключает, какое из них видно, и двигает вершины. Без JavaScript страница — обычная стопка разделов, и именно это читает поисковик. Сами заметки-точки помечены noindex, а их канонический адрес указывает на колесо. Открыть такую, например /ru/circle/channel, — значит увидеть колесо, повёрнутое к этой точке.

Визуализации — ассеты макета

Каждая точка может назвать маленькую зацикленную анимацию. Каждая анимация — один файл рядом с макетом, circle/viz_channel.js и так далее, и каждая — чистая функция времени:

window.trip2gCircleViz.channel = function (ctx, t, w, h, colors) { … }

Раз картинка зависит только от t, «уменьшенное движение» достаётся даром: нарисовать один кадр и остановиться. Файлы уезжают на сервер обычной синхронизацией хранилища, как любой ассет макета.

Где упёрлось

  • Скрипт нельзя выбрать по имени во время работы. asset() регистрирует только статические строки. Поэтому в макете по строке <script> на каждую анимацию. Новая точка со старой анимацией — это просто заметка. Новая анимация — новый файл и новая строка в макете.
  • Нет dirname. Папку я получил, разрезав путь по / и отрезав последний кусок. Работает. Это три строки, которые должны быть одной функцией.
  • Запрос не умеет фильтровать по полю frontmatter. Фильтрую внутри цикла через if. Естественным фильтром был бы .Public(), но он отбрасывает noindex-заметки, а заметки-точки как раз такие.
  • Оболочка лендинга не подошла. Общая оболочка темы mesh жёстко ставит языковые ссылки на главную, поэтому эта страница пишет свой <head>.
  • Подписи внутри анимаций живут в скрипте. Трёхактная анимация про компанию подписывает акты по-русски или по-английски, глядя на язык страницы. Эти строки — в JavaScript, а не в заметке, и это единственное место, где правило «всё — заметка» гнётся.
  • Одну CSS-ловушку пришлось поискать. Плавное появление текста анимировало transform. С animation-fill-mode: both трансформация оставалась применённой и тихо становилась опорой для позиционирования холста внутри, и на телефонах анимация прыгала в середину текста. Помогло анимировать только прозрачность.

Зачем это всё

Страница — демонстрация того, о чём она рассказывает. Позиционирование живёт в заметках, которые я правлю в Obsidian, сервер их рендерит, а макет — несколько сотен строк в том же хранилище. Захочу завтра десятую дверь — это ещё одна заметка.