Нужен был инструмент для создания карточек Instagram-каруселей. Без Figma, без Canva — прямо из markdown-заметок.
Что получилось
Один markdown-файл = одна карусель. Секции ### = слайды. Превью в браузере, скачивание PNG одной кнопкой.
---
title: Эго — маска труса
layout: trip2g/instaframe
---
### ТВОЁ [ЭГО] НЕ ДЕЛАЕТ ТЕБЯ СИЛЬНЫМ
Оно прячет твою трусость.
Слово в квадратных скобках [ЭГО] получает рамку — акцент на ключевом слове.
Архитектура
Данные в HTML, логика в JS
Первая версия пыталась передать данные напрямую в JavaScript через template-выражения:
const slides = [
{{ range s := note.Sections(3) }}
{ title: {{ s.TitleHTML }} },
{{ end }}
];
Сломалось на кириллице — Unexpected identifier 'ИСКАТЬ'. Кавычки не расставились.
Решение — рендерить данные в скрытый HTML, читать из DOM:
<div class="slides-data" style="display:none">
{{ range s := note.Sections(3) }}
<div class="slide-item">
<div class="slide-title">{{ s.TitleHTML }}</div>
<div class="slide-text">{{ s.ContentHTML }}</div>
</div>
{{ end }}
</div>
const slides = Array.from(document.querySelectorAll('.slide-item'))
.map(el => ({
title: el.querySelector('.slide-title').innerHTML,
text: el.querySelector('.slide-text').innerHTML
}));
Чисто и надёжно. Template-код не смешивается с JS.
Выделение слов рамкой
Markdown не поддерживает произвольные рамки. Использовали синтаксис ссылок [слово] — он не конфликтует с wikilinks, потому что нет () после скобок.
JS на лету заменяет на <mark>:
const parseTitle = html => html.replace(/\[([^\]]+)\]/g, '<mark>$1</mark>');
CSS делает рамку:
.card-title mark {
background: none;
border: 2.5px solid #111;
padding: 0.05em 0.25em;
display: inline-block;
}
Проблема с line-height
Рамка вокруг слова налезала на соседние строки. Когда текст переносится, border добавляет высоту, но line-height остаётся прежним.
Решение — увеличить line-height заголовка до 1.6:
.card-title {
line-height: 1.6;
}
.card-title mark {
display: inline-block;
line-height: 1;
}
inline-block на mark изолирует его от влияния на общий поток.
Скачивание PNG
Использовали html2canvas — рендерит DOM в canvas, который можно сохранить как PNG.
const canvas = await html2canvas(card, {
scale: 2,
backgroundColor: '#e5e5e5'
});
const link = document.createElement('a');
link.download = 'slide-1.png';
link.href = canvas.toDataURL('image/png');
link.click();
scale: 2 даёт чёткость на ретине. Размер карточки 4:5 — стандарт Instagram.
Кнопка «Все N шт.» скачивает слайды по очереди с задержкой 250ms между файлами — иначе браузер блокирует множественные загрузки.
Типографика
Для заголовков — Cormorant SC. Шрифт с капителями и характером старинных карт. Тот же, что в интерактивной карте.
Для текста — Inter. Читается на любом размере.
Интерфейс
Эволюция за три итерации:
v1 — кнопки навигации отдельно от карточки, нумерованные миниатюры внизу. Выглядело разбросанно.
v2 — стрелки по бокам карточки (как в Instagram), точки вместо номеров. Компактнее.
v3 — кнопки скачивания в хедер. Две кнопки: «1/11 ⬇» (текущий) и «11/11 ⬇» (все). Минимум элементов.
Структура файлов
instaframes/
├── _index.md — инструкция
├── task0.md — карусель про эго
└── example.md — пример
Каждый файл — отдельная карусель. Добавить новую — создать markdown с layout: trip2g/instaframe.
CSS в HTML
Весь CSS инлайн в шаблоне. Не зависит от основных стилей сайта. Можно скопировать шаблон в другой проект — будет работать.
Итого
- Markdown → карусель — секции
###становятся слайдами - Выделение
[слово]— рамка на ключевом слове - Скачивание PNG — html2canvas, scale 2x
- Типографика — Cormorant SC + Inter
- Автономный шаблон — CSS и JS инлайн
Контент в Obsidian, результат в Instagram. Без дизайнера, без Figma.
Попробовать: /instaframes/task0