feat: компонент фильтр-чипов в дизайн-систему + фильтр проектов на /ai-dev/
This commit is contained in:
1 parent
52485a17c4
commit
ee0dca11a2
9 files changed
+761
-527
No files matched your search
@@ -147,6 +147,34 @@
|
||||
| `.hero__tags` | Контейнер тегов в Hero |
|
||||
| `.hero__tag` | Тег (пилл с рамкой). Используйте для ключевых слов |
|
||||
| `.portfolio-card__tag` | Тег внутри карточки портфолио |
|
||||
| `.mp-card__tags` | Контейнер тегов в mp-card |
|
||||
| `.mp-card__tag` | Тег внутри mp-card |
|
||||
|
||||
### Фильтр-чипы
|
||||
|
||||
Переключаемая группа тегов над списком карточек. Нажатый чип оставляет только элементы с
|
||||
совпадающим тегом, остальные скрываются.
|
||||
|
||||
| Класс / атрибут | Назначение |
|
||||
|---|---|
|
||||
| `.filter-chips` | Контейнер чипов (`<ul>`). Атрибут `data-filter-chips` включает поведение |
|
||||
| `.filter-chip` | Кнопка-чип. Атрибут `data-filter-value` = тег, который она включает |
|
||||
| `.filter-chip__count` | Счётчик совпадений. Проставляется скриптом автоматически |
|
||||
| `data-filter-scope` | Селектор контейнера с фильтруемыми элементами. По умолчанию `document` |
|
||||
| `data-filter-item` | Помечает фильтруемый элемент |
|
||||
| `data-filter-tag` | Теги элемента через пробел. Несколько тегов работают как OR |
|
||||
|
||||
Правила:
|
||||
|
||||
- Состояние держится в `aria-pressed="true|false"`, а не в классе. Оформление выбранного
|
||||
чипа — `[aria-pressed="true"]`, поэтому оно не зависит от `:focus` и не залипает после клика.
|
||||
- Значение `all` (или пустое) показывает все элементы. Такой чип обычно первый.
|
||||
- Скрытие через атрибут `hidden` + `[data-filter-item][hidden] { display: none }` — сетка
|
||||
не оставляет дыр. Плавное затухание потребовало бы анимации высоты, а для фильтра
|
||||
мгновенный отклик важнее.
|
||||
- JS: `initFilterChips()` в `public/js/modules/ui/filter-chips.js`, подключён в `main.js`.
|
||||
Счётчики и стартовое состояние берутся из разметки, модуль сам их заполняет.
|
||||
- Помечайте элементы `hidden`, а не удаляйте — иначе фильтр ломает раскладку при возврате.
|
||||
|
||||
### Карточки
|
||||
|
||||
|
||||
+199
-265
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Николай Мамадаев, AI-разработчик. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты."
|
||||
content="Николай Мамадаев, AI-разработник. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты."
|
||||
/>
|
||||
<title>AI-разработка · Николай Мамадаев</title>
|
||||
<script>
|
||||
@@ -124,7 +124,7 @@
|
||||
<meta property="og:locale" content="ru_RU" />
|
||||
<meta property="og:locale:alternate" content="en_US" />
|
||||
<meta property="og:title" content="AI-разработка · Николай Мамадаев" />
|
||||
<meta property="og:description" content="Николай Мамадаев, AI-разработчик. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты." />
|
||||
<meta property="og:description" content="Николай Мамадаев, AI-разработник. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты." />
|
||||
<meta property="og:url" content="https://mamadaevv.site/ai-dev/" />
|
||||
<meta property="og:image" content="https://mamadaevv.site/assets/og/og-default.png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
@@ -134,7 +134,7 @@
|
||||
<!-- Twitter / X -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="AI-разработка · Николай Мамадаев" />
|
||||
<meta name="twitter:description" content="Николай Мамадаев, AI-разработчик. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты." />
|
||||
<meta name="twitter:description" content="Николай Мамадаев, AI-разработник. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты." />
|
||||
<meta name="twitter:image" content="https://mamadaevv.site/assets/og/og-default.png" />
|
||||
<meta name="twitter:image:alt" content="AI-разработка · Николай Мамадаев" />
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
"@id": "https://mamadaevv.site/ai-dev/#page",
|
||||
"url": "https://mamadaevv.site/ai-dev/",
|
||||
"name": "AI-разработка · Николай Мамадаев",
|
||||
"description": "Николай Мамадаев, AI-разработчик. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты.",
|
||||
"description": "Николай Мамадаев, AI-разработник. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты.",
|
||||
"isPartOf": {
|
||||
"@id": "https://mamadaevv.site/#website"
|
||||
},
|
||||
@@ -219,6 +219,10 @@
|
||||
<link rel="stylesheet" href="/vendor/plyr/plyr.css" />
|
||||
|
||||
<style>
|
||||
/* Локальные правила страницы. Всё остальное — компоненты дизайн-системы:
|
||||
.contact-grid (сетка), .mp-card (карточка), .filter-chips (переключатель),
|
||||
.mp-card__tags/.mp-card__tag (метаданные), .case-links-grid (ссылки),
|
||||
.hero__tags/.hero__tag (список навыков). */
|
||||
.ai-page {
|
||||
max-width: 52rem;
|
||||
margin: 0 auto;
|
||||
@@ -236,24 +240,19 @@
|
||||
.ai-page a:not(.btn) {
|
||||
color: var(--accent);
|
||||
}
|
||||
/* Сетка карточек проектов: 2 колонки, схлопывается в одну на мобильном */
|
||||
/* Сетка проектов: контракт .contact-grid, но 2 колонки и шире минимальная
|
||||
колонка — карточкам нужен воздух. Сама сетка и её адаптив из системы. */
|
||||
.ai-projects {
|
||||
list-style: none;
|
||||
margin: 1.25rem 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.ai-projects > li {
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* Обложка- заглушка внутри карточки. */
|
||||
.ai-projects__media {
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
margin-bottom: 0.7rem;
|
||||
background: var(--surface);
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
.ai-projects__media img {
|
||||
display: block;
|
||||
@@ -262,56 +261,19 @@
|
||||
aspect-ratio: 16 / 10;
|
||||
object-fit: cover;
|
||||
}
|
||||
.ai-projects__head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.ai-projects__title {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
/* Стек проекта — моноширинный, как .filter-chip__count в системе. */
|
||||
.ai-projects__stack {
|
||||
margin: 0.3rem 0 0;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.ai-projects__desc {
|
||||
margin: 0.45rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
color: var(--text);
|
||||
}
|
||||
.ai-projects__links {
|
||||
list-style: none;
|
||||
margin: 0.6rem 0 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
/* Навыки — компактные чипы переиспользуют стили тегов карточек активности */
|
||||
.ai-stack-list {
|
||||
list-style: none;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.ai-stack-list li {
|
||||
margin: 0;
|
||||
padding: 0.3rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.4;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
/* У mp-card--compact теги уходят вниз карточки (order 2) — оставляем
|
||||
стек под заголовком, поэтому поднимаем его наравне с описанием. */
|
||||
.mp-card--compact .ai-projects__stack {
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -425,228 +387,200 @@
|
||||
</p>
|
||||
|
||||
<h2 id="roles">Что я делаю</h2>
|
||||
<ul class="section__prose ai-stack-list">
|
||||
<li>Продуктовая логика и API</li>
|
||||
<li>Telegram-боты на aiogram 3</li>
|
||||
<li>MCP-серверы</li>
|
||||
<li>Плагины Figma и Obsidian</li>
|
||||
<li>Автоматизация и агентные пайплайны</li>
|
||||
<ul class="section__prose hero__tags" aria-label="Что я делаю">
|
||||
<li class="hero__tag">Продуктовая логика и API</li>
|
||||
<li class="hero__tag">Telegram-боты на aiogram 3</li>
|
||||
<li class="hero__tag">MCP-серверы</li>
|
||||
<li class="hero__tag">Плагины Figma и Obsidian</li>
|
||||
<li class="hero__tag">Автоматизация и агентные пайплайны</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="projects">Проекты</h2>
|
||||
<ol class="ai-projects">
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Personal Assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · PostgreSQL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Личный ассистент в Telegram: напоминания, планирование дел и работа с LLM
|
||||
в одном диалоге.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="../cases/ai-platform-primo-rpa/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<ul class="filter-chips" data-filter-chips data-filter-scope="#ai-projects" aria-label="Фильтр проектов по типу">
|
||||
<li><button type="button" class="filter-chip" data-filter-value="all" aria-pressed="true">Все</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="bots" aria-pressed="false">Бots</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="mcp" aria-pressed="false">MCP</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="plugins" aria-pressed="false">Плагины</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="web" aria-pressed="false">Веб</button></li>
|
||||
</ul>
|
||||
|
||||
<ol class="contact-grid ai-projects" id="ai-projects">
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Personal Assistant</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · PostgreSQL</p>
|
||||
<p class="mp-card__desc">Личный ассистент в Telegram: напоминания, планирование дел и работа с LLM в одном диалоге.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Image Gen Bot</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · Redis · PostgreSQL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Бот для генерации изображений: баланс, реферальная программа, админ-логи
|
||||
в отдельные топики супергруппы.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="../cases/ai-platform-primo-rpa/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Image Gen Bot</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · Redis · PostgreSQL</p>
|
||||
<p class="mp-card__desc">Бот для генерации изображений: баланс, реферальная программа, админ-логи в отдельные топики супергруппы.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-03.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">MCP Image Server</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">JavaScript · MCP · OpenRouter</p>
|
||||
<p class="ai-projects__desc">
|
||||
Собственный MCP-сервер для генерации картинок. Подключается к агентам
|
||||
как инструмент.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/mcp-img-gen" target="_blank" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="mcp bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-03.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">MCP Image Server</h3>
|
||||
<p class="ai-projects__stack">JavaScript · MCP · OpenRouter</p>
|
||||
<p class="mp-card__desc">Собственный MCP-сервер для генерации картинок. Подключается к агентам как инструмент.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">MCP</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">AI assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">TypeScript · Figma Plugin API</p>
|
||||
<p class="ai-projects__desc">
|
||||
Плагин для Figma, который исполняет скрипты прямо в редакторе.
|
||||
ИИ-ассистент для любого тарифа.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="../cases/ai-assistant-figma/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">AI assistant</h3>
|
||||
<p class="ai-projects__stack">TypeScript · Figma Plugin API</p>
|
||||
<p class="mp-card__desc">Плагин для Figma, который исполняет скрипты прямо в редакторе. ИИ-ассистент для любого тарифа.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Card Columns</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">TypeScript · Obsidian API</p>
|
||||
<p class="ai-projects__desc">
|
||||
Вью для заметок в стиле канбан-доски: группировка по колонкам с фильтрами
|
||||
по тегам.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="../cases/card-columns-obsidian/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Card Columns</h3>
|
||||
<p class="ai-projects__stack">TypeScript · Obsidian API</p>
|
||||
<p class="mp-card__desc">Вью для заметок в стиле канбан-доски: группировка по колонкам с фильтрами по тегам.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-03.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Hermes Desktop Plugins</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">JavaScript · TypeScript · Electron</p>
|
||||
<p class="ai-projects__desc">
|
||||
Плагины для рабочего стола Hermes: чип баланса API, звук завершения задачи
|
||||
и контроль трат.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/unorouter-balance" target="_blank" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-03.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Hermes Desktop Plugins</h3>
|
||||
<p class="ai-projects__stack">JavaScript · TypeScript · Electron</p>
|
||||
<p class="mp-card__desc">Плагины для рабочего стола Hermes: чип баланса API, звук завершения задачи и контроль трат.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Social Post Writer</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · SQLite</p>
|
||||
<p class="ai-projects__desc">
|
||||
Бот, который превращает тезисы в готовый пост под нужную площадку и тон,
|
||||
учитывая голос автора.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/social-post-writer-bot" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Social Post Writer</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · SQLite</p>
|
||||
<p class="mp-card__desc">Бот, который превращает тезисы в готовый пост под нужную площадку и тон, учитывая голос автора.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="../assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">AI Design Assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">HTML · CSS · GLSL · WebGL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Этот сайт целиком: структура, дизайн-система, шейдеры, CI/CD.
|
||||
Весь цикл создания вместе с ИИ-агентом.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="../cases/site-portfolio/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="web">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="../assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">AI Design Assistant</h3>
|
||||
<p class="ai-projects__stack">HTML · CSS · GLSL · WebGL</p>
|
||||
<p class="mp-card__desc">Этот сайт целиком: структура, дизайн-система, шейдеры, CI/CD. Весь цикл создания вместе с ИИ-агентом.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Веб</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="stack">Стек</h2>
|
||||
<ul class="section__prose ai-stack-list">
|
||||
<li>Python</li>
|
||||
<li>TypeScript</li>
|
||||
<li>JavaScript</li>
|
||||
<li>Go</li>
|
||||
<li>HTML/CSS</li>
|
||||
<li>GLSL</li>
|
||||
<li>aiogram 3</li>
|
||||
<li>FastAPI</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Redis</li>
|
||||
<li>SQLite</li>
|
||||
<li>Docker</li>
|
||||
<li>Coolify</li>
|
||||
<li>Gitea</li>
|
||||
<li>MCP</li>
|
||||
<ul class="section__prose hero__tags" aria-label="Стек">
|
||||
<li class="hero__tag">Python</li>
|
||||
<li class="hero__tag">TypeScript</li>
|
||||
<li class="hero__tag">JavaScript</li>
|
||||
<li class="hero__tag">Go</li>
|
||||
<li class="hero__tag">HTML/CSS</li>
|
||||
<li class="hero__tag">GLSL</li>
|
||||
<li class="hero__tag">aiogram 3</li>
|
||||
<li class="hero__tag">FastAPI</li>
|
||||
<li class="hero__tag">PostgreSQL</li>
|
||||
<li class="hero__tag">Redis</li>
|
||||
<li class="hero__tag">SQLite</li>
|
||||
<li class="hero__tag">Docker</li>
|
||||
<li class="hero__tag">Coolify</li>
|
||||
<li class="hero__tag">Gitea</li>
|
||||
<li class="hero__tag">MCP</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="links">Где я это выкладываю</h2>
|
||||
<p class="section__prose">
|
||||
Код открыт: <a href="https://github.com/mamadaevv" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
и <a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin" target="_blank" rel="noopener noreferrer">Gitea</a>.
|
||||
Сервисы, которые работают прямо сейчас, живут на
|
||||
<a href="https://secrets-4-me.dynv6.net" target="_blank" rel="noopener noreferrer">secrets-4-me</a>.
|
||||
</p>
|
||||
<ul class="case-links-grid">
|
||||
<li>
|
||||
<a class="case-link-card" href="https://github.com/mamadaevv" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">GitHub</span>
|
||||
<span class="case-link-card__meta">Проекты и эксперименты с кодом</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">Gitea</span>
|
||||
<span class="case-link-card__meta">Внутренние проекты и сервисы</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="../cases/site-portfolio/" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">Кейс этого сайта</span>
|
||||
<span class="case-link-card__meta">Как я делал сайт вместе с ИИ-агентом</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="https://secrets-4-me.dynv6.net" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">secrets-4-me</span>
|
||||
<span class="case-link-card__meta">Сервис, который работает сейчас</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -1032,6 +1032,26 @@
|
||||
</ul>
|
||||
<p class="sg-label">.mp-card__tags / .mp-card__tag</p>
|
||||
|
||||
<h3>Фильтр-чипы</h3>
|
||||
<p class="section__prose">
|
||||
Переключаемая группа тегов над списком карточек. Нажатый чип показывает только
|
||||
те элементы, у которых этот тег есть в <code>data-filter-tag</code>. Остальные
|
||||
скрываются атрибутом <code>hidden</code>, поэтому сетка не оставляет дыр.
|
||||
</p>
|
||||
<ul class="filter-chips" data-filter-chips data-filter-scope="#filter-demo" aria-label="Фильтр демо-карточек">
|
||||
<li><button type="button" class="filter-chip" data-filter-value="all" aria-pressed="true">Все</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="bots" aria-pressed="false">Боты</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="plugins" aria-pressed="false">Плагины</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="web" aria-pressed="false">Веб</button></li>
|
||||
</ul>
|
||||
<ul class="mp-card__tags" id="filter-demo" style="display: grid; gap: 0.4rem; list-style: none; padding: 0; margin: 0.5rem 0">
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="bots web" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Бот на aiogram + веб-панель</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="bots" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Telegram-бот генерации картинок</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="plugins" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Плагин для Figma</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="plugins web" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Плагин для Obsidian</li>
|
||||
</ul>
|
||||
<p class="sg-label">.filter-chips / .filter-chip[aria-pressed] + [data-filter-item][data-filter-tag]</p>
|
||||
|
||||
<h3>Карточка портфолио</h3>
|
||||
<div class="sg-portfolio-wrap">
|
||||
<a class="mp-card mp-card--link mp-card--stack-bottom" href="#components" tabindex="-1" aria-hidden="true">
|
||||
|
||||
@@ -3570,6 +3570,82 @@ body.home .section__title {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Фильтр-чипы — переключаемая группа тегов для фильтрации карточек.
|
||||
Разметка: .filter-chips > button.filter-chip[aria-pressed] (или .filter-chip--all).
|
||||
Партнёр: элементы с [data-filter-item] и [data-filter-tag~="<значение>"].
|
||||
========================================================================== */
|
||||
|
||||
.filter-chips {
|
||||
list-style: none;
|
||||
margin: 0 0 1.25rem;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem 0.45rem;
|
||||
}
|
||||
|
||||
.filter-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 2rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--surface) 78%, transparent);
|
||||
color: var(--text-muted);
|
||||
font-family: inherit;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
background-color 0.18s ease;
|
||||
}
|
||||
|
||||
.filter-chip:hover {
|
||||
color: var(--text);
|
||||
border-color: color-mix(in srgb, var(--accent) 40%, var(--line));
|
||||
}
|
||||
|
||||
/* Выбранный чип — акцентная заливка. aria-pressed держит состояние явным,
|
||||
поэтому оформление не зависит от :focus и переживает клик без залипания. */
|
||||
.filter-chip[aria-pressed="true"] {
|
||||
color: var(--accent-text);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
.filter-chip:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.filter-chip__count {
|
||||
margin-left: 0.45rem;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.filter-chip {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Скрытие несовпавших карточек: display:none, чтобы grid не оставлял дыр.
|
||||
Плавное затухание вместо этого потребовало бы анимации высоты —
|
||||
для фильтра мгновенный отклик важнее. */
|
||||
[data-filter-item][hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mp-card__media {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
@@ -25,6 +25,7 @@ import { initProjectsCarousel } from "./modules/ui/projects-carousel.js";
|
||||
import { initCaseMediaSlider } from "./modules/cases/media-slider.js";
|
||||
import { initSkillsAccordion } from "./modules/ui/skills-accordion.js";
|
||||
import { initSkillsIntroMessages } from "./modules/ui/skills-intro.js";
|
||||
import { initFilterChips } from "./modules/ui/filter-chips.js";
|
||||
import { initContentEditMode } from "./modules/ui/content-edit/content-edit-mode.js";
|
||||
import { initDeferredPortfolioCovers } from "./modules/ui/deferred-media.js";
|
||||
import { initImageLoadPlaceholder } from "./modules/ui/image-load-placeholder.js";
|
||||
@@ -54,6 +55,7 @@ initMiniProjectsExpand(miniProjectsMasonry);
|
||||
initExperienceHoverGlow();
|
||||
initContactCopy();
|
||||
initSkillsIntroMessages();
|
||||
initFilterChips();
|
||||
initCaseNaturalImagesHalfSize();
|
||||
initCaseSequentialImages();
|
||||
initCaseTallMediaExpand();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Фильтр-чипы — переключение видимости элементов по тегу.
|
||||
*
|
||||
* Разметка (см. DESIGN.md → Компоненты → Фильтр-чипы):
|
||||
* <ul class="filter-chips" data-filter-chips>
|
||||
* <li><button class="filter-chip" type="button" data-filter-value="all" aria-pressed="true">Все</button></li>
|
||||
* <li><button class="filter-chip" type="button" data-filter-value="bots" aria-pressed="false">Боты</button></li>
|
||||
* </ul>
|
||||
*
|
||||
* Партнёрские элементы:
|
||||
* <li data-filter-item data-filter-tag="bots mcp">…</li>
|
||||
*
|
||||
* Значение `all` (или пустое) показывает все элементы. Несколько тегов
|
||||
* на элементе пишутся через пробел и работают как OR.
|
||||
*
|
||||
* Счётчики в чипах (`.filter-chip__count`) считаются автоматически:
|
||||
* сколько элементов соответствует этому значению.
|
||||
*/
|
||||
export function initFilterChips() {
|
||||
const root = document.querySelector("[data-filter-chips]");
|
||||
if (!root) return;
|
||||
|
||||
const scope = document.querySelector(root.getAttribute("data-filter-scope")) || document;
|
||||
const items = Array.from(scope.querySelectorAll("[data-filter-item]"));
|
||||
if (!items.length) return;
|
||||
|
||||
const chips = Array.from(root.querySelectorAll("[data-filter-value]"));
|
||||
if (!chips.length) return;
|
||||
|
||||
const ALL = "all";
|
||||
|
||||
function matches(item, value) {
|
||||
if (value === ALL || !value) return true;
|
||||
const tags = (item.getAttribute("data-filter-tag") || "").split(/\s+/);
|
||||
return tags.includes(value);
|
||||
}
|
||||
|
||||
function apply(value) {
|
||||
items.forEach((item) => {
|
||||
const show = matches(item, value);
|
||||
if (show) {
|
||||
item.removeAttribute("hidden");
|
||||
} else {
|
||||
item.setAttribute("hidden", "");
|
||||
}
|
||||
});
|
||||
|
||||
chips.forEach((chip) => {
|
||||
chip.setAttribute("aria-pressed", chip.getAttribute("data-filter-value") === value ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
function fillCounts() {
|
||||
chips.forEach((chip) => {
|
||||
const value = chip.getAttribute("data-filter-value");
|
||||
const count = items.filter((item) => matches(item, value)).length;
|
||||
let slot = chip.querySelector(".filter-chip__count");
|
||||
if (!slot) {
|
||||
slot = document.createElement("span");
|
||||
slot.className = "filter-chip__count";
|
||||
slot.setAttribute("aria-hidden", "true");
|
||||
chip.appendChild(slot);
|
||||
}
|
||||
slot.textContent = String(count);
|
||||
});
|
||||
}
|
||||
|
||||
chips.forEach((chip) => {
|
||||
chip.addEventListener("click", () => {
|
||||
apply(chip.getAttribute("data-filter-value") || ALL);
|
||||
});
|
||||
});
|
||||
|
||||
fillCounts();
|
||||
// Стартовое состояние берём из чипа, который уже нажат в разметке,
|
||||
// иначе после bfcache-возврата фильтр сбросился бы на «все».
|
||||
const initial = chips.find((chip) => chip.getAttribute("aria-pressed") === "true");
|
||||
apply(initial ? initial.getAttribute("data-filter-value") || ALL : ALL);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Проверка логики initFilterChips без браузера: собираем дерево из собранной
|
||||
// страницы public/ai-dev/index.html и прогоняем по нему реальный модуль.
|
||||
import fs from "node:fs";
|
||||
|
||||
const HTML = fs.readFileSync(process.argv[2] || "public/ai-dev/index.html", "utf8");
|
||||
|
||||
class El {
|
||||
constructor() {
|
||||
this.attrs = {};
|
||||
this.children = [];
|
||||
this._text = "";
|
||||
this.listeners = {};
|
||||
}
|
||||
setAttribute(k, v) {
|
||||
this.attrs[k] = String(v);
|
||||
}
|
||||
getAttribute(k) {
|
||||
return this.attrs[k] ?? null;
|
||||
}
|
||||
removeAttribute(k) {
|
||||
delete this.attrs[k];
|
||||
}
|
||||
hasAttribute(k) {
|
||||
return k in this.attrs;
|
||||
}
|
||||
appendChild(c) {
|
||||
this.children.push(c);
|
||||
return c;
|
||||
}
|
||||
*walk() {
|
||||
for (const c of this.children) {
|
||||
yield c;
|
||||
yield* c.walk();
|
||||
}
|
||||
}
|
||||
querySelector(sel) {
|
||||
const cls = sel.startsWith(".") ? sel.slice(1) : null;
|
||||
for (const d of this.walk()) {
|
||||
if (cls && (d.getAttribute("class") || "").split(/\s+/).includes(cls)) return d;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
querySelectorAll(sel) {
|
||||
const attr = sel.replace(/[[\]]/g, "");
|
||||
const out = [];
|
||||
for (const d of this.walk()) if (d.hasAttribute(attr)) out.push(d);
|
||||
return out;
|
||||
}
|
||||
addEventListener(ev, fn) {
|
||||
this.listeners[ev] = fn;
|
||||
}
|
||||
click() {
|
||||
this.listeners.click?.();
|
||||
}
|
||||
get textContent() {
|
||||
return this._text;
|
||||
}
|
||||
set textContent(v) {
|
||||
this._text = String(v);
|
||||
}
|
||||
// Модуль ставит slot.className = "..." — в реальном DOM это атрибут class.
|
||||
set className(v) {
|
||||
this.attrs.class = String(v);
|
||||
}
|
||||
get className() {
|
||||
return this.attrs.class || "";
|
||||
}
|
||||
}
|
||||
|
||||
const scopeSel = (HTML.match(/data-filter-scope="([^"]+)"/) || [, "#ai-projects"])[1];
|
||||
const scopeId = scopeSel.replace(/^#/, "");
|
||||
|
||||
const chipsRoot = new El();
|
||||
chipsRoot.setAttribute("data-filter-scope", scopeSel);
|
||||
const scope = new El();
|
||||
scope.setAttribute("id", scopeId);
|
||||
|
||||
const chipRe =
|
||||
/<button[^>]*class="filter-chip"[^>]*data-filter-value="([^"]+)"[^>]*aria-pressed="([^"]*)"[^>]*>([^<]*)<\/button>/g;
|
||||
for (const m of HTML.matchAll(chipRe)) {
|
||||
const li = new El();
|
||||
const b = new El();
|
||||
b.setAttribute("class", "filter-chip");
|
||||
b.setAttribute("data-filter-value", m[1]);
|
||||
b.setAttribute("aria-pressed", m[2]);
|
||||
b.textContent = m[3];
|
||||
li.appendChild(b);
|
||||
chipsRoot.appendChild(li);
|
||||
}
|
||||
const itemRe = /<(\w+)([^>]*\bdata-filter-item\b[^>]*)>/g;
|
||||
for (const m of HTML.matchAll(itemRe)) {
|
||||
const li = new El();
|
||||
const tagm = m[2].match(/data-filter-tag="([^"]+)"/);
|
||||
li.setAttribute("data-filter-item", "");
|
||||
li.setAttribute("data-filter-tag", tagm ? tagm[1] : "");
|
||||
scope.appendChild(li);
|
||||
}
|
||||
|
||||
const mod = await import("../public/js/modules/ui/filter-chips.js");
|
||||
// Подменяем document на наше дерево.
|
||||
globalThis.document = {
|
||||
querySelector: (s) => {
|
||||
if (s === "[data-filter-chips]") return chipsRoot;
|
||||
if (s === scopeSel) return scope;
|
||||
return null;
|
||||
},
|
||||
createElement: () => new El(),
|
||||
};
|
||||
mod.initFilterChips();
|
||||
|
||||
const items = () => scope.querySelectorAll("[data-filter-item]");
|
||||
const chips = () => chipsRoot.querySelectorAll("[data-filter-value]");
|
||||
const visible = () => items().filter((i) => !i.hasAttribute("hidden")).length;
|
||||
const pressed = () => chips().filter((c) => c.getAttribute("aria-pressed") === "true").map((c) => c.getAttribute("data-filter-value"));
|
||||
const shownTags = () => items().filter((i) => !i.hasAttribute("hidden")).map((i) => i.getAttribute("data-filter-tag"));
|
||||
|
||||
console.log(`chips=${chips().length} items=${items().length}`);
|
||||
console.log("counts in DOM:", chips().map((c) => {
|
||||
const slot = c.children.find((k) => (k.getAttribute("class") || "").includes("filter-chip__count"));
|
||||
return `${c.getAttribute("data-filter-value")}=${slot ? slot.textContent : "MISSING"}`;
|
||||
}).join(" "));
|
||||
console.log("initial:", visible(), pressed());
|
||||
|
||||
let fail = 0;
|
||||
const tagOf = (i) => (i.getAttribute("data-filter-tag") || "").split(/\s+/);
|
||||
const expect = {};
|
||||
for (const c of chips()) {
|
||||
const v = c.getAttribute("data-filter-value");
|
||||
expect[v] = v === "all" ? items().length : items().filter((i) => tagOf(i).includes(v)).length;
|
||||
}
|
||||
console.log("expected:", JSON.stringify(expect));
|
||||
for (const [val, want] of Object.entries(expect)) {
|
||||
chips().find((c) => c.getAttribute("data-filter-value") === val).click();
|
||||
const got = visible();
|
||||
const ok = got === want && pressed().length === 1 && pressed()[0] === val;
|
||||
if (!ok) fail++;
|
||||
console.log(`${ok ? "OK " : "FAIL"} ${val.padEnd(8)} visible=${got} (want ${want}) pressed=${pressed()} tags=${JSON.stringify(shownTags())}`);
|
||||
}
|
||||
|
||||
console.log(fail === 0 ? "\nALL PASS" : `\n${fail} FAILURES`);
|
||||
process.exit(fail === 0 ? 0 : 1);
|
||||
+196
-262
@@ -5,12 +5,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Николай Мамадаев, AI-разработчик. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты."
|
||||
content="Николай Мамадаев, AI-разработник. Делаю целые продукты: Telegram-боты, MCP-серверы, плагины для Figma и Obsidian, desktop-утилиты."
|
||||
/>
|
||||
<title>AI-разработка · Николай Мамадаев</title>
|
||||
{{> head-links}}
|
||||
{{> head-plyr}}
|
||||
<style>
|
||||
/* Локальные правила страницы. Всё остальное — компоненты дизайн-системы:
|
||||
.contact-grid (сетка), .mp-card (карточка), .filter-chips (переключатель),
|
||||
.mp-card__tags/.mp-card__tag (метаданные), .case-links-grid (ссылки),
|
||||
.hero__tags/.hero__tag (список навыков). */
|
||||
.ai-page {
|
||||
max-width: 52rem;
|
||||
margin: 0 auto;
|
||||
@@ -28,24 +32,19 @@
|
||||
.ai-page a:not(.btn) {
|
||||
color: var(--accent);
|
||||
}
|
||||
/* Сетка карточек проектов: 2 колонки, схлопывается в одну на мобильном */
|
||||
/* Сетка проектов: контракт .contact-grid, но 2 колонки и шире минимальная
|
||||
колонка — карточкам нужен воздух. Сама сетка и её адаптив из системы. */
|
||||
.ai-projects {
|
||||
list-style: none;
|
||||
margin: 1.25rem 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.ai-projects > li {
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* Обложка- заглушка внутри карточки. */
|
||||
.ai-projects__media {
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
margin-bottom: 0.7rem;
|
||||
background: var(--surface);
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
.ai-projects__media img {
|
||||
display: block;
|
||||
@@ -54,56 +53,19 @@
|
||||
aspect-ratio: 16 / 10;
|
||||
object-fit: cover;
|
||||
}
|
||||
.ai-projects__head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.ai-projects__title {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
/* Стек проекта — моноширинный, как .filter-chip__count в системе. */
|
||||
.ai-projects__stack {
|
||||
margin: 0.3rem 0 0;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.ai-projects__desc {
|
||||
margin: 0.45rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
color: var(--text);
|
||||
}
|
||||
.ai-projects__links {
|
||||
list-style: none;
|
||||
margin: 0.6rem 0 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
/* Навыки — компактные чипы переиспользуют стили тегов карточек активности */
|
||||
.ai-stack-list {
|
||||
list-style: none;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.ai-stack-list li {
|
||||
margin: 0;
|
||||
padding: 0.3rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.4;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
/* У mp-card--compact теги уходят вниз карточки (order 2) — оставляем
|
||||
стек под заголовком, поэтому поднимаем его наравне с описанием. */
|
||||
.mp-card--compact .ai-projects__stack {
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -123,228 +85,200 @@
|
||||
</p>
|
||||
|
||||
<h2 id="roles">Что я делаю</h2>
|
||||
<ul class="section__prose ai-stack-list">
|
||||
<li>Продуктовая логика и API</li>
|
||||
<li>Telegram-боты на aiogram 3</li>
|
||||
<li>MCP-серверы</li>
|
||||
<li>Плагины Figma и Obsidian</li>
|
||||
<li>Автоматизация и агентные пайплайны</li>
|
||||
<ul class="section__prose hero__tags" aria-label="Что я делаю">
|
||||
<li class="hero__tag">Продуктовая логика и API</li>
|
||||
<li class="hero__tag">Telegram-боты на aiogram 3</li>
|
||||
<li class="hero__tag">MCP-серверы</li>
|
||||
<li class="hero__tag">Плагины Figma и Obsidian</li>
|
||||
<li class="hero__tag">Автоматизация и агентные пайплайны</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="projects">Проекты</h2>
|
||||
<ol class="ai-projects">
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Personal Assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · PostgreSQL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Личный ассистент в Telegram: напоминания, планирование дел и работа с LLM
|
||||
в одном диалоге.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="{{casesPrefix}}ai-platform-primo-rpa/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<ul class="filter-chips" data-filter-chips data-filter-scope="#ai-projects" aria-label="Фильтр проектов по типу">
|
||||
<li><button type="button" class="filter-chip" data-filter-value="all" aria-pressed="true">Все</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="bots" aria-pressed="false">Бots</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="mcp" aria-pressed="false">MCP</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="plugins" aria-pressed="false">Плагины</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="web" aria-pressed="false">Веб</button></li>
|
||||
</ul>
|
||||
|
||||
<ol class="contact-grid ai-projects" id="ai-projects">
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Personal Assistant</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · PostgreSQL</p>
|
||||
<p class="mp-card__desc">Личный ассистент в Telegram: напоминания, планирование дел и работа с LLM в одном диалоге.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Image Gen Bot</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · Redis · PostgreSQL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Бот для генерации изображений: баланс, реферальная программа, админ-логи
|
||||
в отдельные топики супергруппы.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="{{casesPrefix}}ai-platform-primo-rpa/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Image Gen Bot</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · Redis · PostgreSQL</p>
|
||||
<p class="mp-card__desc">Бот для генерации изображений: баланс, реферальная программа, админ-логи в отдельные топики супергруппы.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-03.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">MCP Image Server</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">JavaScript · MCP · OpenRouter</p>
|
||||
<p class="ai-projects__desc">
|
||||
Собственный MCP-сервер для генерации картинок. Подключается к агентам
|
||||
как инструмент.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/mcp-img-gen" target="_blank" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="mcp bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-03.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">MCP Image Server</h3>
|
||||
<p class="ai-projects__stack">JavaScript · MCP · OpenRouter</p>
|
||||
<p class="mp-card__desc">Собственный MCP-сервер для генерации картинок. Подключается к агентам как инструмент.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">MCP</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">AI assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">TypeScript · Figma Plugin API</p>
|
||||
<p class="ai-projects__desc">
|
||||
Плагин для Figma, который исполняет скрипты прямо в редакторе.
|
||||
ИИ-ассистент для любого тарифа.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="{{casesPrefix}}ai-assistant-figma/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">AI assistant</h3>
|
||||
<p class="ai-projects__stack">TypeScript · Figma Plugin API</p>
|
||||
<p class="mp-card__desc">Плагин для Figma, который исполняет скрипты прямо в редакторе. ИИ-ассистент для любого тарифа.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Card Columns</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">TypeScript · Obsidian API</p>
|
||||
<p class="ai-projects__desc">
|
||||
Вью для заметок в стиле канбан-доски: группировка по колонкам с фильтрами
|
||||
по тегам.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="{{casesPrefix}}card-columns-obsidian/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Card Columns</h3>
|
||||
<p class="ai-projects__stack">TypeScript · Obsidian API</p>
|
||||
<p class="mp-card__desc">Вью для заметок в стиле канбан-доски: группировка по колонкам с фильтрами по тегам.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-03.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Hermes Desktop Plugins</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">JavaScript · TypeScript · Electron</p>
|
||||
<p class="ai-projects__desc">
|
||||
Плагины для рабочего стола Hermes: чип баланса API, звук завершения задачи
|
||||
и контроль трат.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/unorouter-balance" target="_blank" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="plugins">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-03.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Hermes Desktop Plugins</h3>
|
||||
<p class="ai-projects__stack">JavaScript · TypeScript · Electron</p>
|
||||
<p class="mp-card__desc">Плагины для рабочего стола Hermes: чип баланса API, звук завершения задачи и контроль трат.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Плагины</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">Social Post Writer</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · SQLite</p>
|
||||
<p class="ai-projects__desc">
|
||||
Бот, который превращает тезисы в готовый пост под нужную площадку и тон,
|
||||
учитывая голос автора.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin/social-post-writer-bot" rel="noopener noreferrer">Код</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="bots">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-01.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">Social Post Writer</h3>
|
||||
<p class="ai-projects__stack">Python · aiogram 3 · SQLite</p>
|
||||
<p class="mp-card__desc">Бот, который превращает тезисы в готовый пост под нужную площадку и тон, учитывая голос автора.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Боты</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
<li>
|
||||
<div class="ai-projects__media">
|
||||
<img
|
||||
src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp"
|
||||
alt=""
|
||||
width="640"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-projects__head">
|
||||
<h3 class="ai-projects__title">AI Design Assistant</h3>
|
||||
</div>
|
||||
<p class="ai-projects__stack">HTML · CSS · GLSL · WebGL</p>
|
||||
<p class="ai-projects__desc">
|
||||
Этот сайт целиком: структура, дизайн-система, шейдеры, CI/CD.
|
||||
Весь цикл создания вместе с ИИ-агентом.
|
||||
</p>
|
||||
<ul class="ai-projects__links">
|
||||
<li><a href="{{casesPrefix}}site-portfolio/" target="_blank" rel="noopener noreferrer">Кейс</a></li>
|
||||
</ul>
|
||||
<li data-filter-item data-filter-tag="web">
|
||||
<article class="mp-card mp-card--compact" data-glass="false">
|
||||
<div class="mp-card__media ai-projects__media" aria-hidden="true">
|
||||
<img class="mp-card__image" src="{{assetPrefix}}assets/ai-dev/placeholder-02.webp" alt="" width="640" height="400" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div class="mp-card__body">
|
||||
<h3 class="mp-card__title">AI Design Assistant</h3>
|
||||
<p class="ai-projects__stack">HTML · CSS · GLSL · WebGL</p>
|
||||
<p class="mp-card__desc">Этот сайт целиком: структура, дизайн-система, шейдеры, CI/CD. Весь цикл создания вместе с ИИ-агентом.</p>
|
||||
<ul class="mp-card__tags" aria-label="Тип проекта">
|
||||
<li class="mp-card__tag">Веб</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="stack">Стек</h2>
|
||||
<ul class="section__prose ai-stack-list">
|
||||
<li>Python</li>
|
||||
<li>TypeScript</li>
|
||||
<li>JavaScript</li>
|
||||
<li>Go</li>
|
||||
<li>HTML/CSS</li>
|
||||
<li>GLSL</li>
|
||||
<li>aiogram 3</li>
|
||||
<li>FastAPI</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Redis</li>
|
||||
<li>SQLite</li>
|
||||
<li>Docker</li>
|
||||
<li>Coolify</li>
|
||||
<li>Gitea</li>
|
||||
<li>MCP</li>
|
||||
<ul class="section__prose hero__tags" aria-label="Стек">
|
||||
<li class="hero__tag">Python</li>
|
||||
<li class="hero__tag">TypeScript</li>
|
||||
<li class="hero__tag">JavaScript</li>
|
||||
<li class="hero__tag">Go</li>
|
||||
<li class="hero__tag">HTML/CSS</li>
|
||||
<li class="hero__tag">GLSL</li>
|
||||
<li class="hero__tag">aiogram 3</li>
|
||||
<li class="hero__tag">FastAPI</li>
|
||||
<li class="hero__tag">PostgreSQL</li>
|
||||
<li class="hero__tag">Redis</li>
|
||||
<li class="hero__tag">SQLite</li>
|
||||
<li class="hero__tag">Docker</li>
|
||||
<li class="hero__tag">Coolify</li>
|
||||
<li class="hero__tag">Gitea</li>
|
||||
<li class="hero__tag">MCP</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="links">Где я это выкладываю</h2>
|
||||
<p class="section__prose">
|
||||
Код открыт: <a href="https://github.com/mamadaevv" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
и <a href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin" target="_blank" rel="noopener noreferrer">Gitea</a>.
|
||||
Сервисы, которые работают прямо сейчас, живут на
|
||||
<a href="https://secrets-4-me.dynv6.net" target="_blank" rel="noopener noreferrer">secrets-4-me</a>.
|
||||
</p>
|
||||
<ul class="case-links-grid">
|
||||
<li>
|
||||
<a class="case-link-card" href="https://github.com/mamadaevv" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">GitHub</span>
|
||||
<span class="case-link-card__meta">Проекты и эксперименты с кодом</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="http://x1qnvk08zec2u5cx6yh5iexh.155.212.186.39.sslip.io/admin" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">Gitea</span>
|
||||
<span class="case-link-card__meta">Внутренние проекты и сервисы</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="{{casesPrefix}}site-portfolio/" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">Кейс этого сайта</span>
|
||||
<span class="case-link-card__meta">Как я делал сайт вместе с ИИ-агентом</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="case-link-card" href="https://secrets-4-me.dynv6.net" target="_blank" rel="noopener noreferrer">
|
||||
<span class="case-link-card__body">
|
||||
<span class="case-link-card__title">secrets-4-me</span>
|
||||
<span class="case-link-card__meta">Сервис, который работает сейчас</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -724,6 +724,26 @@
|
||||
</ul>
|
||||
<p class="sg-label">.mp-card__tags / .mp-card__tag</p>
|
||||
|
||||
<h3>Фильтр-чипы</h3>
|
||||
<p class="section__prose">
|
||||
Переключаемая группа тегов над списком карточек. Нажатый чип показывает только
|
||||
те элементы, у которых этот тег есть в <code>data-filter-tag</code>. Остальные
|
||||
скрываются атрибутом <code>hidden</code>, поэтому сетка не оставляет дыр.
|
||||
</p>
|
||||
<ul class="filter-chips" data-filter-chips data-filter-scope="#filter-demo" aria-label="Фильтр демо-карточек">
|
||||
<li><button type="button" class="filter-chip" data-filter-value="all" aria-pressed="true">Все</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="bots" aria-pressed="false">Боты</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="plugins" aria-pressed="false">Плагины</button></li>
|
||||
<li><button type="button" class="filter-chip" data-filter-value="web" aria-pressed="false">Веб</button></li>
|
||||
</ul>
|
||||
<ul class="mp-card__tags" id="filter-demo" style="display: grid; gap: 0.4rem; list-style: none; padding: 0; margin: 0.5rem 0">
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="bots web" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Бот на aiogram + веб-панель</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="bots" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Telegram-бот генерации картинок</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="plugins" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Плагин для Figma</li>
|
||||
<li class="filter-demo-item" data-filter-item data-filter-tag="plugins web" style="padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 0.88rem">Плагин для Obsidian</li>
|
||||
</ul>
|
||||
<p class="sg-label">.filter-chips / .filter-chip[aria-pressed] + [data-filter-item][data-filter-tag]</p>
|
||||
|
||||
<h3>Карточка портфолио</h3>
|
||||
<div class="sg-portfolio-wrap">
|
||||
<a class="mp-card mp-card--link mp-card--stack-bottom" href="#components" tabindex="-1" aria-hidden="true">
|
||||
|
||||
Reference in new issue
Block a user