From 95b4eaca93e7b2d4fa1859902ce70598cdcf2115 Mon Sep 17 00:00:00 2001
From: root
Date: Wed, 9 Sep 2026 17:49:56 +0200
Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B3=D0=B0=D0=BB=D0=B5=D1=80=D0=B5?=
=?UTF-8?q?=D1=8F-=D1=81=D0=BB=D0=B0=D0=B9=D0=B4=D0=B5=D1=80,=20=D0=BB?=
=?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B0=D0=BB=D0=BA=D0=B0=20=D0=B2=20=D0=BF?=
=?UTF-8?q?=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80=D1=89=D0=B8=D0=BA?=
=?UTF-8?q?=D0=B5=20=D0=B8=20TUI=20braille-=D1=81=D0=BF=D0=B8=D0=BD=D0=BD?=
=?UTF-8?q?=D0=B5=D1=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 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 при повторном открытии
---
DESIGN.md | 2 +
docs/case-page-blocks.md | 44 ++-
.../template-elements-demo/slider-demo-1.webp | Bin 0 -> 5646 bytes
.../template-elements-demo/slider-demo-2.webp | Bin 0 -> 6826 bytes
.../template-elements-demo/slider-demo-3.webp | Bin 0 -> 9448 bytes
.../template-elements-demo/slider-demo-4.webp | Bin 0 -> 8420 bytes
public/cases/design-system/index.html | 17 +-
.../cases/template-elements-demo/index.html | 38 ++
public/css/main.css | 331 +++++++++++++++++-
public/js/main.js | 2 +
public/js/modules/cases/image-viewer.js | 157 ++++++++-
public/js/modules/cases/media-slider.js | 141 ++++++++
src/pages/cases/design-system.html | 17 +-
src/pages/cases/template-elements-demo.html | 38 ++
14 files changed, 753 insertions(+), 34 deletions(-)
create mode 100644 public/assets/template-elements-demo/slider-demo-1.webp
create mode 100644 public/assets/template-elements-demo/slider-demo-2.webp
create mode 100644 public/assets/template-elements-demo/slider-demo-3.webp
create mode 100644 public/assets/template-elements-demo/slider-demo-4.webp
create mode 100644 public/js/modules/cases/media-slider.js
diff --git a/DESIGN.md b/DESIGN.md
index 50209c3..e6af351 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -172,6 +172,8 @@
| `.case-media--natural` | Изображение в натуральном масштабе |
| `.case-media--content` | По ширине текстовой колонки |
| `.case-media--wide` | Шире колонки статьи |
+| `.case-media-gallery--wide` | Шире колонки статьи (галерея) |
+| `.case-media-slider` | Слайдер: одна картинка, стрелки и точки-пагинация |
---
diff --git a/docs/case-page-blocks.md b/docs/case-page-blocks.md
index 3c7237b..1f076ee 100644
--- a/docs/case-page-blocks.md
+++ b/docs/case-page-blocks.md
@@ -141,6 +141,48 @@
**Сетка + отложенный `src`:** в ячейках можно задать реальный URL в **`data-src`** и плейсхолдер в `src` (крошечный `data:image/gif;base64,…`), чтобы [`initCaseSequentialImages`](public/js/modules/cases/sequential-images.js) подставлял картинки **по порядку в DOM** (в т.ч. строка за строкой в галерее). Пока у `img` есть `data-src`, просмотр по клику не открывается. Пример: [`src/pages/cases/lunacy.html`](src/pages/cases/lunacy.html).
+### Слайдер изображений с точками (`case-media-slider`)
+
+Одна картинка во весь блок, навигация стрелками «пред/след» и пагинация точками (активная — акцентным цветом). Переиспользует маски стрелок `shevron-left/right.svg` и токены темы. Логика: [`public/js/modules/cases/media-slider.js`](public/js/modules/cases/media-slider.js) (`initCaseMediaSlider()` вызывается из [`public/js/main.js`](public/js/main.js)).
+
+**Возможности:**
+
+- стрелки `data-slider-prev` / `data-slider-next` — переключают соседний слайд, блокируются (`disabled`) на краях;
+- точки `data-slider-dot` (в `data-slider-dots`) — переход на конкретный слайд, активная получает `is-active` + `aria-selected`;
+- клавиатура: ←/→ внутри блока, Home/End на точках;
+- тач/мышь-свайп (только горизонтальный);
+- ленивая подгрузка соседних кадров: у ` ` можно задать `data-src` (временный `src` — прозрачный GIF), скрипт подставит при подходе, как у галереи; пока есть `data-src`, клик по viewer не открывается;
+- клик по активному кадру открывает общий viewer кейса (`data-image-viewer`).
+
+**Разметка** (число `data-slider-dot` должно равняться числу `.case-media-slider__slide`; `aria-label` точек — «Перейти к изображению N»):
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Живой пример: [`src/pages/cases/template-elements-demo.html`](src/pages/cases/template-elements-demo.html) (секция «Слайдер»).
+
### Последовательная подгрузка (`data-case-sequential-images`)
- Оберни фрагмент статьи с картинками (часто весь `.case-article`): **`data-case-sequential-images`** на контейнере.
@@ -183,7 +225,7 @@ PNG/JPEG → WebP по папке: `docs/image-optimization.md`, скрипт `s
Подключаемые через `{{> head-links}}` сценарии уже инициализируют:
- последовательную подгрузку `data-src` внутри `[data-case-sequential-images]` (`initCaseSequentialImages`);
-- просмотр картинок по клику (`data-image-viewer`, делегирование на документ);
+- просмотр картинок по клику (`data-image-viewer`, делегирование на документ); при открытии из **галереи** (`.case-media-gallery`) или **слайдера** (`.case-media-slider`) в нижней панели просмотрщика появляются **кнопки-пилюли «пред/след»** и **точки-пагинация** — переключают только картинки этого блока (клавиши ←/→, свайп по горизонтали, клик по точке); у одиночной картинки кнопки и точки скрыты;
- плеер видео кейса (`data-case-video-player`);
- натуральный размер для `.case-media--natural img`;
- сворачивание длинного скрина (`[data-case-tall-media]`, `initCaseTallMediaExpand`).
diff --git a/public/assets/template-elements-demo/slider-demo-1.webp b/public/assets/template-elements-demo/slider-demo-1.webp
new file mode 100644
index 0000000000000000000000000000000000000000..66ec9a92800f5d1c6cbded4e80dba20cd4742308
GIT binary patch
literal 5646
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
opQ1+n+a
literal 0
HcmV?d00001
diff --git a/public/assets/template-elements-demo/slider-demo-2.webp b/public/assets/template-elements-demo/slider-demo-2.webp
new file mode 100644
index 0000000000000000000000000000000000000000..bf53716b570117084e747728ae7ce36be99769a9
GIT binary patch
literal 6826
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 ;&aWW5E$PX&@@E#%PAY`1Y3qL=_CP^SDw2M(dn
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
Y^kSrm;_!`@01_0d-|=`OFCYK_0DuW(!vFvP
literal 0
HcmV?d00001
diff --git a/public/assets/template-elements-demo/slider-demo-3.webp b/public/assets/template-elements-demo/slider-demo-3.webp
new file mode 100644
index 0000000000000000000000000000000000000000..e48474ada7a64afdbbb6bd8a67f56471551c99c7
GIT binary patch
literal 9448
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
GFmM32v|qyj
literal 0
HcmV?d00001
diff --git a/public/cases/design-system/index.html b/public/cases/design-system/index.html
index b8b6c3f..cb81218 100644
--- a/public/cases/design-system/index.html
+++ b/public/cases/design-system/index.html
@@ -953,6 +953,19 @@
>
.has-img-loading · .is-img-loading · .img-ph-demo (демо заглушки)
+ TUI braille-спиннер
+
+ Загрузка тяжёлых изображений в просмотрщике (.case-image-viewer) показывается
+ моноширинным braille-спиннером — как в TUI. Чистый CSS, без JS: 10 кадров переключаются
+ через steps()-анимацию. Переиспользуемый класс .tui-spinner
+ (цвет — --accent, шрифт — системный mono).
+
+
+ ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
+ ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
+
+ .tui-spinner · кадры: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion показывает ⠋
+
Карточка навыка
skill-task-card__task — формулировка задачи от бизнеса.
@@ -1044,7 +1057,7 @@
Живой эталон со всеми вариантами — на отдельной странице; оттуда копируют куски при сборке нового кейса.
- Демо элементов кейса
+ Демо элементов кейса
@@ -1055,7 +1068,7 @@
раскладка вынесены на отдельную страницу.
- Каталог карточек активности
+ Каталог карточек активности
diff --git a/public/cases/template-elements-demo/index.html b/public/cases/template-elements-demo/index.html
index f65d344..d01804a 100644
--- a/public/cases/template-elements-demo/index.html
+++ b/public/cases/template-elements-demo/index.html
@@ -430,6 +430,44 @@
Ширина как у wide-картинки; на узком экране wide-обёртка схлопывается в 100% ширины колонки (см. медиазапросы).
+ Слайдер: одна картинка, стрелки и точки
+
+ Блок .case-media-slider показывает слайды по одному: навигация стрелками «пред/след»,
+ пагинация точками (активная — акцентным цветом). Поддерживается клавиатура (←/→, Home/End на точках),
+ тач/мышь-свайп и ленивая подгрузка соседних кадров через data-src. Клик по активному
+ кадру открывает общий viewer кейса.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Callout: важный абзац с эмоджи
📌
diff --git a/public/css/main.css b/public/css/main.css
index b270bca..e561d1a 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -2162,11 +2162,18 @@ html[data-theme="dark"] {
}
.case-image-viewer__zoom {
- min-width: 3.5rem;
+ min-width: 2.5rem;
text-align: center;
font-size: 0.82rem;
- color: var(--text);
- font-weight: 600;
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+/* Группа слайдер + текст масштаба с уменьшенным внутренним зазором */
+.case-image-viewer__scale-group {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
}
.case-image-viewer__slider {
@@ -2310,29 +2317,82 @@ html[data-theme="dark"] {
opacity 0.16s ease;
}
-.case-image-viewer__loading {
+/* TUI braille-спиннер (переиспользуемый, чистый CSS) */
+.tui-spinner {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.1em;
+ height: 1.1em;
+ font-family: ui-monospace, "Cascadia Code", "Consolas", "Courier New", monospace;
+ font-size: 1.5rem;
+ line-height: 1;
+ letter-spacing: 0.1em;
+ color: var(--accent);
+}
+
+.tui-spinner > span {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ animation: tui-spinner-step 0.9s steps(1, end) infinite;
+}
+
+.tui-spinner > span:nth-child(1) { animation-delay: 0s; }
+.tui-spinner > span:nth-child(2) { animation-delay: 0.09s; }
+.tui-spinner > span:nth-child(3) { animation-delay: 0.18s; }
+.tui-spinner > span:nth-child(4) { animation-delay: 0.27s; }
+.tui-spinner > span:nth-child(5) { animation-delay: 0.36s; }
+.tui-spinner > span:nth-child(6) { animation-delay: 0.45s; }
+.tui-spinner > span:nth-child(7) { animation-delay: 0.54s; }
+.tui-spinner > span:nth-child(8) { animation-delay: 0.63s; }
+.tui-spinner > span:nth-child(9) { animation-delay: 0.72s; }
+.tui-spinner > span:nth-child(10) { animation-delay: 0.81s; }
+
+@keyframes tui-spinner-step {
+ 0%, 9.999% { opacity: 1; }
+ 10%, 100% { opacity: 0; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .tui-spinner > span { animation: none; opacity: 0; }
+ .tui-spinner > span:nth-child(1) { opacity: 1; }
+}
+
+/* Лоадер изображения в просмотрщике (обёртка вокруг .tui-spinner) */
+.case-image-viewer__loader {
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
transform: translate(-50%, -50%);
- min-width: 8.5rem;
- padding: 0.5rem 0.8rem;
- border-radius: 999px;
- text-align: center;
- font-size: 0.8rem;
- color: var(--text);
- background: color-mix(in srgb, var(--surface) 86%, transparent);
- border: 1px solid color-mix(in srgb, var(--line) 88%, transparent);
+ display: inline-flex;
+ align-items: center;
+ text-shadow: 0 1px 6px hsla(0, 0%, 0%, 0.35);
pointer-events: none;
opacity: 0;
transition: opacity 0.14s ease;
}
-.case-image-viewer.is-loading .case-image-viewer__loading {
+.case-image-viewer.is-loading .case-image-viewer__loader {
opacity: 1;
}
+.case-image-viewer__loader.is-error .tui-spinner {
+ display: none;
+}
+
+.case-image-viewer__loader-msg {
+ font-family: var(--font, inherit);
+ font-size: 0.82rem;
+ color: var(--text-muted);
+ margin-left: 0.5rem;
+}
+
.case-image-viewer.is-loading .case-image-viewer__img {
opacity: 0;
}
@@ -2347,6 +2407,80 @@ html[data-theme="dark"] {
cursor: grabbing;
}
+/* Кнопки пред/след в тулбаре: группа с отрицательным зазором и скруглением только снаружи */
+.case-image-viewer__nav-group {
+ display: inline-flex;
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.case-image-viewer__btn--nav {
+ --imgv-nav-size: 2rem;
+ width: var(--imgv-nav-size);
+ height: var(--imgv-nav-size);
+ padding: 0;
+ border-radius: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Соседняя кнопка сдвигается на 1px, перекрывая разделительный бордер */
+.case-image-viewer__btn--nav + .case-image-viewer__btn--nav {
+ margin-left: -1px;
+}
+
+.case-image-viewer__btn--nav[hidden] {
+ display: none;
+}
+
+/* Скругления только на крайних кнопках группы (все 4 угла заданы явно) */
+.case-image-viewer__btn--nav:first-child {
+ border-radius: 999px 0 0 999px;
+}
+
+.case-image-viewer__btn--nav:last-child {
+ border-radius: 0 999px 999px 0;
+}
+
+/* У одиночной кнопки в группе — скругление со всех сторон */
+.case-image-viewer__btn--nav:only-child {
+ border-radius: 999px;
+}
+
+/* Точки-пагинация в тулбаре просмотрщика */
+.case-image-viewer__dots {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ padding: 0 0.15rem;
+}
+
+.case-image-viewer__dots[hidden] {
+ display: none;
+}
+
+.case-image-viewer__dot {
+ width: 0.5rem;
+ height: 0.5rem;
+ padding: 0;
+ border: none;
+ border-radius: 50%;
+ background: color-mix(in srgb, var(--text-muted) 40%, transparent);
+ cursor: pointer;
+ transition: background 0.18s ease, transform 0.18s ease;
+}
+
+.case-image-viewer__dot:hover,
+.case-image-viewer__dot:focus-visible {
+ background: color-mix(in srgb, var(--text-muted) 65%, transparent);
+}
+
+.case-image-viewer__dot.is-active {
+ background: var(--accent);
+ transform: scale(1.25);
+}
+
@media (max-width: 640px) {
.case-link-card {
grid-template-columns: 1fr;
@@ -5194,3 +5328,174 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
.sep--air-lg {
height: 2.5rem;
}
+
+/* Слайдер изображений в лонгриде кейса: одна картинка + стрелки + точки */
+.case-media-slider {
+ --slider-gap: 0;
+ --slider-radius: var(--radius-card);
+ --slider-ctrl-size: 2.5rem;
+ --slider-ctrl-inset: 0.75rem;
+ --slider-dot-size: 0.5rem;
+ --slider-dot-gap: 0.5rem;
+ position: relative;
+ margin: 0;
+ overflow: hidden;
+ border-radius: var(--slider-radius);
+}
+
+.case-media-slider__viewport {
+ overflow: hidden;
+ width: 100%;
+ touch-action: pan-y;
+ cursor: grab;
+}
+
+.case-media-slider__viewport:active {
+ cursor: grabbing;
+}
+
+.case-media-slider__track {
+ display: flex;
+ width: 100%;
+ transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
+ will-change: transform;
+}
+
+.case-media-slider__slide {
+ flex: 0 0 100%;
+ min-width: 0;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.case-media-slider__slide img {
+ display: block;
+ width: 100%;
+ height: auto;
+ max-height: 80vh;
+ object-fit: contain;
+ border-radius: var(--slider-radius);
+}
+
+/* Подпись под активным слайдом (как у case-media) */
+.case-media-slider__slide figcaption,
+.case-media-slider figcaption {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 0.6rem 0.9rem;
+ font-size: 0.82rem;
+ color: var(--text-muted);
+ text-align: center;
+ background: color-mix(in srgb, var(--surface) 78%, transparent);
+ backdrop-filter: blur(6px);
+ border-bottom-left-radius: var(--slider-radius);
+ border-bottom-right-radius: var(--slider-radius);
+}
+
+/* Стрелки */
+.case-media-slider__nav {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: var(--slider-ctrl-size);
+ height: var(--slider-ctrl-size);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--line);
+ border-radius: 50%;
+ background: color-mix(in srgb, var(--surface) 86%, transparent);
+ color: var(--text-muted);
+ cursor: pointer;
+ z-index: 3;
+ transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease,
+ opacity 0.18s ease;
+}
+
+.case-media-slider__nav--prev {
+ left: var(--slider-ctrl-inset);
+}
+
+.case-media-slider__nav--next {
+ right: var(--slider-ctrl-inset);
+}
+
+.case-media-slider__nav:hover:not(:disabled),
+.case-media-slider__nav:focus-visible:not(:disabled) {
+ color: var(--accent-text);
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
+ background: var(--accent-soft);
+}
+
+.case-media-slider__nav:disabled {
+ opacity: 0.35;
+ cursor: default;
+}
+
+.case-media-slider__nav-ico {
+ width: 1rem;
+ height: 1rem;
+ display: block;
+ background: currentColor;
+ mask-size: contain;
+ -webkit-mask-size: contain;
+ mask-repeat: no-repeat;
+ -webkit-mask-repeat: no-repeat;
+ mask-position: center;
+ -webkit-mask-position: center;
+}
+
+.case-media-slider__nav-ico--left {
+ mask-image: url("../assets/shared/icons/shevron-left.svg");
+ -webkit-mask-image: url("../assets/shared/icons/shevron-left.svg");
+}
+
+.case-media-slider__nav-ico--right {
+ mask-image: url("../assets/shared/icons/shevron-right.svg");
+ -webkit-mask-image: url("../assets/shared/icons/shevron-right.svg");
+}
+
+/* Точки-пагинация */
+.case-media-slider__dots {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: var(--slider-dot-gap);
+ margin-top: 0.9rem;
+}
+
+.case-media-slider__dot {
+ width: var(--slider-dot-size);
+ height: var(--slider-dot-size);
+ padding: 0;
+ border: none;
+ border-radius: 50%;
+ background: color-mix(in srgb, var(--text-muted) 40%, transparent);
+ cursor: pointer;
+ transition: background 0.18s ease, transform 0.18s ease;
+}
+
+.case-media-slider__dot:hover,
+.case-media-slider__dot:focus-visible {
+ background: color-mix(in srgb, var(--text-muted) 65%, transparent);
+}
+
+.case-media-slider__dot.is-active {
+ background: var(--accent);
+ transform: scale(1.25);
+}
+
+/* Неактивные слайды скрыты от скринридеров и не ловят клики по viewer */
+.case-media-slider__slide:not(.is-active) {
+ visibility: hidden;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .case-media-slider__track {
+ transition: none;
+ }
+}
diff --git a/public/js/main.js b/public/js/main.js
index a83bd71..f3f0303 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -20,6 +20,7 @@ import { initHeroMotionVideo } from "./modules/ui/hero-motion-video.js";
import { initPageMeshBackground } from "./modules/ui/page-mesh-bg.js";
import { initPrimaryButtonShine } from "./modules/ui/primary-button-shine.js";
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 { initContentEditMode } from "./modules/ui/content-edit/content-edit-mode.js";
@@ -35,6 +36,7 @@ const miniProjectsMasonry = initMiniProjectsMasonry();
initThemeControls();
initPrimaryButtonShine();
initProjectsCarousel();
+initCaseMediaSlider();
initDeferredPortfolioCovers();
initImageLoadPlaceholder();
initSkillsAccordion();
diff --git a/public/js/modules/cases/image-viewer.js b/public/js/modules/cases/image-viewer.js
index fee9394..e73fd6f 100644
--- a/public/js/modules/cases/image-viewer.js
+++ b/public/js/modules/cases/image-viewer.js
@@ -15,8 +15,19 @@ export function initCaseImageViewer() {
overlay.hidden = true;
overlay.innerHTML = `
-
Загрузка...
+
⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
`;
@@ -37,11 +48,15 @@ export function initCaseImageViewer() {
const stage = overlay.querySelector("[data-imgv-stage]");
const viewImg = overlay.querySelector("[data-imgv-img]");
const loading = overlay.querySelector("[data-imgv-loading]");
+ const loaderMsg = overlay.querySelector("[data-imgv-loader-msg]");
const zoomLabel = overlay.querySelector("[data-imgv-zoom-label]");
const zoomSlider = overlay.querySelector("[data-imgv-slider]");
const btnFit = overlay.querySelector("[data-imgv-fit]");
const btnOriginal = overlay.querySelector("[data-imgv-original]");
const btnClose = overlay.querySelector("[data-imgv-close]");
+ const btnPrev = overlay.querySelector("[data-imgv-prev]");
+ const btnNext = overlay.querySelector("[data-imgv-next]");
+ const dotsWrap = overlay.querySelector("[data-imgv-dots]");
let scale = 1;
let naturalW = 0;
@@ -56,11 +71,33 @@ export function initCaseImageViewer() {
let panScrollLeft = 0;
let panScrollTop = 0;
let panMoved = false;
+ let panDirX = 0;
let closeTimer = 0;
let openRaf = 0;
const viewerAnimMs = 210;
let centeredAnchor = null;
+ // Состояние листалки: группа картинок (например, одна галерея) + индекс в ней.
+ let groupImgs = [];
+ let groupIndex = -1;
+
+ function resolveGroup(img) {
+ // Поднимаемся до ближайшего контейнера группы, пропуская отдельные ячейки/слайды.
+ const container = img.closest(
+ ".case-media-gallery, .case-media-slider, .case-media, figure",
+ );
+ const scope = container || img.parentElement || document;
+ const list = Array.from(
+ scope.querySelectorAll("img[data-image-viewer]"),
+ ).filter((el) => {
+ if (el.hasAttribute("data-src")) return false;
+ const s = el.getAttribute("src") || el.currentSrc || "";
+ if (isViewerThumbnailPlaceholder(s)) return false;
+ return true;
+ });
+ return list;
+ }
+
function measureToolbarSpace() {
const toolbarRect = overlay.querySelector(".case-image-viewer__toolbar")?.getBoundingClientRect();
const next = toolbarRect ? Math.ceil(toolbarRect.height + 22) : 0;
@@ -160,7 +197,12 @@ export function initCaseImageViewer() {
centeredAnchor = null;
}
- function openViewer(src, alt) {
+ function showAt(group, index) {
+ if (index < 0 || index >= group.length) return;
+ groupIndex = index;
+ const img = group[groupIndex];
+ const src = img.getAttribute("src") || img.currentSrc || "";
+ const alt = img.alt || "";
if (closeTimer) {
window.clearTimeout(closeTimer);
closeTimer = 0;
@@ -171,15 +213,52 @@ export function initCaseImageViewer() {
}
overlay.classList.remove("is-open");
overlay.classList.add("is-loading");
+ if (loading) loading.classList.remove("is-error");
+ if (loaderMsg) loaderMsg.textContent = "";
centeredAnchor = null;
viewImg.onload = null;
viewImg.onerror = null;
viewImg.src = src;
- viewImg.alt = alt || "";
- lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
- overlay.hidden = false;
- document.documentElement.classList.add("imgv-lock");
- active = true;
+ viewImg.alt = alt;
+ // Открываем просмотрщик сразу (поверх показывается спиннер), не дожидаясь загрузки.
+ openRaf = window.requestAnimationFrame(() => {
+ overlay.classList.add("is-open");
+ openRaf = 0;
+ });
+
+ // Навигация внутри группы (галерея / слайдер). Скрываем, если картинка одна.
+ const multi = group.length > 1;
+ btnPrev.hidden = !multi;
+ btnNext.hidden = !multi;
+ if (multi) {
+ // Точки-пагинация в тулбаре.
+ if (dotsWrap.childElementCount !== group.length) {
+ dotsWrap.innerHTML = group
+ .map(
+ (_, i) =>
+ ` `,
+ )
+ .join("");
+ dotsWrap.querySelectorAll("[data-imgv-dot]").forEach((dot) => {
+ dot.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const i = Number(dot.getAttribute("data-imgv-dot"));
+ showAt(group, i);
+ });
+ });
+ }
+ dotsWrap.hidden = false;
+ dotsWrap.querySelectorAll("[data-imgv-dot]").forEach((dot, i) => {
+ const active = i === groupIndex;
+ dot.classList.toggle("is-active", active);
+ dot.setAttribute("aria-selected", active ? "true" : "false");
+ dot.tabIndex = active ? 0 : -1;
+ });
+ btnPrev.disabled = groupIndex === 0;
+ btnNext.disabled = groupIndex === group.length - 1;
+ } else {
+ dotsWrap.hidden = true;
+ }
const applyAfterLoad = () => {
overlay.classList.remove("is-loading");
@@ -203,10 +282,10 @@ export function initCaseImageViewer() {
const applyLoadError = () => {
overlay.classList.remove("is-loading");
- if (loading) loading.textContent = "Не удалось загрузить";
+ if (loading) loading.classList.add("is-error");
+ if (loaderMsg) loaderMsg.textContent = "Не удалось загрузить";
};
- if (loading) loading.textContent = "Загрузка...";
if (viewImg.complete && viewImg.naturalWidth > 0) applyAfterLoad();
else {
viewImg.onload = applyAfterLoad;
@@ -228,7 +307,8 @@ export function initCaseImageViewer() {
overlay.hidden = true;
stage.classList.remove("is-center-y");
stage.classList.remove("is-centered");
- if (loading) loading.textContent = "Загрузка...";
+ if (loading) loading.classList.remove("is-error");
+ if (loaderMsg) loaderMsg.textContent = "";
viewImg.onload = null;
viewImg.onerror = null;
viewImg.removeAttribute("src");
@@ -249,7 +329,15 @@ export function initCaseImageViewer() {
const src = img.currentSrc || img.getAttribute("src") || "";
if (!src || isViewerThumbnailPlaceholder(src)) return;
e.preventDefault();
- openViewer(src, img.alt || "");
+ const group = resolveGroup(img);
+ const index = group.indexOf(img);
+ groupImgs = group;
+ groupIndex = index >= 0 ? index : 0;
+ lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
+ overlay.hidden = false;
+ document.documentElement.classList.add("imgv-lock");
+ active = true;
+ showAt(groupImgs, groupIndex >= 0 ? groupIndex : 0);
},
true,
);
@@ -265,8 +353,24 @@ export function initCaseImageViewer() {
btnOriginal.addEventListener("click", () => zoomTo(1));
btnClose.addEventListener("click", closeViewer);
+ function navigateGroup(dir) {
+ if (!groupImgs.length) return;
+ const next = Math.min(Math.max(groupIndex + dir, 0), groupImgs.length - 1);
+ if (next === groupIndex) return;
+ showAt(groupImgs, next);
+ }
+
+ btnPrev.addEventListener("click", (e) => {
+ e.stopPropagation();
+ navigateGroup(-1);
+ });
+ btnNext.addEventListener("click", (e) => {
+ e.stopPropagation();
+ navigateGroup(1);
+ });
+
overlay.addEventListener("click", (e) => {
- if (e.target === overlay) closeViewer();
+ if (e.target === overlay && !overlay.classList.contains("is-loading")) closeViewer();
});
stage.addEventListener(
@@ -300,7 +404,10 @@ export function initCaseImageViewer() {
if (!active || !isPanning) return;
const dx = e.clientX - panStartX;
const dy = e.clientY - panStartY;
- if (Math.abs(dx) > 3 || Math.abs(dy) > 3) panMoved = true;
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
+ panMoved = true;
+ panDirX = dx;
+ }
stage.scrollLeft = panScrollLeft - dx;
stage.scrollTop = panScrollTop - dy;
e.preventDefault();
@@ -314,9 +421,16 @@ export function initCaseImageViewer() {
}
stage.addEventListener("pointerup", (e) => {
+ const horizSwipe = panMoved && Math.abs(panDirX) > 40 && groupImgs.length > 1;
const shouldClose = !panMoved;
stopPan(e.pointerId);
- if (shouldClose && active) {
+ if (horizSwipe) {
+ e.preventDefault();
+ navigateGroup(panDirX < 0 ? 1 : -1);
+ return;
+ }
+ // Пока грузится изображение — не закрываем по тапу/клику.
+ if (shouldClose && active && !overlay.classList.contains("is-loading")) {
e.preventDefault();
closeViewer();
}
@@ -365,6 +479,17 @@ export function initCaseImageViewer() {
if (e.key === "0") {
e.preventDefault();
zoomTo(fitScale());
+ return;
+ }
+ if (e.key === "ArrowLeft") {
+ e.preventDefault();
+ navigateGroup(-1);
+ return;
+ }
+ if (e.key === "ArrowRight") {
+ e.preventDefault();
+ navigateGroup(1);
+ return;
}
});
}
diff --git a/public/js/modules/cases/media-slider.js b/public/js/modules/cases/media-slider.js
new file mode 100644
index 0000000..5943f78
--- /dev/null
+++ b/public/js/modules/cases/media-slider.js
@@ -0,0 +1,141 @@
+// Слайдер изображений для лонгридов кейсов (одна картинка + стрелки + точки).
+// Разметка:
+//
+//
+// …
+// …
+//
+//
+// ...
+//
+//
+
+function clampIndex(i, len) {
+ if (len <= 0) return 0;
+ return Math.min(Math.max(i, 0), len - 1);
+}
+
+function loadSlide(slide) {
+ if (!slide) return;
+ const img = slide.querySelector("img[data-src]");
+ if (img && img.dataset.src) {
+ img.src = img.dataset.src;
+ delete img.dataset.src;
+ }
+}
+
+function loadAround(slides, index) {
+ loadSlide(slides[index]);
+ loadSlide(slides[index - 1]);
+ loadSlide(slides[index + 1]);
+}
+
+export function initCaseMediaSlider() {
+ const roots = document.querySelectorAll("[data-case-media-slider]");
+ roots.forEach((root) => {
+ const viewport = root.querySelector("[data-slider-viewport]");
+ const track = root.querySelector("[data-slider-track]");
+ const prevBtn = root.querySelector("[data-slider-prev]");
+ const nextBtn = root.querySelector("[data-slider-next]");
+ const dotsWrap = root.querySelector("[data-slider-dots]");
+ if (!viewport || !track) return;
+
+ const slides = Array.from(track.querySelectorAll(":scope > .case-media-slider__slide"));
+ const dots = dotsWrap
+ ? Array.from(dotsWrap.querySelectorAll(":scope > [data-slider-dot]"))
+ : [];
+ const count = slides.length;
+ if (count === 0) return;
+
+ let current = 0;
+
+ const reduceMotion = window.matchMedia(
+ "(prefers-reduced-motion: reduce)"
+ ).matches;
+
+ function goTo(index, behavior) {
+ const next = clampIndex(index, count);
+ current = next;
+ const offset = -100 * next;
+ track.style.transform = `translateX(${offset}%)`;
+ track.style.transition = reduceMotion || behavior === "auto"
+ ? "none"
+ : "";
+ slides.forEach((s, i) => {
+ s.classList.toggle("is-active", i === next);
+ s.setAttribute("aria-hidden", i === next ? "false" : "true");
+ });
+ dots.forEach((d, i) => {
+ const active = i === next;
+ d.classList.toggle("is-active", active);
+ d.setAttribute("aria-selected", active ? "true" : "false");
+ d.tabIndex = active ? 0 : -1;
+ });
+ if (prevBtn) prevBtn.disabled = next === 0;
+ if (nextBtn) nextBtn.disabled = next === count - 1;
+ loadAround(slides, next);
+ }
+
+ prevBtn?.addEventListener("click", () => goTo(current - 1));
+ nextBtn?.addEventListener("click", () => goTo(current + 1));
+
+ dots.forEach((dot, i) => {
+ dot.addEventListener("click", () => goTo(i));
+ });
+
+ // Клавиатура: стрелки при фокусе внутри слайдера.
+ root.addEventListener("keydown", (e) => {
+ if (e.key === "ArrowLeft") {
+ e.preventDefault();
+ goTo(current - 1);
+ } else if (e.key === "ArrowRight") {
+ e.preventDefault();
+ goTo(current + 1);
+ }
+ });
+
+ // Поддержка свопа по клику на точку при навигации с клавиатуры.
+ dotsWrap?.addEventListener("keydown", (e) => {
+ if (e.key === "Home") {
+ e.preventDefault();
+ goTo(0);
+ } else if (e.key === "End") {
+ e.preventDefault();
+ goTo(count - 1);
+ }
+ });
+
+ // Свайп на тач/мыши (только горизонтальный).
+ let drag = null;
+ viewport.addEventListener("pointerdown", (e) => {
+ if (e.button !== 0) return;
+ drag = { x: e.clientX, moved: false };
+ });
+ viewport.addEventListener("pointermove", (e) => {
+ if (!drag) return;
+ if (Math.abs(e.clientX - drag.x) > 6) drag.moved = true;
+ });
+ function endDrag(e) {
+ if (!drag) return;
+ const dx = e.clientX - drag.x;
+ if (drag.moved && Math.abs(dx) > 40) {
+ goTo(current + (dx < 0 ? 1 : -1));
+ }
+ drag = null;
+ }
+ viewport.addEventListener("pointerup", endDrag);
+ viewport.addEventListener("pointercancel", endDrag);
+
+ // Инициализация.
+ slides.forEach((s, i) => {
+ s.setAttribute("aria-hidden", i === 0 ? "false" : "true");
+ s.classList.toggle("is-active", i === 0);
+ });
+ goTo(0, "auto");
+ });
+}
diff --git a/src/pages/cases/design-system.html b/src/pages/cases/design-system.html
index 428f09a..be42313 100644
--- a/src/pages/cases/design-system.html
+++ b/src/pages/cases/design-system.html
@@ -765,6 +765,19 @@
>
.has-img-loading · .is-img-loading · .img-ph-demo (демо заглушки)
+ TUI braille-спиннер
+
+ Загрузка тяжёлых изображений в просмотрщике (.case-image-viewer) показывается
+ моноширинным braille-спиннером — как в TUI. Чистый CSS, без JS: 10 кадров переключаются
+ через steps()-анимацию. Переиспользуемый класс .tui-spinner
+ (цвет — --accent, шрифт — системный mono).
+
+
+ ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
+ ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏
+
+ .tui-spinner · кадры: ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ · prefers-reduced-motion показывает ⠋
+
Карточка навыка
skill-task-card__task — формулировка задачи от бизнеса.
@@ -856,7 +869,7 @@
Живой эталон со всеми вариантами — на отдельной странице; оттуда копируют куски при сборке нового кейса.
- Демо элементов кейса
+ Демо элементов кейса
@@ -867,7 +880,7 @@
раскладка вынесены на отдельную страницу.
- Каталог карточек активности
+ Каталог карточек активности
diff --git a/src/pages/cases/template-elements-demo.html b/src/pages/cases/template-elements-demo.html
index e2e92f4..7222831 100644
--- a/src/pages/cases/template-elements-demo.html
+++ b/src/pages/cases/template-elements-demo.html
@@ -241,6 +241,44 @@
Ширина как у wide-картинки; на узком экране wide-обёртка схлопывается в 100% ширины колонки (см. медиазапросы).
+ Слайдер: одна картинка, стрелки и точки
+
+ Блок .case-media-slider показывает слайды по одному: навигация стрелками «пред/след»,
+ пагинация точками (активная — акцентным цветом). Поддерживается клавиатура (←/→, Home/End на точках),
+ тач/мышь-свайп и ленивая подгрузка соседних кадров через data-src. Клик по активному
+ кадру открывает общий viewer кейса.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Callout: важный абзац с эмоджи