feat: галерея-слайдер, листалка в просмотрщике и TUI braille-спиннер

- case-media-slider: переиспользуемый слайдер (prev/next + точки, swipe, клавиатура, lazy)
- image-viewer: листалка prev/next в тулбаре как пилюли, пагинация-точки в тулбаре слева,
  группа масштаба (slider + текст) с gap 0.25rem, текст масштаба --text-muted/400
- tui-spinner: переиспользуемый braille-спиннер (чистый CSS), лоадер просмотрщика
- design-system.html: живой пример TUI braille-спиннера
- fix: удалён мёртвый textContent='Загрузка...', убивавший .tui-spinner при повторном открытии
This commit is contained in:
root committed 2026-09-09 17:49:56 +02:00
1 parent 9b91ed165f
commit 95b4eaca93
14 files changed
+753 -34

No files matched your search

+15 -2
View File
@@ -765,6 +765,19 @@
></div>
<p class="sg-label">.has-img-loading · .is-img-loading · .img-ph-demo (демо заглушки)</p>
<h3>TUI braille-спиннер</h3>
<p class="section__prose">
Загрузка тяжёлых изображений в просмотрщике (<code>.case-image-viewer</code>) показывается
моноширинным braille-спиннером — как в TUI. Чистый CSS, без JS: 10 кадров переключаются
через <code>steps()</code>-анимацию. Переиспользуемый класс <code>.tui-spinner</code>
(цвет — <code>--accent</code>, шрифт — системный mono).
</p>
<div class="sg-stack sg-stack--row sg-stack--center" style="gap: 1.5rem; padding: 1.25rem; background: color-mix(in srgb, var(--surface) 70%, transparent); border: 1px solid var(--line); border-radius: var(--radius-card, 14px);">
<span class="tui-spinner" aria-label="Загрузка" role="status"><span>⠋</span><span>⠙</span><span>⠹</span><span>⠸</span><span>⠼</span><span>⠴</span><span>⠦</span><span>⠧</span><span>⠇</span><span>⠏</span></span>
<code class="tui-spinner-seq" style="font-family: ui-monospace, 'Cascadia Code', Consolas, monospace; color: var(--text-muted); font-size: 0.95rem;">⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏</code>
</div>
<p class="sg-label">.tui-spinner · кадры: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion показывает ⠋</p>
<h3>Карточка навыка</h3>
<article class="skill-task-card">
<p class="skill-task-card__task">skill-task-card__task — формулировка задачи от бизнеса.</p>
@@ -856,7 +869,7 @@
Живой эталон со всеми вариантами — на отдельной странице; оттуда копируют куски при сборке нового кейса.
</p>
<p>
<a class="btn btn--secondary btn--flat" href="template-elements-demo/">Демо элементов кейса</a>
<a class="btn btn--secondary btn--flat" href="../template-elements-demo/">Демо элементов кейса</a>
</p>
</div>
@@ -867,7 +880,7 @@
раскладка вынесены на отдельную страницу.
</p>
<p>
<a class="btn btn--secondary btn--flat" href="mini-projects-showcase/">Каталог карточек активности</a>
<a class="btn btn--secondary btn--flat" href="../mini-projects-showcase/">Каталог карточек активности</a>
</p>
</div>
</div>
@@ -241,6 +241,44 @@
<figcaption>Ширина как у wide-картинки; на узком экране wide-обёртка схлопывается в 100% ширины колонки (см. медиазапросы).</figcaption>
</figure>
<h3>Слайдер: одна картинка, стрелки и точки</h3>
<p class="section__prose">
Блок <code>.case-media-slider</code> показывает слайды по одному: навигация стрелками «пред/след»,
пагинация точками (активная — акцентным цветом). Поддерживается клавиатура (←/→, Home/End на точках),
тач/мышь-свайп и ленивая подгрузка соседних кадров через <code>data-src</code>. Клик по активному
кадру открывает общий viewer кейса.
</p>
<figure class="case-media case-media-slider" data-case-media-slider>
<div class="case-media-slider__viewport" data-slider-viewport>
<div class="case-media-slider__track" data-slider-track>
<div class="case-media-slider__slide">
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-1.webp" alt="Слайдер, кадр 1" loading="lazy" decoding="async" data-image-viewer />
</div>
<div class="case-media-slider__slide">
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-2.webp" alt="Слайдер, кадр 2" loading="lazy" decoding="async" data-image-viewer />
</div>
<div class="case-media-slider__slide">
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-3.webp" alt="Слайдер, кадр 3" loading="lazy" decoding="async" data-image-viewer />
</div>
<div class="case-media-slider__slide">
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-4.webp" alt="Слайдер, кадр 4" loading="lazy" decoding="async" data-image-viewer />
</div>
</div>
</div>
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left" aria-hidden="true"></span>
</button>
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right" aria-hidden="true"></span>
</button>
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
</div>
</figure>
<h3>Callout: важный абзац с эмоджи</h3>
<aside class="case-callout" data-callout>
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">📌</span>