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

21 KiB
Raw Permalink Blame History

Страница кейса: каркас и блоки контента

Инструкция завязана на эталон 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 → 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 (initCaseTallMediaExpand уже вызывается из 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):

<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/ai-platform-primo-rpa.html.

Сетка из миниатюр с тем же полноэкранным просмотром, что у одиночных картинок (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 (два варианта: контентная ширина и wide).

Сетка + отложенный src: в ячейках можно задать реальный URL в data-src и плейсхолдер в src (крошечный data:image/gif;base64,…), чтобы initCaseSequentialImages подставлял картинки по порядку в DOM (в т.ч. строка за строкой в галерее). Пока у img есть data-src, просмотр по клику не открывается. Пример: src/pages/cases/lunacy.html.

Слайдер изображений с точками (case-media-slider)

Одна картинка во весь блок, навигация стрелками «пред/след» и пагинация точками (активная — акцентным цветом). Переиспользует маски стрелок shevron-left/right.svg и токены темы. Логика: public/js/modules/cases/media-slider.js (initCaseMediaSlider() вызывается из public/js/main.js).

Возможности:

  • стрелки data-slider-prev / data-slider-next — переключают соседний слайд, блокируются (disabled) на краях;
  • точки data-slider-dot (в data-slider-dots) — переход на конкретный слайд, активная получает is-active + aria-selected;
  • клавиатура: ←/→ внутри блока, Home/End на точках;
  • тач/мышь-свайп (только горизонтальный);
  • ленивая подгрузка соседних кадров: у <img> можно задать data-src (временный src — прозрачный GIF), скрипт подставит при подходе, как у галереи; пока есть data-src, клик по viewer не открывается;
  • клик по активному кадру открывает общий viewer кейса (data-image-viewer).

Ширина слайдера подстраивается под ширину картинок (высота — автоматически по контенту). Модификаторы как у статичных .case-media:

  • базовый (без модификатора) / .case-media--content — ширина = --slider-natural-w (по умолчанию 100% колонки контента). Задайте на <figure> inline-стиль --slider-natural-w: <ширина картинки>px, чтобы слайдер точно встал в размер кадров и не превышал его; max-width ограничен вьюпортом, поэтому на узких экранах сжимается;
  • .case-media--wide — слайдер выходит за колонку контента (как одиночные wide-картинки), центрируется по вьюпорту;
  • .case-media--natural — как базовый, но max-width по умолчанию 600px (переопределите --slider-natural-w).

Кнопки «пред/след» и точки находятся внутри .case-media-slider__viewport, поэтому не участвуют в расчёте ширины блока.

Разметка (число data-slider-dot должно равняться числу .case-media-slider__slide; aria-label точек — «Перейти к изображению N»):

<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="../assets/<папка>/<файл-1>.webp" alt="…" loading="lazy" decoding="async" data-image-viewer />
      </div>
      <div class="case-media-slider__slide">
        <img src="../assets/<папка>/<файл-2>.webp" alt="…" 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>
  </div>
</figure>

Живой пример: src/pages/cases/template-elements-demo.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, вызов initCaseSequentialImages() в public/js/main.js до initCaseImageViewer().
  • Просмотр: public/js/modules/cases/image-viewer.js слушает клики делегированием; не открывает плейсхолдер и не открывает, пока не снят data-src.

Callout

<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 (секция 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, делегирование на документ); при открытии из галереи (.case-media-gallery) или слайдера (.case-media-slider) в нижней панели просмотрщика появляются кнопки-пилюли «пред/след» и точки-пагинация — переключают только картинки этого блока (клавиши ←/→, свайп по горизонтали, клик по точке); у одиночной картинки кнопки и точки скрыты;
  • плеер видео кейса (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 после сборки главной.