<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>taiga</title><link>https://justskiv.github.io/taiga/ru/</link><description>демо-сайт темы — каждый механизм проверен на себе</description><language>ru</language><atom:link href="https://justskiv.github.io/taiga/ru/index.xml" rel="self" type="application/rss+xml"/><item><title>Анатомия виджета</title><link>https://justskiv.github.io/taiga/ru/reference/widget-anatomy/</link><guid>https://justskiv.github.io/taiga/ru/reference/widget-anatomy/</guid><pubDate>Sat, 04 Jul 2026 00:00:00 +0300</pubDate><description>Страница-бандл-эталон: папка widgets/ для обоих классов виджета — статичного и интерактивного — плюс слой _shared, готовые к копированию.</description><content:encoded><![CDATA[<h2 id="classes"><a class="hash" href="#classes" aria-label="Якорь на раздел">#</a>Два класса виджета</h2>
<p>Виджет в папке <code>widgets/</code> этой темы — одно из двух.</p>
<p><strong>Статичный</strong> — только <code>figure.html</code>, и больше ничего. Он рендерится один раз,
на сборке, и остаётся таким навсегда; передавать эстафету некому — своего
<code>widget.js</code> у него нет. Вот весь виджет ниже:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— папка widgets/ этого самого бандла, нарисованная им же</span></figcaption>
<div id="w-anatomy-static" class="w-root"><div class="mem">
  <div class="mem-lab">widgets/ <span class="tot">→ this page bundle's own widget folder</span></div>

  <div class="mem-lab">_shared/ <span class="tot">shared across every widget on this page</span></div>
  <div class="byte-strip">
    <div class="byte-seg"><div class="cells"><div class="byte-box f1" data-tip="lib.js · the guard, plus one helper (h) in scope for every widget.js below">js</div></div><div class="seg-tag">lib.js</div></div>
    <div class="byte-seg"><div class="cells"><div class="byte-box f1" data-tip="shared.css · .wa-tag — used by this figure and by w-anatomy-toggle below">css</div></div><div class="seg-tag">shared.css</div></div>
  </div>

  <div class="mem-lab">w-anatomy-static/ <span class="tot">this widget — no JS, ever</span></div>
  <div class="byte-strip">
    <div class="byte-seg"><div class="cells"><div class="byte-box f0" data-tip="figure.html · this very file · the whole widget, rendered once at build time">fig</div></div><div class="seg-tag">figure.html</div></div>
  </div>

  <div class="mem-lab">w-anatomy-toggle/ <span class="tot">the widget below — JS takes over once it loads</span></div>
  <div class="byte-strip">
    <div class="byte-seg"><div class="cells"><div class="byte-box f0" data-tip="figure.html · the no-JS fallback, shown until widget.js runs">fig</div></div><div class="seg-tag">figure.html</div></div>
    <div class="byte-seg"><div class="cells"><div class="byte-box f1" data-tip="widget.js · registers itself with Taiga.widget('w-anatomy-toggle', fn)">js</div></div><div class="seg-tag">widget.js</div></div>
    <div class="byte-seg"><div class="cells"><div class="byte-box f2" data-tip="widget.css · one class only this widget uses">css</div></div><div class="seg-tag">widget.css</div></div>
  </div>

  <span class="wa-tag">static · no JS</span>
</div>
</div>
</figure>

<p><strong>Интерактивный</strong> — <code>widget.js</code>, который регистрирует себя через
<code>Taiga.widget(id, fn)</code>, обычно в паре с <code>figure.html</code>, который держит место до
запуска JS, и <code>widget.css</code> — на то, чего не хватает готовым классам темы
(<code>.w-row</code>/<code>.w-btn</code>/<code>.w-num</code>/<code>.w-cap</code>):</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— нажми: он называет файл, который сейчас управляет монтом</span></figcaption>
<div id="w-anatomy-toggle" class="w-root"><div class="w-row">
  <span class="wa-tag">figure.html</span>
</div>
<div class="w-cap">This is the static fallback. Turn on JS and click "toggle".</div>
</div>
</figure>

<p>Оба — один и тот же шорткод <code>{{&lt; widget id=&quot;…&quot; &gt;}}</code> без тела внутри — одного
id достаточно, чтобы шорткод сам пошёл искать <code>widgets/&lt;id&gt;/figure.html</code> как
ресурс страницы для монта. Другую форму — фолбэк прямо внутри шорткода — смотри
в «<a href="/taiga/ru/inside/anatomy/guide-is-a-bundle/" data-preview="/taiga/ru/inside/anatomy/guide-is-a-bundle/">Гайд — это папка</a>».</p>
<h2 id="shared"><a class="hash" href="#shared" aria-label="Якорь на раздел">#</a>Слой _shared</h2>
<p>Оба виджета выше подгружают <code>widgets/_shared/lib.js</code> и <code>widgets/_shared/shared.css</code>
раньше своих собственных файлов — сборка сортирует все <code>widgets/**.js</code> и
<code>widgets/**.css</code> бандла по имени, а <code>_shared</code> идёт раньше любой папки <code>w-*</code>. В
этом бандле конкретно:</p>
<ul>
<li><code>_shared/lib.js</code> держит единственный guard (<code>var W = window.Taiga; if (!W || …) return;</code>),
на который опирается каждый <code>widgets/&lt;id&gt;/widget.js</code> бандла, плюс крошечный
хелпер <code>h()</code>, который <code>w-anatomy-toggle/widget.js</code> вызывает без импорта и без
повторного объявления — все файлы склеиваются в один скрипт и делят одну область видимости.</li>
<li><code>_shared/shared.css</code> держит <code>.wa-tag</code> — маленькую пилюлю, которой оба виджета
выше подписывают имя файла. Она попала в <code>_shared</code> по самой непримечательной
причине: её реально используют два виджета на этой странице.</li>
</ul>
<p>Здесь ничто не разделено по необходимости — бандл с одним виджетом вполне мог
бы держать guard прямо в <code>widgets/&lt;id&gt;/widget.js</code>. Разделение сделано нарочно,
чтобы у страницы было на что показать <code>_shared/</code>. Бандл, где разделение
действительно оправдано, выглядит так же по форме, только тяжелее: общий
движок и его таблица спецификаций лежат в <code>_shared/lib.js</code>, а у каждого id,
который этот движок обслуживает, — своя папка <code>widgets/&lt;id&gt;/</code> под то, что
реально принадлежит только ему: фигуру, переопределение в <code>widget.css</code>, а
иногда и вовсе ничего.</p>
<h2 id="source"><a class="hash" href="#source" aria-label="Якорь на раздел">#</a>Читаем исходники</h2>
<p>Весь бандл, рядом с этим файлом:</p>
<span class="code-label">reference/widget-anatomy/ — этот бандл страницы</span>
<pre data-pagefind-weight="0.5"><code class="nohl">widget-anatomy/
  index.md, index.ru.md    ← этот текст на обоих языках
  widgets/
    _shared/
      lib.js               ← guard &#43; хелпер h()
      shared.css            ← .wa-tag
    w-anatomy-static/
      figure.html           ← весь виджет — без widget.js
    w-anatomy-toggle/
      figure.html           ← фолбэк без JS
      widget.js             ← Taiga.widget(&#39;w-anatomy-toggle&#39;, …)
      widget.css             ← .wa-hl, класс только этого виджета</code></pre><p>Любой файл в <code>widgets/</code> необязателен — кроме тех, что дают виджету суть:
статичному id нужен <code>figure.html</code>, интерактивному — <code>widget.js</code>, и любой
может добавить <code>widget.css</code>. Виджет может и вовсе обойтись без <code>figure.html</code>,
начав с пустого монта — именно так устроен <code>w-ks-counter</code> на странице
«<a href="/taiga/ru/reference/kitchen-sink/" data-preview="/taiga/ru/reference/kitchen-sink/">Kitchen sink</a>».</p>
]]></content:encoded></item><item><title>Горячие клавиши и панели</title><link>https://justskiv.github.io/taiga/ru/reference/hotkeys/</link><guid>https://justskiv.github.io/taiga/ru/reference/hotkeys/</guid><pubDate>Sat, 27 Jun 2026 00:00:00 +0300</pubDate><description>Справка по управлению читалкой: клавиши поиска и панелей, фокус-режим, поповер «Вид» и что запоминается локально.</description><content:encoded><![CDATA[<h2 id="keys"><a class="hash" href="#keys" aria-label="Якорь на раздел">#</a>Клавиши</h2>
<table>
  <thead>
      <tr>
          <th>клавиши</th>
          <th>действие</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><kbd class="kb">⌘K</kbd> или <kbd class="kb">/</kbd></td>
          <td>поиск по гайдам</td>
      </tr>
      <tr>
          <td><kbd class="kb">[</kbd> или <kbd class="kb">⌘1</kbd></td>
          <td>показать/скрыть панель серии (слева)</td>
      </tr>
      <tr>
          <td><kbd class="kb">]</kbd> или <kbd class="kb">⌘2</kbd></td>
          <td>показать/скрыть оглавление (справа)</td>
      </tr>
      <tr>
          <td><kbd class="kb">F</kbd></td>
          <td>фокус-режим: спрятать всё, кроме текста статьи</td>
      </tr>
      <tr>
          <td><kbd class="kb">Esc</kbd></td>
          <td>закрыть поиск или поповер</td>
      </tr>
  </tbody>
</table>
<p>Клавиши привязаны к физическим кодам, а не к символам — работают в любой раскладке, включая русскую. В полях ввода не срабатывают; фокус-режим действует только на страницах статей.</p>
<h2 id="panels"><a class="hash" href="#panels" aria-label="Якорь на раздел">#</a>Панели и минимапы</h2>
<p>Скрытая панель не исчезает совсем, а сворачивается в минимапу у края экрана: точки и чёрточки повторяют её содержимое. Наведение раскрывает панель поверх текста, там же её можно закрепить обратно. Прочитанные части серии получают метки — видно, где ты уже был.</p>
<h2 id="view-popover"><a class="hash" href="#view-popover" aria-label="Якорь на раздел">#</a>Поповер «Вид»</h2>
<p>Иконка в правом верхнем углу открывает настройки чтения: палитры со свотчами, включение и выключение боковых панелей, вариант вёрстки списков на страницах рубрик. Шапка отдельной настройки не имеет: она всегда уезжает при скролле вниз и возвращается при скролле вверх.</p>
<h2 id="storage"><a class="hash" href="#storage" aria-label="Якорь на раздел">#</a>Что запоминается</h2>
<p>Все настройки живут локально в браузере — без аккаунтов и без сети:</p>
<table>
  <thead>
      <tr>
          <th>настройка</th>
          <th>ключ localStorage</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>палитра</td>
          <td><code>taiga.theme</code></td>
      </tr>
      <tr>
          <td>панели слева/справа</td>
          <td><code>taiga.railL</code>, <code>taiga.railR</code></td>
      </tr>
      <tr>
          <td>вариант списков рубрики</td>
          <td><code>taiga.rubvar</code></td>
      </tr>
      <tr>
          <td>прочитанные гайды</td>
          <td><code>taiga.visited</code></td>
      </tr>
  </tbody>
</table>
<p>Очистка site data браузера возвращает всё к дефолтам — это штатный способ «сбросить настройки».</p>
]]></content:encoded></item><item><title>Сделать своей</title><link>https://justskiv.github.io/taiga/ru/howto/handbook/make-it-yours/</link><guid>https://justskiv.github.io/taiga/ru/howto/handbook/make-it-yours/</guid><pubDate>Sat, 20 Jun 2026 00:00:00 +0300</pubDate><description>Кастомизация без форка: палитра одним toml-файлом, custom.css, hooks-партиалы, собственный раздел и второй язык.</description><content:encoded><![CDATA[<h2 id="own-palette"><a class="hash" href="#own-palette" aria-label="Якорь на раздел">#</a>Палитра — один файл</h2>
<p>Фирменный цвет сайта — это toml-файл в <code>data/themes/</code> <strong>на стороне сайта</strong>:</p>
<span class="code-label">data/themes/mine.toml (фрагмент)</span>
<pre data-pagefind-weight="0.5"><code class="nohl">name = &#34;Моя&#34;      # имя в пикере
weight = 80       # позиция в списке пикера

&#34;bg-deep&#34; = &#34;#101418&#34;
&#34;bg-surface&#34; = &#34;#1a2027&#34;
&#34;text-primary&#34; = &#34;#d8dee6&#34;</code></pre><p>Hugo мержит данные темы и сайта (при совпадении имён побеждает сайт), поэтому файл сам попадает и в CSS, и в пикер палитр в шапке. Не нужна палитра из комплекта темы — переопредели её файл с <code>disabled = true</code>, и она исчезнет отовсюду. Как из toml получаются CSS-блоки и свотчи — разобрано в «<a href="/taiga/ru/inside/anatomy/palette-is-a-file/" data-preview="/taiga/ru/inside/anatomy/palette-is-a-file/">Палитра — это один файл</a>».</p>
<h2 id="css-hooks"><a class="hash" href="#css-hooks" aria-label="Якорь на раздел">#</a>custom.css и хуки</h2>
<p>Точечные правки поверх палитры — <code>assets/css/custom.css</code> сайта: если файл существует, тема подключает его последним в CSS-бандл, так что твои правила побеждают без <code>!important</code>. Дизайн-токены — публичный API: имена CSS-переменных стабильны и перечислены в <code>docs/customizing.md</code> темы.</p>
<p>Для аналитики, своих meta-тегов и сторонних скриптов есть hooks-партиалы: <code>hooks/head-extra.html</code> и <code>hooks/foot-extra.html</code> в теме пустые, сайт кладёт одноимённые файлы в свой <code>layouts/_partials/</code> — и их содержимое попадает в head и в конец body. А если нужно поменять сам рендер — любой партиал темы переопределяется одноимённым файлом сайта: lookup order Hugo всегда предпочитает файлы сайта. Мелкие именованные партиалы — логотип, футер, карточка ленты — существуют ровно для такого точечного переопределения.</p>
<h2 id="own-section"><a class="hash" href="#own-section" aria-label="Якорь на раздел">#</a>Свой раздел</h2>
<p>Понадобился раздел, которого в теме нет, — глоссарий, каталог, песочница с тренажёром. Для него не нужны ни новый layout, ни правки темы: это обычная страница или бандл, а интерактив — те же <code>widgets.js</code> рядом с текстом, механизм работает для любого бандла, не только для гайдов. Пункт меню добавляется в <code>[[menus.main]]</code> конфига; параметр <code>params.badge = &quot;β&quot;</code> у пункта рисует бейдж — это генерик-фича навигации, а не признак особого раздела.</p>
<p>На этом демо так собрана «<a href="/taiga/ru/playground/">Песочница</a>»: в теме от неё нет ни следа, весь раздел — три файла на стороне сайта.</p>
<h2 id="own-strings"><a class="hash" href="#own-strings" aria-label="Якорь на раздел">#</a>Свои строки</h2>
<p>Тексты интерфейса не захардкожены в шаблонах. Серверные строки живут в <code>i18n/*.toml</code>: сайт кладёт свой файл с частичным набором ключей, и он мержится с файлом темы — переопределяешь только нужное. Строки, живущие в JS — поповер «Вид», подсказки минимап, — переопределяются объектом <code>window.THEME_I18N</code>; список ключей — в <code>docs/customizing.md</code>.</p>
<h2 id="second-language"><a class="hash" href="#second-language" aria-label="Якорь на раздел">#</a>Второй язык</h2>
<p>Добавление языка — аддитивный шаг, а не миграция. В конфиг дописывается блок <code>[languages.en]</code> (плюс <code>[languages.en.params]</code> для hero-строки и <code>[languages.en.menus]</code> для меню). Переводы — суффиксами файлов: <code>index.en.md</code> рядом с <code>index.md</code>, включая <code>_index</code> рубрик и серий; Hugo связывает версии сам. Переключатель языков появляется в шапке, когда языков больше одного, а Pagefind строит отдельный индекс на каждый язык по <code>lang</code> страницы. Правок шаблонов — ноль; пошаговый чеклист — в <code>docs/i18n.md</code> темы.</p>
<div class="callout key">
<span class="clabel">Главная мысль</span>
<p>Инвариант всей этой части: кастомизация — это файлы твоего сайта. В каталог темы не попадает ни одна правка, поэтому обновление темы — <code>git pull</code> или бамп версии модуля, без ручного мержа своих изменений.</p>

</div>

<p><em>Теперь ты можешь увезти сайт сколь угодно далеко от этого демо — не форкая тему.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Руководство на этом закончено: тема стоит, гайды пишутся, сайт выглядит по-своему. Если интересно, почему это устроено именно так, — в «Устройстве» есть серия «Анатомия темы»: папка-бандл, арифметика серий, палитры-как-данные. А все компоненты разом собраны на полигоне в «Справочнике».</p>
]]></content:encoded></item><item><title>Написать первый гайд</title><link>https://justskiv.github.io/taiga/ru/howto/handbook/first-guide/</link><guid>https://justskiv.github.io/taiga/ru/howto/handbook/first-guide/</guid><pubDate>Sat, 13 Jun 2026 00:00:00 +0300</pubDate><description>Каркас гайда одной командой, канон front matter, шорткоды, подписи к коду и живой виджет за два касания.</description><content:encoded><![CDATA[<h2 id="scaffold"><a class="hash" href="#scaffold" aria-label="Якорь на раздел">#</a>Каркас одной командой</h2>
<span class="code-label">создать каркас гайда</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo new content guides/my-first --kind guides</span></span></code></pre></div></div><p>Архетип <code>guides</code> создаёт папку-бандл: <code>index.md</code> с заполненным каркасом front matter и заготовку <code>widgets.js</code> рядом. Файлы, которые лежат в папке гайда, тема подхватывает сама — подключать ничего не нужно.</p>
<div class="callout trap">
<span class="clabel">Ловушка</span>
<p>Путь в команде — без <code>/index.md</code>. Напишешь <code>guides/my-first/index.md</code> — Hugo переключится в режим одиночного файла и молча не скопирует соседние файлы архетипа. Папка без <code>widgets.js</code> — первый признак этой ошибки.</p>

</div>

<h2 id="front-matter"><a class="hash" href="#front-matter" aria-label="Якорь на раздел">#</a>Front matter: что заполнять</h2>
<span class="code-label">index.md — front matter</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Мой первый гайд&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Одно предложение — для ленты, поиска и meta.&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">lead</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Лид-абзац под заголовком; может быть длиннее description.&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">example, setup]</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="nt">mins</span><span class="p">:</span><span class="w"> </span><span class="m">4</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;v0.0.1&#34;</span></span></span></code></pre></div></div><p>Подсвечены два поля с неочевидными правилами. <code>mins</code> — рукописная честная оценка времени чтения: она попадает в чипы статьи и в арифметику серии, поэтому автоподсчёту не доверяется (опустишь поле — тема посчитает сама). <code>version</code> — произвольная строка чипа версии: версия языка, СУБД, твоего продукта — тема рендерит её как есть. Поля <code>date</code> и <code>slug</code> архетип тоже проставит; дата живёт только в ленте и RSS — в тексте статьи она не появляется нигде.</p>
<p>Частью серии гайд становится по расположению: его папка лежит внутри папки серии (<code>guides/track/my-part/</code>), а порядок среди частей задаёт <code>weight</code>; короткое имя для панели — <code>rail_title</code>. Как из этого получается «часть N из M», разобрано в «<a href="/taiga/ru/inside/anatomy/series-knows-itself/" data-preview="/taiga/ru/inside/anatomy/series-knows-itself/">Серия знает о себе всё</a>».</p>
<p>Заголовки размечай с явными якорями: <code>## Секция {#section-id}</code>, латиницей — тогда ссылки и оглавление не зависят от транслитерации кириллицы.</p>
<h2 id="shortcodes"><a class="hash" href="#shortcodes" aria-label="Якорь на раздел">#</a>Шорткоды</h2>
<p>Четыре, которых хватает на большинство гайдов:</p>
<ul>
<li><code>callout</code> — врезка; типы <code>key</code>, <code>trap</code>, <code>internals</code>, <code>note</code>;</li>
<li><code>widget</code> — точка монтирования живой иллюстрации (ниже);</li>
<li><code>raw</code> — многострочный HTML-фрагмент как есть, для диаграмм;</li>
<li><code>bigcard</code> — крупная ссылка-карточка на соседний материал.</li>
</ul>
<span class="code-label">врезка в тексте гайда</span>
<pre data-pagefind-weight="0.5"><code class="nohl">{{&lt; callout type=&#34;trap&#34; label=&#34;Ловушка&#34; &gt;}}
Внутри — обычный markdown.
{{&lt; /callout &gt;}}</code></pre><p>У код-блоков есть атрибуты прямо в фенсе: <code>```toml {label=&quot;config.toml (фрагмент)&quot;, hl_lines=[2]}</code> — <code>label</code> рисует подпись над блоком, <code>hl_lines</code> подсвечивает строки. Оба уже встречались в этой статье: подпись у каждого блока и подсветка в примере front matter сделаны именно так.</p>
<h2 id="widget-two-touches"><a class="hash" href="#widget-two-touches" aria-label="Якорь на раздел">#</a>Виджет за два касания</h2>
<p>Живая иллюстрация добавляется без правки темы. Касание первое — шорткод в тексте; внутрь кладётся статичный фолбэк, который останется у читателей без JS:</p>
<span class="code-label">касание 1 — шорткод в тексте</span>
<pre data-pagefind-weight="0.5"><code class="nohl">{{&lt; widget id=&#34;w-demo&#34; note=&#34;— задание читателю&#34; &gt;}}
&lt;p&gt;Статичный фолбэк для читателей без JS.&lt;/p&gt;
{{&lt; /widget &gt;}}</code></pre><p>Касание второе — инициализатор с тем же id в <code>widgets.js</code> рядом с <code>index.md</code>:</p>
<span class="code-label">касание 2 — widgets.js рядом с index.md</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">Taiga</span><span class="p">.</span><span class="nx">widget</span><span class="p">(</span><span class="s2">&#34;w-demo&#34;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">root</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">root</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// строишь виджет из строительных классов темы:
</span></span></span><span class="line"><span class="cl">  <span class="c1">// .w-row, .w-btn, .w-num, .w-cap — свои стили не нужны
</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Рантайм темы дождётся DOM, найдёт точку монтирования и вызовет колбэк в <code>try/catch</code>: упавший виджет напишет в консоль и не уронит страницу. Следи только за тем, чтобы id в шорткоде и в <code>Taiga.widget(…)</code> совпадали буква в букву.</p>
<p>Вот работающее доказательство — прикидка честного <code>mins</code> для будущего гайда:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— набери разделы будущего гайда и посмотри, какой mins будет честным</span></figcaption>
<div id="w-mins" class="w-root">
<div class="w-cap">Прикидка mins по числу разделов. Сейчас это статичный фолбэк: включи JS — и посчитаем.</div>
</div>
</figure>

<p>Он собран ровно как в примерах выше: шорткод — здесь, <code>Taiga.widget(&quot;w-mins&quot;, …)</code> — в <code>widgets.js</code> в папке этой статьи.</p>
<p><em>Теперь ты можешь написать гайд с живой иллюстрацией, не открыв ни одного файла темы.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Гайд есть, сайт собирается — но выглядит он пока как это демо, а не как твой проект. Третья часть — про палитру одним файлом, <code>custom.css</code>, собственные разделы и второй язык.</p>
]]></content:encoded></item><item><title>Поставить и запустить</title><link>https://justskiv.github.io/taiga/ru/howto/handbook/get-started/</link><guid>https://justskiv.github.io/taiga/ru/howto/handbook/get-started/</guid><pubDate>Sat, 06 Jun 2026 00:00:00 +0300</pubDate><description>Что делает тема, три способа её установить, минимальный конфиг и структура контента — от пустой папки до собранного сайта.</description><content:encoded><![CDATA[<h2 id="what-it-is"><a class="hash" href="#what-it-is" aria-label="Якорь на раздел">#</a>Что это за тема</h2>
<p>Тема для учебных гайдов, которые читают подряд, а не вразброс. Контент раскладывается в три уровня: рубрики → серии → гайды. Серия — это папка внутри рубрики: членство определяется тем, где лежит гайд; всё остальное — «часть N из M» в кикере, панель-оглавление слева, мост с «осталось ~N мин» внизу — считается на сборке. Гайд — это папка: рядом с текстом лежат его интерактивные виджеты, стили и обложка, и переносится он целиком, одной папкой. Всё, что видно на этом демо, — лента, поиск, палитры, генерация обложек — штатные механизмы темы: само демо собрано на ней же.</p>
<p>Из инструментов нужны два бинаря. Первый — обычный <code>hugo</code>, не extended: Sass в теме нет, а esbuild для JS встроен в стандартный бинарь. Второй — <code>pagefind</code>, полнотекстовый поиск по собранному сайту. Node, npm и <code>node_modules</code> не участвуют ни в сборке, ни в разработке. Минимальная версия Hugo объявлена в конфиге темы: слишком старый бинарь остановит сборку с внятной ошибкой, а не со странным поведением.</p>
<h2 id="three-ways"><a class="hash" href="#three-ways" aria-label="Якорь на раздел">#</a>Три способа установить</h2>
<p>Пути ниже — плейсхолдеры: подставь адрес репозитория темы.</p>
<p><strong>Hugo Module</strong> — основной способ. В конфиг сайта:</p>
<span class="code-label">hugo.toml — тема как модуль</span>
<pre data-pagefind-weight="0.5"><code class="nohl">[module]
  [[module.imports]]
    path = &#34;github.com/user/theme&#34;</code></pre><p>Затем <code>hugo mod get -u</code> (если сайт ещё не модуль — один раз <code>hugo mod init github.com/user/site</code>). Версии резолвятся по semver-тегам темы; для локальной правки темы — <code>replace</code> в <code>go.mod</code> сайта.</p>
<p><strong>Git submodule</strong> — если модули не хочется:</p>
<span class="code-label">тема сабмодулем</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git submodule add https://github.com/user/theme themes/theme</span></span></code></pre></div></div><p>и <code>theme = &quot;theme&quot;</code> в конфиге. В CI не забудь checkout с <code>submodules: recursive</code>.</p>
<p><strong>Копия в <code>themes/</code></strong> — клон или распакованный архив в <code>themes/theme</code> плюс тот же <code>theme = &quot;theme&quot;</code>. Обновления руками; зато способ годится, чтобы просто посмотреть тему без обязательств.</p>
<div class="callout key">
<span class="clabel">Главная мысль</span>
<p>Все три способа равноправны: тема держит ассеты в стандартных каталогах и не требует собирать саму себя. Разница только в том, как приезжают обновления.</p>

</div>

<h2 id="minimal-config"><a class="hash" href="#minimal-config" aria-label="Якорь на раздел">#</a>Минимальный конфиг</h2>
<span class="code-label">hugo.toml</span>
<pre data-pagefind-weight="0.5"><code class="nohl">baseURL = &#34;https://example.org/&#34;
title = &#34;Мой учебник&#34;
theme = &#34;theme&#34;                # не нужен при установке модулем
defaultContentLanguage = &#34;ru&#34;

[languages.ru]
  languageCode = &#34;ru&#34;
  languageName = &#34;Русский&#34;
  weight = 1

[taxonomies]
  tag = &#34;tags&#34;

[[menus.main]]
  name = &#34;Гайды&#34;
  url = &#34;/guides/&#34;
  weight = 10</code></pre><p>Блок <code>[languages.ru]</code> выглядит лишним, пока язык один, но он делает добавление второго языка аддитивным шагом, а не миграцией конфига. Остальное поведение настраивается через <code>[params]</code> — hero-строка главной, число видимых постов ленты, «гайд недели», палитра по умолчанию, — и у всего есть дефолты: сайт собирается и без единого параметра. Полный реестр — в <code>docs/params.md</code> темы.</p>
<h2 id="content-layout"><a class="hash" href="#content-layout" aria-label="Якорь на раздел">#</a>Структура контента</h2>
<span class="code-label">content/</span>
<pre data-pagefind-weight="0.5"><code class="nohl">content/
├── _index.md              # главная: hero-строка и данные
└── guides/
    ├── _index.md          # рубрика: label, lead
    ├── my-first/
    │   └── index.md       # одиночный гайд = папка (leaf bundle)
    └── track/             # серия = подпапка рубрики
        ├── _index.md      # метаданные серии: title, description
        └── part-one/
            └── index.md   # часть — та же папка гайда, уровнем глубже</code></pre><p>Имена секций — свои: тема оперирует «секциями», «сериями» и «гайдами» и не хардкодит ни названий рубрик, ни предметных слов. Учебник про базы данных и учебник про акварель раскладываются одинаково.</p>
<h2 id="build"><a class="hash" href="#build" aria-label="Якорь на раздел">#</a>Собрать</h2>
<span class="code-label">разработка и прод</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo server                        <span class="c1"># разработка</span>
</span></span><span class="line"><span class="cl">hugo <span class="o">&amp;&amp;</span> pagefind --site public     <span class="c1"># прод: сайт + поисковый индекс</span></span></span></code></pre></div></div><p>Команд две, потому что pagefind строит индекс по уже собранному сайту. В dev без индекса поиск не ломается: модалка тихо подсказывает, какой командой он собирается.</p>
<p><em>Теперь ты можешь развернуть сайт на теме с нуля и собрать его в один прогон — осталось его наполнить.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Пустой сайт собирается, но показывать пока нечего. Следующая часть — про первый гайд: каркас одной командой, канон front matter и живой виджет за два касания.</p>
]]></content:encoded></item><item><title>Kitchen sink: все компоненты темы</title><link>https://justskiv.github.io/taiga/ru/reference/kitchen-sink/</link><guid>https://justskiv.github.io/taiga/ru/reference/kitchen-sink/</guid><pubDate>Sat, 30 May 2026 00:00:00 +0300</pubDate><description>Регресс-полигон: все шорткоды, коллауты, код, диаграммы и виджеты на одной странице.</description><content:encoded><![CDATA[<h2 id="headings"><a class="hash" href="#headings" aria-label="Якорь на раздел">#</a>Заголовки и якоря</h2>
<p>Второго уровня — с решёткой <code>#</code>, третьего — с <code>##</code>. Инлайн: <em>курсив</em>, <strong>жирный</strong>,
<code>код</code>, внутренняя ссылка на <a href="/taiga/ru/inside/anatomy/guide-is-a-bundle/" data-preview="/taiga/ru/inside/anatomy/guide-is-a-bundle/">эталонный гайд</a>.</p>
<h3 id="sub"><a class="hash" href="#sub" aria-label="Якорь на подраздел">##</a>Подзаголовок третьего уровня</h3>
<p>Проверяем, что render-heading вешает <code>##</code> и якорь на h3.</p>
<h2 id="code"><a class="hash" href="#code" aria-label="Якорь на раздел">#</a>Код</h2>
<p>Go подсвечивается сервером (Chroma в цветах палитры):</p>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Point</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">X</span><span class="p">,</span><span class="w"> </span><span class="nx">Y</span><span class="w"> </span><span class="kt">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">dist</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="nx">Point</span><span class="p">)</span><span class="w"> </span><span class="kt">int64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">X</span><span class="o">-</span><span class="nx">b</span><span class="p">.</span><span class="nx">X</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">X</span><span class="o">-</span><span class="nx">b</span><span class="p">.</span><span class="nx">X</span><span class="p">)</span><span class="w"> </span><span class="p">}</span></span></span></code></pre></div></div><p>С подписью файла и подсветкой строки (<code>{label=… hl_lines=[2]}</code>):</p>
<span class="code-label">runtime/malloc.go (упрощено)</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">mallocgc</span><span class="p">(</span><span class="nx">size</span><span class="w"> </span><span class="kt">uintptr</span><span class="p">)</span><span class="w"> </span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">getMCache</span><span class="p">()</span><span class="w">          </span><span class="c1">// горячий путь — без блокировок</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="nx">size</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Остальные подсвеченные языки носят те же роли — синий у структурного имени,
медный у числа, свой тон у строк, приглушённый курсив у комментария:</p>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;items&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span> <span class="nt">&#34;shipped&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">101</span><span class="p">}</span>  <span class="c1">// хвостовой комментарий допустим
</span></span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GODEBUG</span><span class="o">=</span><span class="nv">schedtrace</span><span class="o">=</span><span class="m">1000</span>
</span></span><span class="line"><span class="cl">go build ./... <span class="o">&amp;&amp;</span> ./app --addr <span class="s2">&#34;</span><span class="nv">$HOST</span><span class="s2">:8080&#34;</span>   <span class="c1"># либо всё, либо ничего</span></span></span></code></pre></div></div><p>Голый забор, <code>text</code> и <code>txt</code> — плоская форма, <code>.nohl</code>: она и нужна выводу
команды:</p>
<pre data-pagefind-weight="0.5"><code class="nohl">$ go build ./...
ok  taiga/internals  0.312s</code></pre><h2 id="table"><a class="hash" href="#table" aria-label="Якорь на раздел">#</a>Таблица</h2>
<p>Markdown-таблица, обёртку <code>.tbl-wrap</code> вешает render-table:</p>
<table>
  <thead>
      <tr>
          <th>Тип</th>
          <th style="text-align: right">Размер</th>
          <th style="text-align: center">Выравнивание</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>bool</code></td>
          <td style="text-align: right">1</td>
          <td style="text-align: center">1</td>
      </tr>
      <tr>
          <td><code>int64</code></td>
          <td style="text-align: right">8</td>
          <td style="text-align: center">8</td>
      </tr>
      <tr>
          <td><code>[]T</code></td>
          <td style="text-align: right">24</td>
          <td style="text-align: center">8</td>
      </tr>
  </tbody>
</table>
<h2 id="callouts"><a class="hash" href="#callouts" aria-label="Якорь на раздел">#</a>Коллауты</h2>
<div class="callout key">
<span class="clabel">Главная мысль</span>
<p>Главная мысль с дефолтным лейблом. Внутри — <strong>markdown</strong> и <code>код</code>.</p>

</div>

<div class="callout trap">
<span class="clabel">Ловушка</span>
<p>Ловушка с дефолтным лейблом.</p>

</div>

<div class="callout internals">
<span class="clabel">Под капотом: свой лейбл</span>
<p>Коллаут «под капотом» с переопределённым лейблом.</p>

</div>

<div class="callout note">
<span class="clabel">Историческая сноска</span>
<p>Историческая сноска — самый тихий тип.</p>

</div>

<div class="callout warn">
<span class="clabel">Внимание</span>
<p>Внимание — самый громкий тип, красный.</p>

</div>

<h2 id="fold"><a class="hash" href="#fold" aria-label="Якорь на раздел">#</a>Fold</h2>
<p>Сворачиваемая врезка — значок, заголовок и переключатель; панель выезжает по
высоте. <code>title=</code> — инлайновый Markdown, тело — Markdown.</p>
<details class="fold" data-size="note">
<summary><svg class="fold-ic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M10 8.4v7.2l6-3.6z" fill="currentColor" stroke="none"/></svg><span class="fold-title">У этого гайда есть также <a href="https://youtu.be/dQw4w9WgXcQ" target="_blank" rel="noopener" class="lp-nb" data-link="yt" data-yt="https://youtu.be/dQw4w9WgXcQ">видео-версия</a>.</span>
<span class="fold-toggle"><span class="fold-more">Подробнее</span><span class="fold-less">Свернуть</span><svg class="fold-chev" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg></span>
</summary>
<div class="fold-body">
<button class="fold-rail" type="button" tabindex="-1" aria-hidden="true"></button>
<p>Сначала посмотрите, потом прочитайте для закрепления — что-то нагляднее на
экране, что-то лучше усваивается текстом. Ссылка выше открывает видео <strong>не</strong>
сворачивая панель.</p>


</div>
</details>
<details class="fold" data-size="note">
<summary><svg class="fold-ic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18h6"/><path d="M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.2 1 2.5h6c0-1.3.3-1.8 1-2.5A6 6 0 0 0 12 3z"/></svg><span class="fold-title">Упрощение, сделанное намеренно.</span>
<span class="fold-toggle"><span class="fold-more">Подробнее</span><span class="fold-less">Свернуть</span><svg class="fold-chev" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg></span>
</summary>
<div class="fold-body">
<button class="fold-rail" type="button" tabindex="-1" aria-hidden="true"></button>
<p>На самом деле всё чуть сложнее, но упрощённая модель верна в главном —
детали дальше.</p>


</div>
</details>
<details class="fold" data-size="note" open>
<summary><svg class="fold-ic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 18 4-4-4-4"/><path d="m8 6-4 4 4 4"/><path d="m13 5-2 14"/></svg><span class="fold-title">Полный листинг, сразу раскрытый.</span>
<span class="fold-toggle"><span class="fold-more">Показать код</span><span class="fold-less">Скрыть</span><svg class="fold-chev" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg></span>
</summary>
<div class="fold-body">
<button class="fold-rail" type="button" tabindex="-1" aria-hidden="true"></button>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nb">println</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">}</span></span></span></code></pre></div></div>

</div>
</details>
<p>Примерно за полэкрана текста кат сам меняет регистр: заголовок набирается
читательским кеглем, блок получает свой воздух, а панель закрывается явной
кнопкой — длинное отступление можно свернуть там, где кончилось чтение.
<code>size=</code> задаёт регистр принудительно; здесь — чтобы показать секцию, не
вставляя стену текста.</p>
<details class="fold" data-size="section">
<summary><svg class="fold-ic" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 7v13"/><path d="M3 5.5A1.5 1.5 0 0 1 4.5 4H9a3 3 0 0 1 3 3 3 3 0 0 1 3-3h4.5A1.5 1.5 0 0 1 21 5.5v11a1.5 1.5 0 0 1-1.5 1.5H14a2 2 0 0 0-2 2 2 2 0 0 0-2-2H4.5A1.5 1.5 0 0 1 3 16.5z"/></svg><span class="fold-title">Ликбез: отступление, выросшее в главу</span>
<span class="fold-toggle"><span class="fold-more">Развернуть</span><span class="fold-less">Свернуть</span><svg class="fold-chev" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg></span>
</summary>
<div class="fold-body">
<button class="fold-rail" type="button" tabindex="-1" aria-hidden="true"></button>
<p>Отступление на несколько экранов — уже не сноска, и набирать его двумя
кеглями ниже значит наказывать читателя за то, что он открыл: клик — это и
есть его согласие читать.</p>
<p>Поэтому раскрытая панель набрана прозой — тем же кеглем и цветом, что статья
вокруг, — а «это вставка» говорит структура: строка-заголовок, рельс слева и
строка-выход внизу.</p>
<p>Рельс — единственное, что переживает прокрутку. За первым экраном заголовок
уже уехал, и линия остаётся единственным признаком того, что вы всё ещё
внутри блока, который статья предложила пропустить.</p>

<button class="fold-end" type="button"><svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6l4.5 4.5L12.5 6"/></svg>Свернуть</button>
</div>
</details>
<h2 id="terms"><a class="hash" href="#terms" aria-label="Якорь на раздел">#</a>Термины</h2>
<p>Наведи на подчёркнутое слово — определение раскроется карточкой; клик её
закрепит, чтобы можно было спокойно читать и уйти по ссылке. Без JavaScript
все определения лежат списком в конце страницы, и каждое слово ведёт к своему.</p>
<p>Простейшая форма — слово и определение, тип по умолчанию:
у каждого P есть свой <a class="term" href="#term-a0747904c4" data-term="term-a0747904c4">mcache</a>, и попадание в него — самый дешёвый путь аллокации.</p>
<p>С ярлыком и цветом типа, и с ссылкой в подвале карточки:
компилятор вставляет <a class="term c-copper" href="#term-bd2d7d0010" data-term="term-bd2d7d0010">барьер записи</a> — и именно поэтому у записи указателя цена выше, чем у записи <code>int</code>.</p>
<p>Тело определения проходит тот же конвейер, что и статья, поэтому листинг
внутри карточки подсвечен ровно так же, как был бы в прозе:
путь пишется как <a class="term c-blue" href="#term-cf48d26a8c" data-term="term-cf48d26a8c">JSON Pointer</a>, а не как путь через точки.</p>
<p>Слово в тексте склоняется, а шапка карточки — нет; для этого <code>title=</code>:
данные лежат в <a class="term c-green" href="#term-c11452ecf2" data-term="term-c11452ecf2">кэшах ядра</a>, а не в оперативке.</p>
<h2 id="diagrams"><a class="hash" href="#diagrams" aria-label="Якорь на раздел">#</a>Диаграммы</h2>
<p>Диаграмма памяти и байтовая лента — сырой HTML через <code>{{&lt; raw &gt;}}</code>:</p>

<div class="mem">
  <div class="mem-lab"><code>Point{X, Y int64}</code> <span class="tot">→ 16 байт, ноль воздуха</span></div>
  <div class="byte-strip" aria-hidden="true">
    <div class="byte-seg"><div class="cells"><div class="byte-box f1" data-tip="X · int64 · 8 Б">0</div><div class="byte-box f1" data-tip="X · int64 · 8 Б">1</div></div><div class="seg-tag">X (int64)</div></div>
    <div class="byte-seg"><div class="cells"><div class="byte-box f2" data-tip="Y · int64 · 8 Б">8</div><div class="byte-box f2" data-tip="Y · int64 · 8 Б">9</div></div><div class="seg-tag">Y (int64)</div></div>
  </div>
</div>

<h2 id="widgets"><a class="hash" href="#widgets" aria-label="Якорь на раздел">#</a>Виджеты</h2>
<p>Форма 1 — пустой mount, оживляет <code>Taiga.widget</code> из <code>widgets.js</code>:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— жми, число тикает</span></figcaption>
<div id="w-ks-counter" class="w-root"></div>
</figure>

<p>Форма 2 — mount со статик-фолбэком внутри (виден без JS):</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— под ним статичная диаграмма</span></figcaption>
<div id="w-ks-fallback" class="w-root"><div class="mem"><div class="mem-lab">статик-фолбэк</div><div class="mem-row"><div class="word live" data-tip="виден без JS">e0</div></div></div></div>
</figure>

<p>Форма 3 — без id, произвольный HTML прямо в figure:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— свой HTML, без mount</span></figcaption>
<div class="w-row"><span class="w-cap">произвольная разметка виджета</span></div>
</figure>

<h2 id="card"><a class="hash" href="#card" aria-label="Якорь на раздел">#</a>Карточка</h2>
<a class="bigcard" href="/taiga/ru/inside/anatomy/guide-is-a-bundle/">
<span class="bc-k">К эталону →</span>
<span class="bc-t">Гайд — это папка</span>
<span class="bc-s">Большая CTA-карточка: kicker, заголовок, подпись.</span>
</a>

<h2 id="link-previews"><a class="hash" href="#link-previews" aria-label="Якорь на раздел">#</a>Превью ссылок</h2>
<p>На точном курсоре наведи на любую ссылку ниже — появится карточка-превью:
физика терм-карточки, один поповер, собранный из билд-фрагмента (внутренние
ссылки) или из пререндеренной карточки (Telegram, YouTube).</p>
<p>Внутренние ссылки открывают окно в страницу: превью целого
<a href="/taiga/ru/inside/anatomy/guide-is-a-bundle/" data-preview="/taiga/ru/inside/anatomy/guide-is-a-bundle/">гайда</a>, <a href="/taiga/ru/inside/anatomy/guide-is-a-bundle/#bundle" data-preview="/taiga/ru/inside/anatomy/guide-is-a-bundle/">якорь на раздел</a>,
который сужает карточку до одного раздела, и <a href="/taiga/ru/inside/anatomy/" data-preview="/taiga/ru/inside/anatomy/">оглавление серии</a>
со списком частей.</p>
<p>Telegram — пост из авторского канала получает карточку, собранную на билде:
<a href="https://t.me/ntuzov/409" target="_blank" rel="noopener" class="lp-nb" data-link="tg" data-tg="https://t.me/ntuzov/409"><span class="lp-w">пост </span>409</a> и <a href="https://t.me/ntuzov/410" target="_blank" rel="noopener" class="lp-nb" data-link="tg" data-tg="https://t.me/ntuzov/410"><span class="lp-w">пост </span>410</a>.
<a href="https://t.me/ntuzov/99999" target="_blank" rel="noopener" class="lp-nb" data-link="tg" data-tg="https://t.me/ntuzov/99999"><span class="lp-w">Несуществующий </span>пост</a> деградирует до карточки канала,
а <a href="https://t.me/telegram/999" target="_blank" rel="noopener" class="lp-nb" data-link="tg"><span class="lp-w">чужой </span>канал</a> получает только значок ✈, без карточки.</p>
<p>YouTube — <a href="https://www.youtube.com/watch?v=kedW1xO3Zbo" target="_blank" rel="noopener" class="lp-nb" data-link="yt" data-yt="https://www.youtube.com/watch?v=kedW1xO3Zbo"><span class="lp-w">доклад про планировщик </span>Go</a>
открывает видео-карточку с превью-кадром, длительностью и числом просмотров.</p>
<p>Обычная <a href="https://go.dev/blog/" target="_blank" rel="noopener" class="lp-nb" data-link="ext"><span class="lp-w">внешняя </span>ссылка</a> несёт только значок ↗.</p>
<p>Википедия — <a href="https://en.wikipedia.org/wiki/Work_stealing" target="_blank" rel="noopener" class="lp-nb" data-link="wiki" data-wiki="https://en.wikipedia.org/wiki/Work_stealing"><span class="lp-w">work-stealing </span>планировщик</a>
и <a href="https://ru.wikipedia.org/wiki/%d0%92%d1%8b%d1%82%d0%b5%d1%81%d0%bd%d1%8f%d1%8e%d1%89%d0%b0%d1%8f_%d0%bc%d0%bd%d0%be%d0%b3%d0%be%d0%b7%d0%b0%d0%b4%d0%b0%d1%87%d0%bd%d0%be%d1%81%d1%82%d1%8c" target="_blank" rel="noopener" class="lp-nb" data-link="wiki" data-wiki="https://ru.wikipedia.org/wiki/Вытесняющая_многозадачность"><span class="lp-w">вытесняющая </span>многозадачность</a>
открывают карточку-сводку из REST API Wikimedia, со значком W.</p>
<p>Блог Go — <a href="https://go.dev/blog/loopvar-preview" target="_blank" rel="noopener" class="lp-nb" data-link="gob" data-gob="https://go.dev/blog/loopvar-preview"><span class="lp-w">превью области видимости переменной </span>цикла</a>
и классический <a href="https://go.dev/blog/error-handling-and-go" target="_blank" rel="noopener" class="lp-nb" data-link="gob" data-gob="https://go.dev/blog/error-handling-and-go"><span class="lp-w">error handling in </span>Go</a>
открывают карточку с курсивным значком Go, байлайном и зачином поста.</p>
<h2 id="newsletter"><a class="hash" href="#newsletter" aria-label="Якорь на раздел">#</a>Рассылка</h2>
<p>Блоки подписки — на сайте, где рассылка включена (<code>params.newsletter.enable</code>,
см. <a href="https://github.com/justskiv/taiga/blob/main/docs/ru/newsletter.md" target="_blank" rel="noopener" class="lp-nb" data-link="ext">newsletter.md</a>).
За ними в демо нет эндпоинта, поэтому отправка заканчивается состоянием ошибки —
на неё как раз и стоит посмотреть.</p>
<p>Срединный блок, поставленный руками, с крестиком (<code>dismiss=</code>), решение по
которому запоминается по блоку:</p>
<aside class="nl-inline" aria-label="Подписка на рассылку" data-pagefind-ignore data-nl-dismiss="ks-demo" tabindex="-1">
  <div class="nl-label">серия · продолжение</div>
  <div class="nl-txt"><p>Здесь гайд приводит свой довод в пользу письма — своим голосом, а не голосом темы.</p>
</div>
  <div class="nl-unit">
  <form class="nl-form" novalidate>
    <label class="nl-vh" for="nl-email-mid-18">Адрес почты</label>
    <input class="nl-field" id="nl-email-mid-18" type="email" name="email" inputmode="email" autocomplete="email" spellcheck="false" placeholder="your@mail.com" required>
    <span class="nl-hp" aria-hidden="true"><input type="text" name="website" tabindex="-1" autocomplete="off"></span>
    <button class="nl-go" type="submit"><span class="nl-spin" aria-hidden="true"></span><span class="nl-go-t">Подписаться</span></button>
  </form>
  <p class="nl-note">Демо-форма: за ней нет эндпоинта.</p>
  <p class="nl-msg" role="status" aria-live="polite"></p>
</div>

  
  <button class="nl-x" type="button" aria-label="Свернуть этот блок" title="Свернуть этот блок"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="m5 5 14 14M19 5 5 19"/></svg></button>
  
  <p class="nl-ghost" aria-label="Свёрнутая форма подписки" hidden>Форма подписки свёрнута — <button class="nl-ghost-a" type="button" title="Вернуть форму подписки">вернуть</button></p>
</aside>
<p>Голая форма для страницы, написанной прозой (<code>newsletter-inline</code>) — тот же
компонент, что стоит на странице подписки:</p>
<div class="nl-prose" data-pagefind-ignore>
  <div class="nl-unit">
  <form class="nl-form" novalidate>
    <label class="nl-vh" for="nl-email-prose-19">Адрес почты</label>
    <input class="nl-field" id="nl-email-prose-19" type="email" name="email" inputmode="email" autocomplete="email" spellcheck="false" placeholder="your@mail.com" required>
    <span class="nl-hp" aria-hidden="true"><input type="text" name="website" tabindex="-1" autocomplete="off"></span>
    <button class="nl-go" type="submit"><span class="nl-spin" aria-hidden="true"></span><span class="nl-go-t">Подписаться</span></button>
  </form>
  <p class="nl-note">Демо-форма: за ней нет эндпоинта.</p>
  <p class="nl-msg" role="status" aria-live="polite"></p>
</div>

</div>
<p>Карточка в подвале этой страницы — третий вариант, <code>newsletter-cta</code>; здесь её
вставил автоматически <code>placements.articleEnd</code>.</p>
]]></content:encoded></item><item><title>Обложки, поиск и другая магия сборки</title><link>https://justskiv.github.io/taiga/ru/howto/standalone/build-magic/</link><guid>https://justskiv.github.io/taiga/ru/howto/standalone/build-magic/</guid><pubDate>Sat, 23 May 2026 00:00:00 +0300</pubDate><description>Что тема делает на сборке: OG-обложки из папки-стиля, полнотекстовый поиск Pagefind и чип версии.</description><content:encoded><![CDATA[<h2 id="og-covers"><a class="hash" href="#og-covers" aria-label="Якорь на раздел">#</a>Обложка без дизайнера</h2>
<p>Наивная модель: обложки для шаринга рисуют руками. На тридцатой статье это боль, а после переименования заголовка — боль вдвойне: картинка теперь врёт, перерисовывай.</p>
<p>Здесь обложки генерирует сборка, а внешний вид описывает <strong>стиль</strong> — папка в <code>assets/og/</code>, имя папки = ключ стиля. Внутри два обязательных файла: <code>base.png</code> — подложка 1200×630, где живёт вся графика, и <code>layout.toml</code> — раскладка текстовых блоков поверх неё:</p>
<span class="code-label">assets/og/dots/layout.toml (фрагмент)</span>
<pre data-pagefind-weight="0.5"><code class="nohl">[[block]]
text = &#34;{title}&#34;
font = &#34;Inter-ExtraBold.ttf&#34;
size = 66
color = &#34;#e6dfd2&#34;
x = 76
y = 270
width = 1010     # автоперенос длинных заголовков</code></pre><p>Плейсхолдеры — <code>{title}</code>, <code>{kicker}</code>, <code>{meta}</code>, <code>{domain}</code>, <code>{part}</code> — собирает партиал, так что стиль языко-нейтрален: тексты приходят из статьи, раскладка — из стиля. Блоки рисуются по порядку; неизвестные ключи игнорируются — старый стиль переживёт новую версию темы. Опциональный <code>home.png</code> — полностью запечённый постер для главной и рубрик; если его нет, общая обложка генерируется тем же пайплайном из <code>base.png</code>. А папка без <code>layout.toml</code> стилем просто не считается.</p>
<p>Выбирается стиль параметром сайта <code>ogImages.style</code>, для отдельной страницы — <code>og_style</code> во front matter. А поверх любого стиля работает авторский оверрайд: положи <code>og.png</code> в бандл статьи — и генератор уступит место твоей картинке. Свой стиль сайт добавляет папкой: ассеты темы и сайта мержатся, сайт побеждает.</p>
<div class="callout trap">
<span class="clabel">Ловушка</span>
<p>Текст на обложке рисует движок картинок Hugo, и у него есть пределы: межбуквенный интервал не поддерживается (аппроксимируется кеглем), а выравнивание вправо работает только для моноширинных шрифтов — ширина строки считается по числу символов. Дизайнь стили с этим в голове, а не выясняй на готовой обложке.</p>

</div>

<h2 id="search"><a class="hash" href="#search" aria-label="Якорь на раздел">#</a>Поиск, который читает всё</h2>
<p>Наивная модель поиска на статике — индекс из заголовков и описаний: он найдёт статью по названию и промолчит про термин из середины текста. Здесь движок другой: <strong>Pagefind</strong> строит полнотекстовый индекс по уже собранному сайту. Поэтому сборка — две команды:</p>
<span class="code-label">полная сборка сайта</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo <span class="o">&amp;&amp;</span> pagefind --site public</span></span></code></pre></div></div><p>Pagefind — standalone-бинарь, Node не нужен. Он проходит по готовому HTML и берёт только то, что размечено темой: тело статьи — целиком, крошки и минуты для карточки результата — отдельными метаданными, а панели, виджеты и прочий хром в индекс не попадают вовсе. Код-блоки индексируются с пониженным весом: поиск по имени функции обязан работать, но код не должен забивать выдачу. Русская морфология — из коробки, по языку страницы: запрос «обложек» найдёт «обложка». Скрипт поиска и сам индекс грузятся лениво, при первом открытии модалки: пока не искал — не скачивал ни байта.</p>
<p>Проверь на этой странице: открой поиск и набери слово из середины текста — например, «кегль». Найдётся, с подсветкой и выдержкой. А если запустить голый <code>hugo server</code> без второй команды, модалка честно подскажет, чем индекс собирается, — тихо, без красных ошибок.</p>
<div class="callout key">
<span class="clabel">Главная мысль</span>
<p>Все три механизма — build-time. Браузеру достаются готовая картинка, готовый индекс и готовая строка; в рантайме никто ничего не вычисляет и ни к каким сервисам не ходит.</p>

</div>

<h2 id="version-chip"><a class="hash" href="#version-chip" aria-label="Якорь на раздел">#</a>Чип версии</h2>
<p>В мете этой статьи стоит чип «v0.0.1». Тема понятия не имеет, что это значит: чип — произвольная строка из <code>version</code> во front matter, а если поля нет — из <code>versionDefault</code> сайта. Сайт про язык программирования напишет туда версию языка, сайт про базу данных — «PostgreSQL 17», а это демо пишет версию самой темы — и тема рендерит всё одинаково, как есть. Маленькая деталь, но именно из таких складывается агностичность: тема не знает, о чём твой сайт.</p>
<p><em>Теперь ты можешь собрать сайт с обложками и полнотекстовым поиском двумя командами — и объяснить, почему команд именно две.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Это был взгляд снаружи — что сборка делает для читателя. Взгляд изнутри — как устроены сами страницы — в серии «Анатомия темы»: про папки-бандлы, арифметику серий и палитры-файлы.</p>
]]></content:encoded></item><item><title>Палитра — это один файл</title><link>https://justskiv.github.io/taiga/ru/inside/anatomy/palette-is-a-file/</link><guid>https://justskiv.github.io/taiga/ru/inside/anatomy/palette-is-a-file/</guid><pubDate>Sat, 16 May 2026 00:00:00 +0300</pubDate><description>Палитры как данные: toml-файл в data/themes/, из которого сборка делает CSS-блоки и свотчи пикера.</description><content:encoded><![CDATA[<h2 id="no-fork"><a class="hash" href="#no-fork" aria-label="Якорь на раздел">#</a>Своя тема без форка</h2>
<p>Наивная модель: хочу перекрасить сайт под себя — открою CSS темы и поменяю цвета. Боль наступает при первом же обновлении темы: твои правки лежат в её файлах, и каждый апдейт — это merge-конфликт. Классический выбор «форкни и отстань от апстрима — или терпи чужие цвета». Оба варианта плохие, и оба растут из одной ошибки: цвет считается <em>оформлением</em>, хотя на самом деле это <em>данные</em> — таблица «имя переменной → значение», которой просто негде лежать, кроме чужого CSS.</p>
<h2 id="one-toml"><a class="hash" href="#one-toml" aria-label="Якорь на раздел">#</a>Один toml</h2>
<p>Здесь палитра — это данные, а не стили. Каждая — файл <code>data/themes/&lt;id&gt;.toml</code>: пара служебных полей плюс цветовые переменные, у которых ключи — буквально имена CSS-переменных без <code>--</code>:</p>
<span class="code-label">data/themes/mine.toml</span>
<pre data-pagefind-weight="0.5"><code class="nohl">name = &#34;Моя&#34;      # имя в пикере
weight = 80       # позиция в списке пикера

&#34;bg-deep&#34; = &#34;#101418&#34;
&#34;bg-surface&#34; = &#34;#1a2027&#34;
&#34;border&#34; = &#34;#2b3138&#34;
&#34;text-primary&#34; = &#34;#d8dee6&#34;</code></pre><p>Подсвечены единственные две строки, которые не являются CSS-переменными. Всё остальное — прямая таблица «имя → цвет». Ключевые из них:</p>
<table>
  <thead>
      <tr>
          <th>Ключ</th>
          <th>Что красит</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>bg-deep</code></td>
          <td>самый глубокий фон страницы</td>
      </tr>
      <tr>
          <td><code>bg-surface</code></td>
          <td>поверхности: карточки, коллауты, виджеты</td>
      </tr>
      <tr>
          <td><code>border</code></td>
          <td>рамки и хайрлайны</td>
      </tr>
      <tr>
          <td><code>text-primary</code></td>
          <td>основной текст</td>
      </tr>
      <tr>
          <td><code>text-muted</code></td>
          <td>подписи, меты, второстепенное</td>
      </tr>
  </tbody>
</table>
<p>На сборке партиал <code>head/palettes.html</code> превращает эти файлы в CSS: блок <code>:root { … }</code> для палитры по умолчанию плюс по блоку <code>[data-theme=&quot;&lt;id&gt;&quot;] { … }</code> на каждую — и вливает результат первым в общий CSS-бандл. Какая палитра дефолтная — решает параметр сайта <code>defaultTheme</code>: это просто id одного из файлов. Переключение темы — смена одного атрибута <code>data-theme</code> на <code>&lt;html&gt;</code>; каскад делает остальное. Поэтому же смена мгновенная: браузеру не нужно ничего дозагружать, все палитры уже в CSS, который он получил с первым байтом страницы.</p>
<div class="callout note">
<span class="clabel">Историческая сноска</span>
<p>В прототипе темы палитры жили JS-таблицей: поповер «Вид» держал массив цветов и подменял переменные через <code>style.setProperty</code> прямо на <code>&lt;html&gt;</code>. При переезде таблицу разобрали на toml-файлы, а генерацию CSS увезли на сборку. Теперь JS значений палитр не знает вовсе — он только читает их список.</p>

</div>

<h2 id="picker-json"><a class="hash" href="#picker-json" aria-label="Якорь на раздел">#</a>Пикер и его JSON</h2>
<p>Откуда пикер знает, какие палитры есть? Из тех же данных: партиал кладёт на страницу <code>&lt;script type=&quot;application/json&quot; id=&quot;dg-themes&quot;&gt;</code> со списком <code>{id, name, group, weight, свотч-цвета}</code> — свотчи собираются из четырёх фиксированных ключей палитры (<code>bg-deep</code>, <code>bg-surface</code>, <code>text-primary</code>, <code>text-muted</code>), так что миниатюра в пикере честно показывает будущий холст, карточку и текст. Поповер в шапке читает этот список и рисует кнопки. Виджет ниже читает <strong>тот же самый</strong> список — и по клику ставит тот же самый атрибут:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— перекрась страницу; пикер в шапке делает ровно то же, только ещё запоминает выбор</span></figcaption>
<div id="w-palette" class="w-root">
<div class="w-cap">Здесь будут свотчи всех палитр темы — кликабельные, с настоящим переключением. Включи JS, чтобы перекрасить страницу.</div>
</div>
</figure>

<p>Заметь честную разницу: виджет меняет только атрибут, а пикер ещё и записывает выбор в хранилище — поэтому после перезагрузки победит его версия. И применяется сохранённый выбор до первой отрисовки, крошечным инлайн-скриптом в <code>&lt;head&gt;</code>: страница ни на кадр не вспыхивает дефолтной палитрой.</p>
<p>Теперь главное — расширяемость. Сайту нужна фирменная палитра? Положи <code>data/themes/mine.toml</code> <strong>на стороне сайта</strong>: Hugo мержит данные темы и сайта (сайт побеждает при совпадении имён), и файл сам попадёт и в CSS, и в пикер. Не нравится палитра из комплекта темы — переопредели её файл с <code>disabled = true</code>, и она исчезнет отовсюду. Ни строчки в теме, ни форка.</p>
<p><em>Теперь ты можешь добавить сайту собственную палитру одним файлом — не трогая ни CSS, ни JS, ни шаблоны темы.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Серия «Анатомия темы» на этом закончена: гайд — папка, серия — папка гайдов, палитра — файл. Всё показанное — и десяток компонентов, до которых мы не дошли, — собрано на одной странице-полигоне:</p>
<a class="bigcard" href="/taiga/ru/reference/kitchen-sink/">
<span class="bc-k">Полигон →</span>
<span class="bc-t">Kitchen-sink</span>
<span class="bc-s">Все компоненты темы на одной странице — смотреть и копировать</span>
</a>

<p>А про то, что происходит на сборке — обложки, поиск, чип версии, — есть отдельный разбор в «Приёмах».</p>
]]></content:encoded></item><item><title>Серия знает о себе всё</title><link>https://justskiv.github.io/taiga/ru/inside/anatomy/series-knows-itself/</link><guid>https://justskiv.github.io/taiga/ru/inside/anatomy/series-knows-itself/</guid><pubDate>Sat, 09 May 2026 00:00:00 +0300</pubDate><description>Механика серий: серия — это папка в рубрике — и кикер «часть N из M», панель и мост считаются на сборке сами.</description><content:encoded><![CDATA[<h2 id="links-by-hand"><a class="hash" href="#links-by-hand" aria-label="Якорь на раздел">#</a>Оглавление руками</h2>
<p>Наивная модель: серия — это когда в конце каждой статьи стоит список ссылок на остальные. Работает ровно до второго изменения. Добавил четвёртую часть — иди обнови три старых статьи. Переставил части местами — «часть 3 из 7» в заголовках теперь врёт, и хорошо, если ты вспомнил про все семь. Оглавление, размазанное по статьям, устаревает в момент любой правки — потому что источников правды столько же, сколько частей.</p>
<p>Хуже того: половина серийной механики вообще не выражается ссылками руками. «Осталось ~9 минут до конца серии» — это арифметика по всем частям сразу; ни одна статья в одиночку такого про себя не знает.</p>
<h2 id="membership"><a class="hash" href="#membership" aria-label="Якорь на раздел">#</a>Папка, а не поле</h2>
<p>Здесь у серии один источник правды. Часть почти ничего о себе не объявляет — она просто лежит в папке серии, под-секции рубрики:</p>
<span class="code-label">content/inside/ — папка этой самой серии</span>
<pre data-pagefind-weight="0.5"><code class="nohl">content/inside/
  anatomy/                  ← серия
    _index.md               ← метаданные серии: title, description, порядок
    guide-is-a-bundle/      ← часть, weight: 10
    series-knows-itself/    ← часть, weight: 20 — ты здесь
    palette-is-a-file/      ← часть, weight: 30</code></pre><p>Это папка статьи, которую ты сейчас читаешь, — можешь сверить с кикером над заголовком. Членство — это расположение; единственное, что часть несёт сама, — <code>weight</code> во front matter, её порядок среди частей. Всё остальное вычисляется на сборке: части сортируются по <code>weight</code>, номер «N» — позиция в отсортированном списке, «M» — его длина. Заметь: порядок задаёт вес, а не даты публикации — переставить части местами можно, не переписывая историю ленты.</p>
<p>Из того же отсортированного списка собирается всё серийное на странице. Кикер склеивается из трёх независимых источников: метка рубрики (front matter секции), заголовок серии (front matter её <code>_index.md</code>) и вычисленное «часть N из M». Панель слева — оглавление с отметкой текущей части. Мост внизу — соседи prev/next плюс шкала прогресса. Один список — четыре потребителя.</p>
<p>Вот как выглядит наша серия глазами сборки:</p>

<div class="mem">
  <div class="mem-lab">inside/anatomy/ <span class="tot">→ 3 части · ~14 мин суммарно</span></div>
  <div class="header">
    <div class="header-word ptr" data-tip="folder · слаг серии, латиницей: anatomy"><span class="wk">folder</span><span class="wv">→</span></div>
    <div class="header-word" data-tip="частей в серии: 3"><span class="wk">parts</span><span class="wv">3</span></div>
    <div class="header-word" data-tip="сумма mins всех частей: 14"><span class="wk">mins</span><span class="wv">14</span></div>
  </div>
  <div class="mem-arrow">│ folder ▼</div>
  <div class="mem-row">
    <div class="word ro" data-tip="часть 1 · «Гайд — это папка» · weight 10 · ~5 мин">w10</div>
    <div class="word live" data-tip="часть 2 · эта самая статья · weight 20 · ~4 мин">w20</div>
    <div class="word spare" data-tip="часть 3 · «Палитра — это один файл» · weight 30 · ~5 мин">w30</div>
  </div>
</div>

<p>Подсвеченная ячейка — статья, внутри которой ты находишься. Наведи на соседние: у каждой в тултипе её вес и минуты.</p>
<p>Заголовок серии, описание и порядок на странице рубрики — тоже не магия: это front matter <code>_index.md</code> её папки — здесь это <code>content/inside/anatomy/_index.md</code> (каркас создаётся командой <code>hugo new content inside/anatomy/_index.md --kind series</code>). Тот же файл даёт серии <strong>лендинг</strong> по адресу <code>/inside/anatomy/</code>: заголовок с тэглайном, описание, тело самого файла как эпиграф, список частей и кнопку «начать серию». Ссылки «вся серия» в панели и мосте ведут именно туда; на странице рубрики та же серия стоит блоком с якорем. А серия, анонсированная <code>_index.md</code> без единой части, — не ошибка: рубрика покажет её тизером «готовится», а лендинг отрендерит анонс.</p>
<div class="callout internals">
<span class="clabel">Под капотом</span>
<p>Контекст серии разрешает хелпер-партиал <code>article/series-ctx.html</code>: серия — это просто <code>.CurrentSection</code> страницы, когда та лежит глубже своей рубрики, а части — обычные страницы этой секции, по возрастанию <code>weight</code> — дефолтная сортировка Hugo. Мост берёт оттуда позицию: prev/next — соседи по списку, шкала — <code>flex-grow: mins</code> каждой части, «осталось ~N мин» — сумма <code>mins</code> частей после текущей.</p>

</div>

<h2 id="check-math"><a class="hash" href="#check-math" aria-label="Якорь на раздел">#</a>Проверь арифметику</h2>
<p>Формула моста настолько короткая, что её можно пересчитать прямо здесь. Виджет ниже гоняет ту же логику по числам этой самой серии:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— поставь себя на часть 3 и посмотри, что мост скажет про остаток</span></figcaption>
<div id="w-series-math" class="w-root">
<div class="w-cap">Слайдер «ты на части k» пересчитывает «осталось ~N мин» и шкалу моста. Включи JS — и посчитаем вместе.</div>
</div>
</figure>

<p>Заметь: числа в виджете совпадают с диаграммой выше и с настоящим мостом внизу страницы. Так и задумано — источник у всех трёх один.</p>
<p>А что со статьями вне серий? У них тот же кикер, только вместо «часть N из M» — «разбор», а панель слева наполняется из поля <code>related</code> во front matter: три-пять ссылок на близкие гайды. Серия — не обязанность, а опция.</p>
<p><em>Теперь ты можешь собрать серию из любых гайдов, сложив их в одну папку, — и переставлять части, меняя один <code>weight</code>, а не N статей.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Мост внизу уже посчитал, что осталась одна часть. В ней — про цвет: почему все семь палитр этой страницы живут в data-файлах и как добавить свою, не тронув ни строчки темы.</p>
]]></content:encoded></item><item><title>Гайд — это папка</title><link>https://justskiv.github.io/taiga/ru/inside/anatomy/guide-is-a-bundle/</link><guid>https://justskiv.github.io/taiga/ru/inside/anatomy/guide-is-a-bundle/</guid><pubDate>Sat, 02 May 2026 00:00:00 +0300</pubDate><description>Контент-модель темы: статья — это leaf bundle, где рядом с текстом лежат её виджеты, стили и обложка.</description><content:encoded><![CDATA[<h2 id="naive-picture"><a class="hash" href="#naive-picture" aria-label="Якорь на раздел">#</a>Хочу живую картинку</h2>
<p>Наивная модель такая: я пишу текст, и в одном месте мне нужна интерактивная иллюстрация. Кнопка, счётчик, что угодно. Кажется, дел на десять строк JS.</p>
<p>Теперь боль. В обычной теме эти десять строк некуда положить. Скрипт надо подключить в <code>&lt;head&gt;</code> — значит, править шаблон. Шаблон общий — значит, скрипт поедет на все страницы сразу. Чтобы не поехал, нужен реестр «какой скрипт какой странице» — значит, конфиг. Через месяц у тебя три статьи с виджетами и одна страница шаблона, которую страшно трогать. Текст живёт в одном месте, его код — в трёх других, и перенести статью целиком уже нельзя: она вросла в тему корнями.</p>
<h2 id="bundle"><a class="hash" href="#bundle" aria-label="Якорь на раздел">#</a>Статья — это папка</h2>
<p>Здесь статья — это <strong>leaf bundle</strong>: папка, в которой лежит всё её хозяйство. Вот лента файлов бандла — той самой папки, из которой собрана эта страница:</p>

<div class="byte-strip" aria-hidden="true">
  <div class="byte-seg"><div class="cells"><div class="byte-box f0" data-tip="index.md · текст и front matter · обязателен">md</div></div><div class="seg-tag">index.md</div></div>
  <div class="byte-seg"><div class="cells"><div class="byte-box f1" data-tip="widgets.js · виджеты страницы · тема подключит сама">js</div></div><div class="seg-tag">widgets.js</div></div>
  <div class="byte-seg"><div class="cells"><div class="byte-box f2" data-tip="widgets.css · свои стили · если строительных классов не хватило">css</div></div><div class="seg-tag">widgets.css</div></div>
  <div class="byte-seg"><div class="cells"><div class="byte-box pad" data-tip="og.png · авторская обложка · перекрывает сгенерированную">og</div></div><div class="seg-tag padtag">↳ опц.</div></div>
</div>

<p>Обязателен только <code>index.md</code>. Всё остальное — опциональные соседи: положил <code>widgets.js</code> рядом — тема сама его найдёт, минифицирует, отфингерпринтит и подключит <strong>только на этой странице</strong>. Положил <code>widgets.css</code> — то же самое со стилями. Положил <code>og.png</code> — обложка для соцсетей берётся авторская вместо сгенерированной.</p>
<div class="callout key">
<span class="clabel">Главная мысль</span>
<p>Всё, что нужно статье, лежит рядом со статьёй. Перенести гайд на другой сайт — значит перенести одну папку. Удалить — удалить одну папку. Никаких хвостов в шаблонах и конфигах.</p>

</div>

<h2 id="two-touches"><a class="hash" href="#two-touches" aria-label="Якорь на раздел">#</a>Два касания</h2>
<p>Полный цикл «добавить живую иллюстрацию» — два касания. Первое: в тексте ставишь шорткод с идентификатором и заданием. Второе: в <code>widgets.js</code> регистрируешь инициализатор с тем же идентификатором:</p>
<span class="code-label">inside/guide-is-a-bundle/widgets.js (фрагмент)</span>
<div data-pagefind-weight="0.5"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">Taiga</span><span class="p">.</span><span class="nx">widget</span><span class="p">(</span><span class="s2">&#34;w-hello&#34;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">root</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">root</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span> <span class="c1">// убираем статик-фолбэк
</span></span></span><span class="line"><span class="cl">  <span class="c1">// …кнопка, счётчик, бейдж — весь код в файле рядом
</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Ни шаблонов, ни <code>&lt;head&gt;</code>, ни реестров. У шорткода есть парная форма: внутрь кладётся статичный фолбэк, который живёт в точке монтирования, пока JS его не заменит, — а если JS выключен, остаётся насовсем. Страница без скриптов деградирует в осмысленный текст, а не в дыру.</p>
<p>Заметь, чего в примере выше нет: собственных стилей. Строительные классы — <code>.w-row</code> для строки контролов, <code>.w-btn</code> для кнопок, <code>.w-num</code> для крупной цифры, <code>.w-cap</code> для строки-вывода — лежат в CSS темы, и виджет выглядит родным, не привезя ни одного своего правила. <code>widgets.css</code> нужен только когда этого набора не хватает — и тогда он, как и всё остальное, просто кладётся в папку.</p>
<h2 id="runtime"><a class="hash" href="#runtime" aria-label="Якорь на раздел">#</a>Рантайм не даст упасть</h2>
<p><code>Taiga.widget</code> — это маленький рантайм в теме. Он ждёт DOM, находит точку монтирования по идентификатору и вызывает твой колбэк, обернув его в <code>try/catch</code>: упавший виджет напишет в консоль и не уронит соседей. А если точки монтирования нет — шорткод убрали из текста — инициализатор молча пропускается.</p>
<div class="callout trap">
<span class="clabel">Ловушка</span>
<p>«Молча» — обоюдоострое слово. Идентификатор в шорткоде и в <code>Taiga.widget(…)</code> должны совпадать буква в букву: при опечатке рантайм решит, что шорткод убрали, и ты не получишь ни виджета, ни ошибки. Пустой квадрат на месте иллюстрации — первым делом сверь id.</p>

</div>

<p>А теперь проверь всё это на живом экземпляре. Виджет ниже смонтирован тем самым рантаймом из того самого <code>widgets.js</code>, что лежит в папке этой статьи:</p>
<figure class="widget">
<figcaption>Поиграй <span class="wnote">— нажми кнопку несколько раз, потом перезагрузи страницу: счётчик живёт в замыкании, а не в теме</span></figcaption>
<div id="w-hello" class="w-root">
<div class="w-cap">Здесь живёт счётчик кликов. Сейчас ты видишь статичный фолбэк: включи JS — и он оживёт.</div>
</div>
</figure>

<p>И последнее: механизм не привязан к гайдам. <code>widgets.js</code> подхватывается у <strong>любого</strong> бандла — страница с интерактивом здесь не отдельный тип со своим шаблоном, а обычная markdown-страница, рядом с которой лежит скрипт.</p>
<p><em>Теперь ты можешь добавить живую иллюстрацию в любой гайд, не открыв ни одного файла темы.</em></p>
<h2 id="what-next"><a class="hash" href="#what-next" aria-label="Якорь на раздел">#</a>Что дальше</h2>
<p>Эта статья — часть серии, и слева от текста уже стоит панель с её оглавлением, а в кикере написано «часть 1 из 3». Руками это никто не набирал. Откуда тема всё знает — во второй части.</p>
]]></content:encoded></item></channel></rss>