Files
site-portfolio/docs/case-page-blocks.md
T

200 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Страница кейса: каркас и блоки контента
Инструкция завязана на эталон **`src/pages/cases/template-elements-demo.html`**: оттуда копируются готовые куски разметки. Текстовый дубль структуры для редактуры — `texts/cases/template-elements-demo.md`.
## Где править
| Что | Путь |
|-----|------|
| Разметка кейса | `src/pages/cases/<имя>.html` |
| Общие вставки (шапка кейса, футер, скрипты) | `src/partials/*.html` |
| Сборка в статику | `npm run build:pages` → `public/cases/<имя>.html` |
**`public/cases/*.html` не редактировать вручную** — они перезаписываются сборкой.
Локальные инструкции агенту без правки HTML в браузере: режим правок при `npm run serve` → [`docs/content-edit-mode.md`](content-edit-mode.md) → `texts/content-edits/*.json` → apply в `src` по запросу в чате.
## Каркас `body` (как в template)
Порядок важен для единообразия с остальными кейсами:
1. `{{> header-case}}` — шапка с ссылкой на главную (внутри partial используется `{{assetPrefix}}`).
2. `{{> fab-stack}}` — плавающие кнопки темы и т.п.
3. `<main id="case-main" class="section section--tight">` → внутри `<div class="section__inner case-article">` — **весь уникальный контент кейса**.
4. Нижние секции как на главной: `{{> projects-section}}`, `{{> contact-section}}`, `{{> footer-spacer}}`, `{{> footer}}`, `{{> cookie-notice}}`, `{{> nav-more-floating}}`.
5. Скрипты подключаются в `<head>` через `{{> head-links}}` (preload + `main.js`, `skill-connectors.js`).
`head` у кейсов: `meta`, `viewport`, `description`, `title`, затем `{{> head-links}}` (preconnect/preload шрифтов и CSS/JS, стили, скрипты с `defer`), при необходимости локальный `<style>` для `.case-article`.
## Пути к CSS и медиа
Файл страницы лежит в **`cases/`**, один уровень ниже корня сайта. В итоговом HTML пути от страницы:
- Стили: `../css/main.css`
- Картинки и видео проекта: `../assets/<папка-проекта>/файл.ext`
В **partials** для ссылок на корень сайта используй **`{{assetPrefix}}`** (при сборке из `cases/*` подставится `../`). В самой странице кейса чаще пишут литерал `../`, как в template.
## Текст и заголовки
Внутри `.case-article`:
- Вводная строка: `<p class="hero__eyebrow">…</p>`
- Заголовок: `<h1 class="case-page__h1">…</h1>`
- Подзаголовок (по желанию): `<p class="case-page__sub">…</p>`
- Обычные абзацы: `<p class="section__prose">…</p>`
- Секции: `<h2>`, при необходимости `<h3>`
Списки — обычные `<ul>` / `<ol>` без обязательных классов (стили задаёт вложенный CSS страницы для `.case-article`).
## Блоки (копировать из template)
### Сетка карточек ссылок
- Обертка: `<div class="case-links-grid">`.
- Одна карточка: `<a class="case-link-card" href="…" target="_blank" rel="noopener noreferrer">`.
- Внутри: `<span class="case-link-card__media">` (превью-картинка или favicon) и `<span class="case-link-card__body">` с `case-link-card__title`, `case-link-card__meta`, `case-link-card__url`.
- Вариант без тяжёлого превью: на `<a>` добавить модификатор **`case-link-card--favicon`**, в `media` — картинка с `https://www.google.com/s2/favicons?domain=…`.
### Изображения (`figure.case-media`)
| Модификатор | Поведение |
|-------------|-----------|
| `case-media--natural` | Натуральный масштаб (визуально ~половина логического размера, см. `initCaseNaturalImagesHalfSize` в `main.js`). **Без** `data-image-viewer`. |
| `case-media--content` | По ширине текстовой колонки; клик — полноэкранный просмотр, если на `<img>` есть **`data-image-viewer`** (обводка и hover как у `.case-video-shell`). |
| `case-media--wide` | Чуть шире колонки; тоже с **`data-image-viewer`** при необходимости просмотра. |
Общие атрибуты для картинок в кейсе: `loading="lazy"`, `decoding="async"`, осмысленный `alt`, при известных размерах — `width` и `height`.
Подпись: `<figcaption>…</figcaption>`. У блока с `.case-video-shell` подпись по центру под видео (как у кадров с `data-image-viewer`).
### Длинный скрин (`data-case-tall-media`)
Для **очень высокого** кадра (полный экран приложения, длинная страница): в свёрнутом виде показывается только верх, внизу превью — плотный градиент («туман»), чтобы было видно, что картинка **не целиком**. Рамка как у кликабельных кадров. Кнопка «Развернуть» / «Свернуть» с шевроном поверх нижней части превью (внутри рамки); после сворачивания — прокрутка к превью (`scrollIntoView` на clip).
**Ширина:** на `figure` с `data-case-tall-media` задай **`data-case-tall-width="content"`** (по ширине текстовой колонки) или **`data-case-tall-width="wide"`** (как у обычной «широкой» картинки: вынос за колонку). Допустим и старый вариант без атрибута: **`class="case-media case-media--wide"`** на `figure` — эквивалентно `wide`. Не смешивай `data-case-tall-width="content"` с классом `case-media--wide` на одном `figure`.
Стили: `.case-media-tall*` и блок ширины в `public/css/main.css`. Логика: [`public/js/modules/cases/tall-media-expand.js`](public/js/modules/cases/tall-media-expand.js) (`initCaseTallMediaExpand` уже вызывается из [`public/js/main.js`](public/js/main.js)).
**Как вставить в кейс**
1. На **`figure`** поставь **`class="case-media"`**, **`data-case-tall-media`**, **`data-case-tall-width="content"`** или **`wide`**.
2. Внутри — разметка ниже: уникальный **`id`** у `.case-media-tall__clip`, тот же id в **`aria-controls`** у кнопки; подписи «Развернуть»/«Свернуть» в span с **`data-case-tall-label-more`** / **`data-case-tall-label-less`** (у «Свернуть» изначально **`hidden`**).
3. На **`img`** оставь **`data-image-viewer`**, если нужен полноэкранный просмотр по клику.
4. Высоту превью (обрезки) задай на clip, например inline: **`style="--case-tall-max: min(60vh, 28rem)"`**. Если не задавать, в CSS по умолчанию `min(70vh, 32rem)`.
5. Файлы клади в **`public/assets/<папка-кейса>/…`**; в HTML страниц кейсов пути **`../assets/<папка>/файл.webp`** (относительно `public/cases/`).
**Сниппет** (подставь `УНИКАЛЬНЫЙ-ID`, путь к WebP, `alt`, при необходимости замени **`wide`** на **`content`**):
```html
<figure class="case-media" data-case-tall-media data-case-tall-width="wide">
<div class="case-media-tall" data-case-tall-root>
<div
class="case-media-tall__clip"
id="УНИКАЛЬНЫЙ-ID"
data-case-tall-clip
style="--case-tall-max: min(70vh, 32rem)"
>
<img
src="../assets/<папка-кейса>/<файл>.webp"
alt="…"
width="1200"
loading="lazy"
decoding="async"
data-image-viewer
/>
<div class="case-media-tall__fog" aria-hidden="true"></div>
</div>
<div class="case-media-tall__actions">
<button
type="button"
class="case-media-tall__btn btn btn--ghost btn--flat"
data-case-tall-toggle
aria-expanded="false"
aria-controls="УНИКАЛЬНЫЙ-ID"
>
<span class="case-media-tall__btn-chev" aria-hidden="true"></span>
<span data-case-tall-label-more>Развернуть</span>
<span data-case-tall-label-less hidden>Свернуть</span>
</button>
</div>
</div>
<figcaption>…</figcaption>
</figure>
```
Живой пример: [`src/pages/cases/template-elements-demo.html`](src/pages/cases/template-elements-demo.html) (секция «Длинный скрин»); второй кейс — [`src/pages/cases/ai-platform-primo-rpa.html`](src/pages/cases/ai-platform-primo-rpa.html).
### Галерея изображений сеткой (`case-media-gallery`)
Сетка из миниатюр с тем же полноэкранным просмотром, что у одиночных картинок (`data-image-viewer` на каждом `<img>`). Стили в `public/css/main.css` (блок `.case-media-gallery`).
**Разметка:**
- Обертка: `figure.case-media.case-media-gallery` (+ опционально `case-media-gallery--wide`).
- Внутри: `div.case-media-gallery__grid` → для каждой ячейки `div.case-media-gallery__item` → `<img … data-image-viewer />`.
- Число колонок на **широком** экране задаётся на `figure`, например: `style="--case-gallery-cols: 3"` (целое ≥ 1). Если атрибут не указан, по умолчанию в CSS задано `3`.
- **Ширина:** без модификатора — как обычный блок в колонке статьи; класс **`case-media-gallery--wide`** — выход за колонку по той же схеме, что у `case-media--wide` (viewport с боковыми отступами). На узких экранах (`max-width: 640px`) wide-галерея схлопывается в ширину колонки.
- **Адаптив сетки:** примерно до `48rem` — не больше двух колонок (`min(2, var(--case-gallery-cols))`); до `28rem` — одна колонка.
Пример целиком см. в [`src/pages/cases/template-elements-demo.html`](src/pages/cases/template-elements-demo.html) (два варианта: контентная ширина и wide).
**Сетка + отложенный `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).
### Последовательная подгрузка (`data-case-sequential-images`)
- Оберни фрагмент статьи с картинками (часто весь `.case-article`): **`data-case-sequential-images`** на контейнере.
- У каждого кадра: **`data-src="../assets/…/файл.webp"`** и временный **`src`** — прозрачный GIF 1×1 (тот же паттерн, что в Lunacy-кейсе).
- Логика: [`public/js/modules/cases/sequential-images.js`](public/js/modules/cases/sequential-images.js), вызов **`initCaseSequentialImages()`** в [`public/js/main.js`](public/js/main.js) **до** `initCaseImageViewer()`.
- Просмотр: [`public/js/modules/cases/image-viewer.js`](public/js/modules/cases/image-viewer.js) слушает клики **делегированием**; не открывает плейсхолдер и не открывает, пока не снят `data-src`.
### Callout
```html
<aside class="case-callout" data-callout>
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">📌</span>
<div class="case-callout__content">
<p>…</p>
</div>
</aside>
```
- **Ширина:** как у wide-медиа — блок шире колонки статьи (`min(100vw − 2×padding, content + 8rem)`), на экранах до 640px — ширина колонки.
- **Иконка:** в `.case-callout__emoji` — цветные emoji (📌 💡 ✨ ✅ ⭐), не текстовые символы (✎ ✓ ◆ →); рендер через emoji-шрифт в `main.css`.
- **Типографика:** текст в `.case-callout__content` чуть крупнее основного абзаца (lead).
- Живой пример: [`src/pages/cases/design-system.html`](src/pages/cases/design-system.html) (секция Callout).
### Видео
- Обертка: `<figure class="case-media">` (часто с inline-отступами как в template) → `<div class="case-video-shell">` → `<video controls preload="metadata" playsinline … data-case-video-player>`.
- Подпись `<figcaption>` под видео — по центру колонки (стили в `main.css`).
- Видео в карточке и в fullscreen — `object-fit: contain` (без обрезки); fullscreen через Plyr (`iosNative: false`), не нативные `controls` на `<video>`.
- Постер: WebP в `poster`.
- Источники: `<source type="video/webm">`, затем `video/mp4`.
Про конвертацию и постеры: `docs/video-conversion.md`.
## Оптимизация изображений
PNG/JPEG → WebP по папке: `docs/image-optimization.md`, скрипт `scripts/optimize-images.mjs`.
## Связь с JS
Подключаемые через `{{> head-links}}` сценарии уже инициализируют:
- последовательную подгрузку `data-src` внутри `[data-case-sequential-images]` (`initCaseSequentialImages`);
- просмотр картинок по клику (`data-image-viewer`, делегирование на документ);
- плеер видео кейса (`data-case-video-player`);
- натуральный размер для `.case-media--natural img`;
- сворачивание длинного скрина (`[data-case-tall-media]`, `initCaseTallMediaExpand`).
Новые интерактивные паттерны — по правилам из `docs/frontend-js-architecture.md` (модуль в `public/js/modules/cases/`, вызов из `main.js`).
## Чеклист нового кейса
1. Скопировать `template-elements-demo.html` → `src/pages/cases/<slug>.html`, заменить `meta`, `title`, контент.
2. Положить медиа в **`public/assets/<slug>/`** (см. `public/assets/README.md` и `docs/image-optimization.md`).
3. Проверить все пути `../assets/…` и `../css/…`.
4. Выполнить `npm run build:pages` и открыть `public/cases/<slug>.html` локально.
5. При необходимости завести текстовый слепок в `texts/cases/<slug>.md` и ссылку на кейс в `src/partials/projects-section.html` после сборки главной.