Передача интерфейса с матрицей возможностей
Финальное состояние каталога включает обычное чтение, диалог, справку, привязку, фильтр и несколько декоративных улучшений. Теперь оформим договор передачи. Результатом станет не ещё один эффект, а описание обязательного поведения, условий поддержки и будущего просмотра. Такой документ помогает развивать проект без случайного удаления базовой ветки.
Папка lesson-14 содержит полный каталог и CAPABILITIES.md. В таблице каждой возможности соответствует базовый путь и признак улучшения. Это не список минимальных версий браузеров: версия быстро устаревает и не отражает все условия среды. Мы указываем конкретный механизм, например функцию showModal или поддержку нужного CSS-свойства. При необходимости продукт может дополнить таблицу реальным перечнем поддерживаемых устройств.
Передача функций вместе с их границами
Начальный документ уже пригоден для чтения. Без JavaScript остаются ссылки, темы, длительности и справка в обычном потоке. При его наличии диалог и popover добавляют способы обращения к дополнительному тексту. Если отдельный новый CSS-механизм отсутствует, он не должен разрушать саму панель. Такое разделение следует сохранить после объединения файлов и минификации.
План клавиатурного маршрута перечисляет Tab, Shift+Tab, Enter, Space и Escape в соответствующих компонентах. Наличие такого плана не означает, что проверки проведены. Столбцы результатов намеренно не заполнены. Позднее наблюдение должно фиксировать среду, действие и фактическое состояние, а не просто общую фразу «всё доступно». Это позволит объяснить несовпадение между ожиданием и исполнением.
Для совместимости нужны несколько независимых условий: отсутствие JavaScript, отсутствие отдельной CSS-возможности, reduced motion, узкий экран, масштабирование и длинный текст. Один современный браузер в исходном размере не представляет весь договор. При этом не требуется бесконечно обещать поддержку любой исторической среды. Команда выбирает реальную целевую аудиторию, а основа документа помогает сохранить доступ к содержанию за её пределами.
Расходы интерфейса нельзя оценить только по числу собственных строк JavaScript. Scroll timeline, снимки переходов и дополнительные слои могут иметь заметную стоимость на определённом устройстве. Для будущего сравнения следует взять один выпуск и одинаковые данные, затем измерить его с эффектами и без них. Мы не приводим выдуманные результаты кадров, памяти и скорости загрузки.
Ресурсный бюджет начинается с простых наблюдений: размер HTML, CSS и JavaScript, число запросов, зависимость первого содержимого от сценария. У нашего учебного проекта нет внешних пакетов и шрифтов, но это ещё не абсолютная гарантия быстрой работы. Большое изображение или слишком тяжёлые эффекты позднее способны изменить картину. Бюджет должен проверяться по настоящему выпуску.
Внешний вид улучшений может отличаться между движками, пока основной маршрут остаётся рабочим. Поэтому критерий принятия формулируется через действие: открыть материал, прочитать справку, выбрать тему, вернуться к каталогу. Снимок состояния полезен для оценки оформления, но не заменяет проверку ссылок, фокуса и выхода из дополнительных панелей.
Перед последующей публикацией потребуется разрешённый просмотр и отдельная сборка настоящего сайта. Сейчас подготовлены только локальные черновики и учебные исходники; тесты, браузерное исполнение примеров и деплой не проводились. Передавая архив, сохраните эту границу. Читатель получает последовательный проект и план наблюдений, а не обещание, что каждое новое API уже подтверждено во всех производственных условиях.
Полный файл index.html текущего этапа:
<!doctype html>
<html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Учебный каталог</title><link rel="stylesheet" href="styles.css"><script type="module" src="app.js"></script></head>
<body><a href="#main">Перейти к материалам</a><header><a href="index.html">Учебный каталог</a></header><main id="main"><h1>Учебные материалы</h1><p>Каталог «Полка»: статьи о контенте и интерфейсах.</p><div class="reading-progress" aria-hidden="true"></div><button type="button" id="open-lesson" hidden>О проекте урока</button>
<dialog id="lesson-dialog" aria-labelledby="dialog-title"><h2 id="dialog-title">Учебная библиотека</h2><p>Три урока с постоянными ссылками. Карточка дополняет каталог.</p><form method="dialog"><button value="close" autofocus>Закрыть</button></form></dialog><button type="button" id="help-button" popovertarget="help" hidden>Как читать каталог</button>
<aside id="help"><h2>Чтение каталога</h2><p>Откройте название урока. Справка не закрывает доступ к другим ссылкам.</p></aside><nav id="filters" aria-label="Фильтр уроков" hidden><button type="button" data-filter="all" aria-pressed="true">Все</button> <button type="button" data-filter="content" aria-pressed="false">Контент</button> <button type="button" data-filter="interface" aria-pressed="false">Интерфейс</button></nav><p id="filter-status" role="status"></p><fieldset id="density-control" hidden><legend>Плотность карточек</legend><label><input type="checkbox" id="compact"> Компактное описание</label></fieldset><ul class="cards"><li class="card" data-topic="content" data-minutes="20"><h2><a href="article.html#markdown">Markdown и структура урока</a></h2><p class="card-description">Разделяем содержание и оформление библиотеки.</p><p class="card-meta">Контент · 20 минут</p></li>
<li class="card" data-topic="interface" data-minutes="35"><h2><a href="article.html#navigation">Навигация учебного каталога</a></h2><p class="card-description">Сохраняем обычные ссылки и последовательность чтения.</p><p class="card-meta">Интерфейс · 35 минут</p></li>
<li class="card" data-topic="content" data-minutes="25"><h2><a href="article.html#urls">Постоянные адреса материалов</a></h2><p class="card-description">Меняем каталог, сохраняя адрес каждой статьи.</p><p class="card-meta">Контент · 25 минут</p></li></ul><p class="notice">Каждый урок доступен по обычной ссылке. Фильтры и эффекты дополняют чтение.</p></main><footer>Полка · учебный проект</footer></body></html>
Полный файл app.js текущего этапа:
document.documentElement.dataset.enhanced = 'true';
const lessonDialog = document.querySelector('#lesson-dialog');
const openLesson = document.querySelector('#open-lesson');
if (typeof lessonDialog.showModal === 'function') {
openLesson.hidden = false;
openLesson.addEventListener('click', () => {
if (!lessonDialog.open) lessonDialog.showModal();
});
lessonDialog.addEventListener('close', () => openLesson.focus());
}
if ('popover' in HTMLElement.prototype) {
document.querySelector('#help').setAttribute('popover', 'auto');
document.querySelector('#help-button').hidden = false;
}
const filters = document.querySelector('#filters');
filters.hidden = false;
const applyFilter = (topic) => {
let count = 0;
document.querySelectorAll('.card').forEach((card) => {
card.hidden = topic !== 'all' && card.dataset.topic !== topic;
if (!card.hidden) count += 1;
});
filters.querySelectorAll('button').forEach((button) => button.setAttribute('aria-pressed', String(button.dataset.filter === topic)));
document.querySelector('#filter-status').textContent = `Показано уроков: ${count}`;
};
let transitionBusy = false;
filters.addEventListener('click', async (event) => {
const button = event.target.closest('button[data-filter]');
if (!button || transitionBusy) return;
const topic = button.dataset.filter;
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!document.startViewTransition || reduceMotion) { applyFilter(topic); return; }
transitionBusy = true;
try {
const transition = document.startViewTransition(() => applyFilter(topic));
await transition.finished;
} catch { applyFilter(topic); }
finally { transitionBusy = false; }
});
document.querySelector('#density-control').hidden = false;
document.querySelector('#compact').addEventListener('change', (event) => {
document.querySelector('.cards').dataset.density = event.target.checked ? 'compact' : 'comfortable';
});
Полный файл styles.css текущего этапа:
* { box-sizing: border-box; }
:root { color-scheme: light; --accent: #6d28d9; --paper: #fff; --ink: #20202a; }
body { margin: 0; color: var(--ink); background: #f7f6fb; font: 1rem/1.6 system-ui, sans-serif; }
header, main, footer { max-width: 72rem; margin-inline: auto; padding: 1.5rem; }
a { color: #5923b5; text-underline-offset: .18em; }
button { font: inherit; padding: .5rem .9rem; cursor: pointer; }
:focus-visible { outline: 3px solid #17693d; outline-offset: 4px; }
[hidden] { display: none !important; }
.cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); list-style: none; padding: 0; }
.card, aside { padding: 1.25rem; border: 1px solid #ddd8e8; border-radius: .7rem; background: white; }
.card h2 { margin-block-start: 0; }
.card-meta { color: #51495f; }
.notice { border-inline-start: .25rem solid var(--accent); padding-inline-start: 1rem; }
dialog { width: min(36rem, calc(100vw - 2rem)); border: 1px solid #b9afca; padding: 1.5rem; border-radius: .8rem; }
dialog::backdrop { background: rgb(20 15 30 / .5); }
@supports (anchor-name: --catalog-help) and (position-area: bottom) {
#help-button { anchor-name: --catalog-help; }
#help[popover] { position: fixed; position-anchor: --catalog-help; position-area: bottom; inset: auto; margin: .5rem; width: min(25rem, calc(100vw - 2rem)); }
}
@supports (position-try-fallbacks: flip-block) {
#help[popover] { position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; max-block-size: calc(100dvh - 2rem); overflow: auto; }
}
.cards { view-transition-name: catalog-list; }
::view-transition-old(catalog-list), ::view-transition-new(catalog-list) { animation-duration: 180ms; }
@view-transition { navigation: auto; }
header { view-transition-name: catalog-header; }
.reading-progress { display: none; }
@supports (animation-timeline: scroll()) {
.reading-progress { display: block; position: fixed; inset: 0 0 auto; height: .2rem; background: #17693d; transform-origin: left; animation: reading linear; animation-timeline: scroll(root block); z-index: 1; }
@keyframes reading { from { transform: scaleX(0); } to { transform: scaleX(1); } }
}
@supports (animation-timeline: view()) {
.card { animation: reveal linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
@keyframes reveal { from { opacity: .65; transform: translateY(.4rem); } to { opacity: 1; transform: none; } }
}
@media (prefers-reduced-motion: reduce) {
.card, .reading-progress { animation: none; transform: none; opacity: 1; }
.reading-progress { display: none; }
::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}
.cards { --density: comfortable; container-name: catalog; }
.cards[data-density="compact"] { --density: compact; }
/* Attribute selector is the conservative fallback. */
.cards[data-density="compact"] .card-description { font-size: .9rem; }
@container catalog style(--density: compact) {
.card-description { font-size: .9rem; }
.card h2 { margin-block-end: .4rem; }
}
.card::after { content: ''; display: block; height: .2rem; width: 30%; background: #76638e; margin-block-start: .7rem; }
@supports (width: calc(attr(data-minutes type(<number>), 20) * 1%)) {
.card::after { width: clamp(0%, calc(attr(data-minutes type(<number>), 20) * 1%), 100%); }
}
header { background: #6d28d9; color: white; }
header a { color: inherit; }
@supports (color: contrast-color(#6d28d9)) {
header { color: contrast-color(#6d28d9); }
}
Разбор изменения каталога
Матрица возможностей полезна и при удалении эффекта. Если команда решит, что view timeline слишком отвлекает, она может убрать только соответствующее правило, не затрагивая данные карточек. Такой рефакторинг должен сохранять ссылки, сообщения и порядок. Набор новых API не является обязательной коллекцией приёмов, которую нужно удерживать ради современности страницы.
Изменение ресурса тоже требует точного описания. Подключение внешнего шрифта добавляет сеть и ожидание, крупный SVG меняет размер файла, а клиентская библиотека может добавить собственный цикл загрузки. Сравнивайте один и тот же выпуск до и после конкретного изменения. Формулировка «без фреймворка» не заменяет измерение, если в страницу позже включили несколько тяжёлых независимых сценариев.
Для самостоятельной передачи выберите один компонент и объясните другому разработчику его обязательное действие, базовую ветку и признак улучшения. Затем попросите назвать состояние, которое ещё не наблюдалось. Если из документа нельзя отличить план от результата, договор следует уточнить до публикации. Такая работа завершает учебный каталог: развитие начинается с понятных границ и содержания, а эффект становится управляемым дополнением.
В договор передачи стоит включить и способ удаления временных учебных вариантов. Если автор менял число карточек для наблюдения шкалы, такой набор не должен случайно стать окончательным содержанием. Финальная папка содержит исходные три записи, чтобы поведение фильтров оставалось объяснимым. Реальный каталог может быть больше, но его данные следует передавать отдельно от экспериментальных условий.
Признак not_run означает именно отсутствие исполнения примера. Он не сообщает, что пример заведомо неправильный, и не подтверждает, что проблем нет. Следующий этап работы может включить разрешённое выполнение и предметный журнал результатов. Пока в передаче достаточно назвать среду, ожидаемый эффект и известные границы, не заполняя фактами ещё не наблюдавшиеся состояния.
Сохранение внешнего вида сайта также не требует бесконечного накопления эффектов. Фиолетовый акцент, читаемая типографика и понятные ссылки могут оставаться устойчивой основой. Новые возможности браузера стоит вводить там, где они улучшают конкретный маршрут. Матрица помогает объяснить это решение и не превращать интерфейс в демонстрацию всех изученных API одновременно.
Наконец, публикация самого учебного курса не запускает его архив. Статическая страница может корректно показывать код, а выполнение этого кода остаётся отдельной задачей. В ProfessorWeb это различие особенно важно при росте числа уроков. Передавая новый материал, указывайте фактические действия подготовки, чтобы читатель понимал, какие результаты ожидаются и какие действительно подтверждены.
Условия для самостоятельного наблюдения
Это план будущего разбора, а не журнал выполненных проверок. Рассмотрите одинаковые исходные данные и сопоставьте действие с ожидаемым состоянием.
| Условие | Ожидаемое состояние и его граница |
|---|---|
| Обязательный путь | Открытие статьи и чтение текста не должны зависеть от декоративных эффектов. |
| Отдельное улучшение отсутствует | Зафиксирован конкретный базовый вариант, а не требование обновить браузер. |
| Проверка ещё не проведена | План остаётся планом; в документе не ставится отметка о полученном результате. |
| Появился внешний пакет | Пересматриваются ресурсы, условия загрузки и возможная зависимость основного содержимого. |
| Изменена семантика панели | План фокуса и выхода нужно обновить вместе с новым назначением компонента. |
| Готовится настоящий выпуск | Статические статьи, учебное исполнение и публикация рассматриваются отдельными действиями. |
Оглавление серии, курсы ProfessorWeb и полный архив исходников. Для этого урока выберите lesson-14.
Первичные источники: Interop 2026, View Transition API.
Редакционный статус: локальный черновик. Установка, учебные программы, браузерные сценарии, тесты и публикация не выполнялись. Описанные эффекты ожидаемые.