Web coding guide: build a shop page with code
/docs/webcodingcurrentМашинный перевод с английского
Для одного AI Агент или разработчик с токеном: выложить HTML page (посадочная площадка, промо-заказ, викторина) и publish В этом визире shop — нет admin панель, API Все нижеприведенные формы запросов были проверены live звонки. спутница document — Структура структуры page ответы: vizen-market/app/docs/html-редактор/06-структура-страниц.md.Заполнить каталог, а не выложить page? Вам нужен гид для братьев и сестер: `GET /docs/catalog-import` — products, categories, attributes, variants и Группировки, фотографии, публикации и откат. В нем также есть таблица решений "variants внутри а product ↔ отдельное связанное products" и field подводные камни (currency, main и дополнительные categories, Тройные флаги, пагинация).
Решите свой path before читать.
| У тебя есть | Читать | Скип |
|---|---|---|
| готовая папка или site — Посадка, генератор export, React/Vite строить, Figma/Webflow/Tilda export | §2а "Готовая папка → page", §2b "Раскладка = папка", площадь GET /docs/transfer, мастерство vizen-transfer | §5, §7, §8a, §11.2 — они cover тот boxed level-1 html block и не распространяется на а level-3 папка |
| а level-1 html вставить внутри page построенный с платформы widgets | §2–§8a, §11.2, §17 | — |
| Та же самая накладка на MANY pages с разными data | §2с "Компонент с параметрами", площадь GET /docs/components | — |
| forms и leads | /docs/forms | — |
Суть в одном абзаце (level-1 html block). Ты пишешь plain HTML+CSS, Поставьте его как file в склад, завернуть в "HTML document", вставить document как section входить в block и скрепить block в page. Твой HTML земли в page's SERVER html (SEO работы) и рендеринг в изоляции: ваши стили не будут разрыв site, и site's Стили не сломают тебя. level 1 Сервер полоскает скрипты — Не баг, это контракт (нуждаюсь) JS — level 2, см. § 8; Готовый page Сценарий с меткой — level 3, §2a: реально DOM, Сценарий работает, нет изоляции).
0. База, токен, префиксы
- База:
https://api.vizen.shop. - Token (PAT):
Authorization: Bearer vz_pat_…по всем запросам, в том числе public конечные точки (решение витрины, чтение а) page через slug): В противном случае вы не сможете получить 403. Анонимный абонент получает 200. Изданный в shop admin →/api-access, показанный один раз. - Первый звонок — это key паспорт:
GET /v1/account/token. Возвращает обратно contour (writes_to_live— Если редактирование идет прямо к live site), тот shop с его currency и витрина address,capabilities(Что? key Действительно можно сделать иwarnings— Передайте это в owner в full. Займись витриной магазина address оттуда (store.storefront_url); Не изобретайте его. - требуемый scopes:
catalog:read+catalog:write+storage:write. Строить LEAD FORM (request/consultation/subscription) — Вам также нужноforms:read/forms:write, иleads:readЧитать далее leads сами себя; Весь сценарий находится в отдельной области. /docs/forms. - ⚠️ `catalog:read` Не решает, "войдете ли вы в него", а WHAT Ты видишь. Без этого key работать public конечные точки, но возвращает именно то, что браузер без key получать: опубликованный items Только, без черновиков или hidden categories, always PROD — а dev key's draft И не покажет. Такого не будет. key Получить own неопубликованный page как
404 …_NOT_FOUND: Не отказ в разрешении, а public Проекция, проверь свою работу. BEFORE Публикация только сcatalog:read. Если вы пишете каталог (catalog:write), братьcatalog:readИ ты, и я cannot Перечитайте то, что вы created. - ⚠️ Приставки не являются однородными: каталог (
/products,/categories,/content-blocks,/html-documents) — без/v1; хранение и организация (/v1/storages/files,/v1/storefronts/resolve) — с/v1. - Полный машиночитаемый schema:
GET /compiled.swagger.json.
Ключевой контроль: GET /v1/account/token → scopes, company_id, Это срок годности. Только надежный способ: public Конечные точки отвечают на 200 даже без catalog:read (с помощью public Проекция, так что "200 на /categories" Доказать ничего не могу. 401 — тот key неправильно или expired; На частных конечных точках отсутствует scope шоу явно: 403 PAT_SCOPE_MISSING (required_scope: …).
⚠️ id Типы ответов различаются — не удивляйтесь
| сущность | Что? result.id возврат |
|---|---|
html document (POST /html-documents) | струна: "12" |
content block (POST /content-blocks) | струна: "139" |
category/page (POST /categories) | number: 45 |
product (GET /products/{id}) | number |
The reason является исторической (int64 Сериализируется для JSON В запросах вы можете отправить number: тот /{id} path Победа над body. Когда сравнивают ids, сгибать в веревку.
1.Читать далее shop first (в противном случае page будет составлен)
| Что тебе нужно | Просьба |
|---|---|
| Магазин, currency, Логотип, язык, контакты | GET /v1/storefronts/resolve?slug={shop} (public) |
| Дерево categories и pages | GET /categories?company_id={id} — ONE смешанный список: type:"product" (каталог category), type:"page" (page), type:"news" (news section); filter через type |
| Продукты (цены, слизни), images) | GET /products?filter.company_id={id}&page.number=1&page.limit=24 |
| Articles/news | GET /articles?company_id={id}&page.number=1&page.limit=10 (⚠️ page.number это required, ≥ 1) |
| Твой html документы | GET /html-documents |
| Что уже есть на page | GET /categories/by-slug/{slug}?company_id={id} |
⚠️ Фильтры для продуктов берут `filter.` prefix, И это имеет значение. параметр без prefix это SWALLOWED SILENTLY: GET /products?category_id=168 200 и возвращает обратно тот shop's ENTIRE каталог, а не section. Нет никакого error — Просто неправильно data, И неправильная page Он построен из него.
| Filter | Что он делает |
|---|---|
filter.category_id | products из одного section (витрина магазина's main filter) |
filter.ids | несколько products через id сразу |
filter.query | Поиск по name |
filter.is_published | опубликовано только |
filter.min_price / filter.max_price | price range |
page.number / page.limit | pages; sort.field / sort.order — order |
Полный список — GET /compiled.swagger.json. Генерал rule: Проверьте, что Ответ изменился after Добавить filter. Тот же самый total before и after средство Параметр не был понят.
Link то REAL слизняки и products: а product URL Это slug chain его main category + тот product slug (/kombo-nabory/kombo); а page URL Это slug chain из тот page category (/m1-smoke). ⚠️ А. product's seo.slug может быть EMPTY — Тогда витрина магазина обращается к нему id: /{category-slug}/{id}. Правило: seo.slug || String(id), Или ты получишь Неработающие ссылки.
⚠️ Конверты ответов различаются — look тщательно
| Конечная точка | Форма ответа |
|---|---|
GET /categories | {"result": [ … ]} — массив непосредственно в result |
GET /products | {"result": {"items": […], "total": N, "currentPage": N}} |
GET /articles | {"items": […], "total": N, "current_page": N} — NO result |
GET /products/{id}, /categories/by-slug/… | {"result": {…}} + братья и сестры (content_blocks, categories, …) |
Один more Несоответствие: в product LIST тот slug плоский (item.slug), В то время как в DETAIL Он находится внутри seo (result.seo.slug). В обращении rule То же самое: The slug или, если Это empty, тот id.
Вся эта page JSON в одном запросе
Чтобы создать уникальный product/category/news page, Читать полностью — Ответ несет как сущность, так и ее blocks:
GET /categories/by-slug/{slug}?company_id={id} # page or category
GET /products/by-slug/{slug}?company_id={id} # product (+categories, +reviews, +variants)
GET /articles/by-slug/{slug}?company_id={id} # article (+related)Что внутри и как его использовать:
| Ответ key | Что дает поколение |
|---|---|
result | name, description, price, slug, SEO fields, images (preview + galleries[] — прикрепленный galleries с items; а variant's set Ссылка на галерею через variants_info.variants[].gallery_id) |
content_blocks[] | ALL page blocks: sections[].payload.kind — block kind, props — его settings. Бери texts, images и ссылки оттуда для повторного использования в вашей разметке |
content_blocks[] с name:"__page:top" | тот zone's таблица содержания: refs (block order) и page (Флаги — есть ли header/footer/breadcrumbs есть hidden) |
categories (на product) | тот chain для breadcrumbs и ссылки |
attribute_sections, reviews_summary, variants_info, group_info | attributes, рейтинг, variants — material для card |
Правило: читать page first, затем генерировать. Так что, вот так, block Ссылки на shop's реальный products и images, не выдуманные и не Повторите то, что уже есть на page.
Что взять из a product для card на твоей page (live Пример ответа):
"name": "Кровать двухъярусная лофт Волстрит 120x90",
"price": { "price": 61192, "old_price": 71991, "currency": "RUB",
"promotion_name": "Кроватная распродажа −15%" }, // WHOLE roubles
"seo": { "slug": "krovat_dvukhyarusnaya_loft_volstrit_120x90_nebula" },
"category_id": 4,
"preview": { "id": "76a176b2-…", "url": "/files/sunset/view/content/76/a1/76a176b2-….webp" },
"gallery_ids": ["31"], // references to shop galleries
"galleries": [ // contents; may be empty
{ "id": "31", "name": "Синие", "source": "images", "canvas_id": "0",
"items": [ { "item_key": "8231…", "kind": "image", // kind: image|video|audio|html
"file": { "id": "…", "url": "/files/…" }, "caption": "" } ] }
]Печать price как и все (целое) currency единицы, а не копейки; возьмите currency из price.currency или storefront.currency. Возьми. product image из preview.url или из galleries[].items[].file.url (items с kind: "image") — Это повторное использование (§3). shop resource (/galleries); Один и тот же может сидеть на нескольких products; LISTS (GET /products) Не носить его, только preview.
Перечислите цены и детали-card Цены на комбо приходят с одного общего сервера-side Расчет состава: старое несоответствие "список с промо-акцией, card Без него "это" fixed. Используйте готовые цены; не звоните GET /products/{id} Для каждой плитки и не
- рекомпьютерные комбо на клиенте. live feed и состав —
GET /docs/own-markup §2, GET /docs/catalogue §4.1.
Замена а section's standard Сетка с вашим own разметка через PAT: Единственный рецепт с а snapshot и откатом является GET /docs/components §4.8 Язык шаблонов и level 1 limits — GET /docs/own-markup §4; selected image fields и sizes — §6.
2.Создать HTML document
POST /html-documents
{ "item": { "name": "Промо-лендинг весна", "level": 3 } }
# → { "result": { "id": 10, … } }level: 3 — твой own разметка в page's реальный DOM (скрипты исполняют, position: fixed работа, content индексируется; owner/admin role, сервер Флаг висит на prod) — тот level любой готовой page или папка (§2а; transfer.mjs создает level-3 document само собой); 1 — ан html вставить внутрь а page построенный из widgets (Санитарная, Тень DOM, Нет сценариев; §5–§8a); 2 — а script в iframe sandbox без SEO (§8). Пустота = 1 — вставка default, не передача default. Если сервер откажется level 3, скажи owner; Не отступать назад к level 1: Это совсем другое. model.
2а. Готовая папка → site page (HTML проект, level 3)
THE SINGLE BUILD RULE (owner, 2026-09-03) — читать first
The page Он построен как обычный site в папке и переносится с Одна команда; Нет, не реконструкция, нет manual file Загрузка, нет path переписывать.
- Виджет = папка.
index.htmlВ корневой +styles.css+app.js+assets/…Все пути относительны, все пути относительны. — из корня папки. Над тот site тот files Они служат для widget корень/_html/{doc}/{release}/…с тем же самым гнездо; site page не принимает участия в обращении., предварительный просмотр, README) входит в.vizenignore. - `index.html` это plain разметка: разметка +
<link>/<style>/<script>, без<!doctype>/<html>/<head>/<body>. У меня есть целое page — Без проблем, платформа снимает wrapper Сама по себе публикация. - А. script который сам строит дороги (
img.src = …,fetch(…)) брать тот widget Корень — каждый проект имеет свой own:const base = document.currentScript?.closest('.vz-body')?.dataset.vzBase ?? window.VZ_ASSET_BASE ?? ''(рядный script);img.src = base + 'assets/x.jpg'. Локально, из папки,baseэто empty; на site Это widget Корень. external илиdeferscript больше не имеетdocument.currentScript— использоватьdocument.querySelector('.vz-body-<id>')?.dataset.vzBaseилиwindow.VZ_ASSETS[<id>], где<id>Это document id от API ответ. - локально Открыть через
npx serve .(неfile://). Это показывает Скриншоты из игры - Without the site header и footer, без своего каскада и безvz-Замены; это exact preview Публикация с a dev key (<slug>--dev). Что меняется в site — Подраздел "Каскад и время выполнения на береговой линии" ниже. - передача:
node backend-3D/tools/html-transfer/transfer.mjs ./folder --page <slug>(Узел 18+, key вVIZEN_TOKEN; стоять —--api http://127.0.0.1:3970) The first Бег создает level-3 document, Освобождение и a block на page; каждый Следующий Run Uploads Изменились только files и обновляемую page сразу же. ZIP в одном запросеPOST /html-documents/{id}/releases/zip?publish=1— Только выпуск: создай document заранее (POST /html-documents {level:3}), устанавливать block на page «Сам себе» («То же самое») path Ручной перевод" ниже). Ошибки валидатора — исправить и запустить снова; откат —--rollback. - Демо template = Та же папка. new page от template — копировать папку и
--page another-slug; а version — a release; a edit (отправить) — file + Перезапустить команду. - Проверить after передача (обязательное: открыть
urlИз ответа в браузере (безголовый считается браузером), возьмите рабочий стол + mobile скриншоты, look В консоли: нет JS Ошибки, нет 404 files; compare с местный version. Несоответствие — исправьте папку и повторите шаг 5. - Не надо.: использовать absolute URL вместо относительных, вырезать page вводить blocks, использовать iframe/shadow "для изоляции", загрузить files Один за другим через хранение API, поставьте второй
.htmlpage В освобождение.
Рабочий процесс чата: читайте key (/v1/account/token) → построенный page локально в папке → одна команда передачи → открылась address, Скриншот, консоль → докладывать в owner с а link.
Если page уже выложен (посадочный, нейронная сеть) export) — Не переписывайте пути и не перерезайте HTML вводить blocks: папка переносится как. Платформа rule: "<head> Удалены, все CSS/JS «Bings Stay» — папка входит в page's реальный DOM, Без тени DOM Без iframe.
Граница: один widget = один html page (index.html). дополнительный .html files В выпуске — предупреждение page.extra; тот gateway не обслуживает их. multi-page site это несколько widgets на нескольких site pages.
принимать standard: Есть один wrapper — тот site's
The site уже имеет page с а header и footer; Ваша наценка вписывается в Без iframe и без тени DOM, Сценарии работают, images открытый. Таким образом, нормальный вход plain разметка (формат 1):
<link rel="stylesheet" href="styles.css">
<style>.hero{…}</style>
<section class="hero"><img src="assets/hero.webp" alt="…"></section>
<script src="app.js" defer></script>— без <!doctype>, <html>, <head>, <body>. Отправить index.html Именно так, если вы строите page Для Vizen с нуля.
Формат 1 не "второй" class": В публикации он также получает wrapper <div class="vz-body vz-body-{doc}">…</div> — То же самое, что и в целом page. Там, где твой body/html/:root из CSS Земля (см. ниже) и несёт widget's file root. Редакция не добавляет второй wrapper.
Целое page (Формат 2: имеет <html><head><body>) Также принимается — на публикацию платформа преобразует его в сам формат 1:
- из
<head>только<link>,<style>,<script>,<noscript>оставаться →<div class="vz-head vz-head-{doc}" hidden>…</div>; <title>,<meta>,<base>выпадение;titleиdescriptionВойди в себя site page's SEO (Если он fields есть empty);<body class="dark" data-x="…">→<div class="vz-body vz-body-{doc} dark" data-x="…">…</div>;- Ваши селекторы
body { … },html { … },:root { … }(в<style>и в.cssfiles) Переписывается для.vz-body-{doc}— Стиль приземляется на ваш wrapper, Два проекта на одном page не перекрывать.
Только чистый result хранится: конвертер записывает его вместо выпуска's index.html; оригинал не сохраняется; document's source_file_id указывает на это file, который можно увидеть и отредактировать в admin ("Страница code"). {doc} Это document id. Редактировать в admin = а new Выпуск (Страница) code" окно Экономия через POST …/releases {base:"active"} → PUT → publish); Замена выпуска file напрямую через ReplaceFile Запрещено — FILE_IN_RELEASE. Проект level это fixed (3); "Блок files" Панель является hidden — files live в освобождение.
Правила папки (проверяется валидатором на публикацию))
index.htmlВ корне это - widget's только page; другой.htmlfiles в Освобождение - предупреждениеpage.extra(тот gateway не служит HTML).- Все пути относительны, от корня папки и внутри него..
../somethingпрошлое корень — errorref.outside; а file Которого не существует —ref.missing. Струна file пути в.jsбезwindow.VZ_ASSET_BASE— предупреждениеjs.assets(на site такой path Решения против page URL, Не то widget Если вы читаете корень черезdataset.vzBase, keepwindow.VZ_ASSET_BASEкак fallback И нет никакого предостережения.. <head>не требуется: на публикации остаются только привязки (link/style/script/noscript),title/meta descriptionВойди в себя site page's SEO, остальное (<base>,og:*,http-equiv,manifest) Упал (предупреждение)head.stripped).- Внешние ресурсы (шрифты Google), CDN) —
https://только;http://— errorref.http. Лучше вставить шрифты в папку. - Готовься images при необходимости size себя:
/w/…изменение размера не применяется проектировать files (Они подаются байт за байт. Рекомендовано ≤ 400 KB иwidth/heightна<img>— иначе warningsimg.large/img.nosize. - Пределы: ≤ 500 files, HTML file ≤ 2 MB, любой file ≤ 50 MB.
- Не допускается: исполняемые сервера (
php/py/sh/exe…) —file.forbidden;navigator.serviceWorker.register— errorsw.register(работник в Корень перехватит весь shop, включая payment). - Ссылки на The Links site — ан absolute path (
/catalog/x) илиvz:page/<id>; forms —href="form:<id>"(§13). А.<form>Без обработчика — предупреждениеform.nohandler. - Не кладите в папку: макеты,
previews*/,originals/,README,qa.mjs— использовать.vizenignoreВ корневой (.gitignoreсинтаксис); hidden files (.DS_Store,.git/) есть skipped молча. - Один проект = один site page. Открытый локально через HTTP сервер (
npx serve .), неfile://.
Пути на пути site: widget = папка, одна mode
А. widget это block внутри site page (header/footer/other blocks вокруг него); Есть один wrapper — тот site's; Нет других режимов. Все выпуски files Они служат для widget корень /_html/{doc}/{release}/… с тем же гнездованием, что и в папке:
- пути в разметке и CSS (
src,href,srcset,url(…),@import) Переписывается в этот корень на output — ничего не менять в папке; - Пути, построенные на script Пройти через widget корень.
/contentобеспечивает В двух направлениях: атрибутdata-vz-base="/_html/{doc}/{rid}/"на wrapper.vz-body-{doc}и, как first узел,<script>window.VZ_ASSET_BASE="…";(window.VZ_ASSETS=window.VZ_ASSETS||{})["{doc}"]="…"</script>. Рекомендуемый способ – прочитать его твой own wrapper:
``js // inline script inside the widget const base = document.currentScript?.closest('.vz-body')?.dataset.vzBase ?? window.VZ_ASSET_BASE ?? '' // external or defer script (currentScript is already null), 51 is the document id const base51 = document.querySelector('.vz-body-51')?.dataset.vzBase ?? window.VZ_ASSETS?.[51] ?? '' ``
window.VZ_ASSET_BASE "Когда есть один" widget на page": два widgets переписывать друг друга's глобального, и first один's отложенный script читается второй's Корень. Без корня а path Решения против page URL и дает 404.
А. level-3 block рендеринг Без платформенного бокса (нет .vz-box/padding/ radius); Нужна коробка — props.html.boxed: true. При переводе в a page, старый html blocks (В том числе и более ранние iframe-переводы) удаляются — есть один проект за page. А. dev key публикуется в The draft: тот page Видно, что в <slug>--dev.<zone> под owner's сеанс (звонки в магазине) /content с contour заголовок; а prod key публикуется в The live site.
Каскад Storefront и время выполнения — что меняется на site по сравнению с npx serve
А. level-3 widget Это page's реальный DOM; Нет никакого style изолированность (по design: тот author Владеет page). Следовательно,:
- Каскад витрины магазина применяется к вашей разметке. Витрина загрузит предполетный рейс Tailwind (
@layer base):*{margin:0;padding:0;border:0 solid},h1…h6{font-size:inherit;font-weight:inherit},a{color:inherit;text-decoration:inherit},ol,ul{list-style:none},img,svg,video{display:block;max-width:100%;height:auto},button,input{font:inherit;background:transparent;border-radius:0}. Твой CSS Внешние слои выигрывают при равной специфичности — все set явно выживает; все, что осталось браузеру по умолчанию (heading sizes, Список маркеров, link подчеркивать, button look) Перезагрузите. own base.css в рамках проекта's корень class. The page шрифт являетсяui-sans-serif, system-ui16x; вашbody{font-family}только на землю.vz-body-{doc}. - `body`, `html`, `:root` в
<style>и в освобождении's.cssПереписывается для.vz-body-{doc}(соединениеbody.darkне является).body{overflow:hidden}из a модальный script Не запирать page Скачать - Заблокируйте свой own корень. - Сценарии исполняются, когда page Снова и снова разобраны after после link внутри site (Платформа воссоздает widget's
<script>nodes). Писать code Это работает «сейчас» и перезапускается наvz:navigateevent наdocument(detail.path);DOMContentLoadedОдного недостаточно — after Клик, который не будет стрелять снова. - Ограниченные зоны
/cart,/checkout,/account,/wishlist,/orders,/deals,/dashboard: тот header Разметка есть, сценарии не выполняются — и тот widget's относительный<link rel="stylesheet">С ними вырезается (измеренные на стенде 2026-09-12: header на/cartбез своейheader.css). ряд<style>Остаться; layout папка's CSS связаны в<head>повсюду. Так: header Стили переходят в layout папка или встроенный. - Геометрия и visibility: матрица
/docs/own-markup§7.2 Уровень 3 Сама по себе не гарантирует fixed относительно вида; account для предков и браузера. HTML изменения main's query container и visibility диапазоны соседних blocks.
- Выпуск files Подается байт за байтом (
/w/не относится к ним - подготовить image sizes Вы сами: сервер переписывает shop images в<img src>то Сама версия. - общий CSS: тот layout папка (§2b). Единый CSS матрица по поверхности, Тень DOM limits и охраняемый pages —
/docs/own-markup§7.2.
The full контракт со столом "что движется 1:1/ является отскоком/не является move" и примечания React/Vite, Хвостовой ветер и экспорт — GET /docs/transfer.
Самый короткий path — тот script
The script Жизнь в backend-3D репо: tools/html-transfer/transfer.mjs (Узел 18+, без зависимостей; README рядом с ним. scopes catalog:write + storage:write (+ catalog:read для --list) и owner/admin role в shop. The page --page <slug> должен уже существуют (type=page; slug — из GET /categories, создавать — POST /categories).
VIZEN_TOKEN=vz_pat_… node backend-3D/tools/html-transfer/transfer.mjs ./my-site --page <page-slug>
# rerun = new release; ONLY changed files are uploaded (dedup by sha256;
# index.html is always uploaded — the converter changes it)
node transfer.mjs --doc <id> --list # releases
node transfer.mjs --doc <id> --rollback # roll back to the previous one (pointer only, no new files)
node transfer.mjs ./my-site --page <slug> --api http://127.0.0.1:3970 # standОтвет имеет url (тот page address; для dev key <slug>--dev.<zone>), path, embed_prefix (= тот widget's file корень), seo.
Тот же самый path рукой (API)
# 1) project (level 3)
POST /html-documents { "item": { "name": "Лендинг курса", "level": 3 } } # → id
# 2) release manifest: the server answers WHAT to upload (known hashes are reused)
POST /html-documents/{id}/releases
{ "base": "active", # "active" (default) | "none" | <release_id>
"files": [ { "path": "index.html", "sha256": "<hex64>", "size": 12345 }, … ],
"delete": [ "old.png" ] }
# → 201 { "release_id", "upload": [ { "path", "file_id", "upload_url" } ], "reused": [...], "inherited": [...] }
# 3) bytes — only for upload[] (PUT without Authorization)
PUT <upload_url>
# 4) publication: confirms uploads, validates, converts a whole page
# into plain markup, atomically makes the release active
POST /html-documents/{id}/releases/{rid}/publish
# → 200 { "release_id", "url", "path", "embed_prefix", "seo": { title, description }, "report": { errors, warnings, info } }
# → 422 RELEASE_INVALID { report } — fix the errors, build a new release with base:<rid>
# → 409 FILES_PENDING { pending } — upload the remaining files and retry
POST /html-documents/{id}/releases/{rid}/validate # the same without publishing
# 5) mounting on the page: a level-3 html block (the type=page page must exist)
POST /content-blocks { "item": { "name": "…", "sections": [ { "type": "text", "files": [],
"payload": { "v": 2, "kind": "html", "props": { "html": { "html_document_id": <id>, "level": 3 } } } } ] } }
PUT /categories/{page_id}/content-blocks { "id": <page_id>, "items": [ { "block_id": <block>, "sort_order": 1 } ] }
GET /html-documents/{id}/releases # list, active one, page path
POST /html-documents/{id}/rollback # { "release_id"? } — previous published one; pointer onlyZIP без script (тот full path если backend-3D Репо не под рукой):
# 1) level-3 document
POST /html-documents { "item": { "name": "Лендинг курса", "level": 3 } } # → id
# 2) the folder archive in one request — the server computes hashes, dedups, validates and publishes itself
curl -X POST "https://api.vizen.shop/html-documents/{id}/releases/zip?publish=1" \
-H "Authorization: Bearer $VIZEN_TOKEN" -F file=@site.zip # or Content-Type: application/zip + --data-binary
# a root folder inside the archive (site/index.html) is unwrapped automatically; archive limit 60 MB
# 3) a kind:"html" block with props.html.{html_document_id, level:3} on the page — step 5 aboveА. dev key Публикует релиз в The shop draft (тот page Видно, что в <slug>--dev.…), а prod key в live site. Переключатель Rollback и активация Они только указывают на то, что не создают new files. А. document более 2 MB на output — FailedPrecondition HTML_DOCUMENT_TOO_LARGE.
Один механизм — выпуск. Редактирование опубликованного page без повторной загрузки
Все входные данные (script, ZIP, manual manifest, тот admin's "Страница code" окно) сходиться на Один механизм — освобождение. The owner "Изменить" text здесь" — Чат должен быть в состоянии исправить уже опубликованный page без повторного загрузки проект:
# 0) document id: by name from the company list …
GET /html-documents # → result[] {id, name, level, …}
# … or from the page block
GET /categories/by-slug/{slug}?company_id=<N> # → content_blocks[].sections[].payload.props.html.html_document_id
# 1) current plain markup (index.html of the active release, already without head/body)
GET /html-documents/{id} # → result.source.url — download the text
# 2) fix the markup locally
# 3) partial release: only index.html, the rest is inherited from the active one
POST /html-documents/{id}/releases
{ "base": "active", "files": [ { "path": "index.html", "sha256": "<hex64>", "size": N } ], "note": "текст в hero" }
PUT <upload_url> # index.html bytes
POST /html-documents/{id}/releases/{rid}/publish # the page is updated; rollback — POST …/rollbackТо replace/add один file (ан image) — Тот же звонок с этим path в files; удалять — "delete": ["assets/old.png"]. The admin's "Страница code" окно Это и есть частичное освобождение.. index.html это never дублированный (преобразователь изменяет его в выпуске) — это нормально, а не error; тот document's source_file_id always указывает на то, что active освобождение's index.html.
2b. Сайт layout = папка (общий) CSS, шрифты и code на всех page)
Где делится CSS Жизнь: не в жизни header Но в layout. Стили, шрифты и сценарии поместить внутрь header's html widget Видны только те, кто header себя: другие widgets, тот page body и editor рама live вне ее изоляции. Все, что принадлежит к SITE Вместо одного block входит в LAYOUT Для папок — это относится к каждый page из shop.
А. layout это container group group_type:"theme" (""Воскресение" в UI). Там может быть быть несколькими: а section может иметь свои own design. Который layout а page Решается вопрос о тот theme slot Вдоль лестницы "page → его sections → shop → Самый ранний layout". Внутри layout Выберите header/strip/body/footer (PUT /design/bindings/theme/{id}, подробно — GET /docs/chrome §3.6, в то время как files и code Это section.
Как найти The layout id
# the block list has no server-side filter — filter the response yourself
curl -s -H "Authorization: Bearer $VZ" $API/content-blocks \
| jq '[.result[] | select(.group_type=="theme") | {id, name}] | sort_by(.id)'
# "Main" = the earliest one (minimum id) — the default resolve uses the same ruleЧто входит в папку
Папка организована как widget's (§2а): index.html В корневой, files в подпапках, относительные пути, релизы · откат · dev contour — Один и тот же механизм. Что меняется В этом смысл index.html: для widget Это content, для layout Это site скелет.
| В папке | Куда он идет |
|---|---|
скелет's <head> | meta/link/style nodes Стать настоящими метками в <head> из каждый page; script/noscript Путешествия как сырые HTML |
class на <body> | наклон page корень (рядом с корнем) scope class) |
<body> содержание | code before </body> на всех page |
<title>, <base>, <meta http-equiv>, <link rel=manifest> | Вырезано из публикации: The title принадлежит к page, Остальное – на платформе |
любой *.css | селектор html, body, :root Сфера действия для .vz-theme-{doc} — тот page ROOT class |
icon.png (.jpg/.jpeg/.webp) | icon-32.png, icon-180.png, icon-192.png, icon-512.png и favicon.ico Создан; Ваш own files с одинаковыми именами не переписывается, icon.svg принимается как |
| другой files | Подается из корня /_html/{doc}/{rid}/…; Сценарий берет корень из window.VZ_THEME_BASE |
Вы можете написать `body { … }` и `:root { … }` — Они приземляются на page корень и Это то, что standard путь к set тот site's шрифт, фон и переменные. Селекторы соединений (body.dark, body[data-x]) умышленно NOT Переписано: Ваш script закладывает class На реальном <body>, и rule Также нужно оставаться там.
Скелет разбит Однажды в публикации и хранимые в собранном освобождение row — Витрина не разбирает HTML. SEO не извлекается из скелета: один <title> для всего site Это будет прямой потерей в поиске. results.
Конечные точки папки
Сферы и role Они такие же, как и для widget выпуски: catalog:write + storage:write (+ catalog:read для чтения), owner/admin role в shop.
| метод | Путь | Что он делает |
|---|---|---|
GET | /themes/{id}/site | состояние папки: {contour, theme_id, document_id, active_release_id, base, preview_base?, scope_class, files[], head_source, foot_source, head[], head_html, foot_html, body_class, icons[], icons_generated[], limits}. Нет папок → {document_id:0, files:[]}; GET Ничего не создает |
POST | /themes/{id}/site/releases | draft освобождение от manifest {base, files[{path,sha256,size}], delete[], note} → {release_id, upload[], reused[], inherited[]} |
POST | /themes/{id}/site/releases/zip | освобождение от ZIP (?publish=1, ?base=) |
POST | /themes/{id}/site/releases/{rid}/publish | валидатор → очистка скелета → фавиконы → скелет разобрали в выпуске row → переключатель указателей. {release_id, base, scope_class, icons_generated[], report}; нет url/path — а layout Не сидеть на одном page |
POST | /themes/{id}/site/releases/{rid}/validate | То же самое без публикации |
GET | /themes/{id}/site/releases, /themes/{id}/site/releases/{rid} | Список выпуска с помощью active один/один выпуск с его file карта |
POST | /themes/{id}/site/rollback | откат {release_id?} — через default предыдущий опубликованный; указатель только |
# ZIP in one command: folder → release → publication
curl -X POST "$API/themes/118/site/releases/zip?publish=1" \
-H "Authorization: Bearer $VZ" -F file=@theme.zip
# → { "release_id": 42, "base": "/_html/77/42/", "scope_class": "vz-theme-77",
# "icons_generated": ["icon-32.png","icon-180.png","icon-192.png","icon-512.png","favicon.ico"],
# "report": { "errors": [], "warnings": [], "info": [] } }
curl -s -H "Authorization: Bearer $VZ" $API/themes/118/site | jq '{base, scope_class, body_class, icons_generated}'С помощью script — То же самое в одной команде (--theme и --page never Пойти вместе: a layout не установлен на page):
node backend-3D/tools/html-transfer/transfer.mjs ./theme --theme 118
node backend-3D/tools/html-transfer/transfer.mjs --theme 118 --list
node backend-3D/tools/html-transfer/transfer.mjs --theme 118 --rollbackОбвяжи. layout и style system pages
# the whole site
PUT /design/bindings/shop/0 { "slots": { "theme": 118 } }
# one section (and everything under it)
PUT /design/bindings/category/648 { "slots": { "theme": 118 } }
# system page: 1 = cart (registry below), slots theme|header|footer only
PUT /design/bindings/system/1 { "slots": { "theme": 118, "header": 1975 } }
# what actually applies
GET /design/system/cart?company_id=<N> # public: { "design": { "page": { header, footer, theme_id, … } } }Система page регистр (number = resource_id; числа являются never повторно используемый): cart=1 · checkout=2 · account=3 · orders=4 · wishlist=5 · search=6 · deals=7 · dashboard=8 · coupons=9 · notfound=10 · thankyou=11 · compare=12. Без них own связывающие их inherit тот shop's layout и chrome.
Твой code на page: vz:navigate и ограниченных зон
- Навигация внутри site Происходит без full После каждой навигации и after тот first load
documentполучающиеCustomEvent('vz:navigate', { detail: { path } })— повторно инициализировать твой code Не полагайся на это.DOMContentLoaded: after щелчок по клику document уже есть загруженный; - Навигация в a page с а DIFFERENT layout = full перезарядка (стили двух Макеты не смешиваются);
- Ограниченные зоны —
/cart,/checkout,/account,/wishlist,/orders,/deals,/dashboard(И все вложенное. layout сценарий (head_html/foot_html,window.VZ_THEME_BASE) ни level-3 html Скрипты разметки выполняются там: это деньги и личные data. Стили, классы и Наценка остается — cart Выглядит как часть вашего site но не исполняет сторонние code. Ты cannot Полагаться на a script в cart.
стабильный site file адрес: /_site/<path>
/_html/{doc}/{rid}/… Версия нарочно: релиз неизменен, поэтому такой address Это именно то, что делает его непригодным для того, чтобы link жизнь LONGER чем освобождение - а email Для клиента, а email Подпись, бизнес listing, кто-то else's site: /_html/51/318/logo.svg В следующем тексте 404 становится папка публикации.
GET /_site/logo.svg # the same file of the ACTIVE layout, without a release number
GET /_site/favicon.icoРезолюция такая же, как и для site header: тот shop's theme slot → Самый ранний layout → Его папка - это active Есть три отличия от /_html/, Все последующие из Недостающее освобождение number:
- кэш
public, max-age=300+ ETag Вместо неизменного (публикация должна быть обязательной) Достучаться до клиента на его own); - арендатор header это required — адресация начинается с хоста; без него тот "active layout" это undefined: прямой API Запрос 404;
- освобождение HTML pages не употребляется (как в
/_html/).
The CSS scope То же самое — .vz-theme-{doc}. The rule Это просто: внутри site link то /_html/{doc}/{rid}/…, давать /_site/… Внешнему миру.
Укладка для одного article
Ан article это design resource Как и в случае с product или section:
PUT /design/bindings/article/<id> { "slots": { "theme": 1837 } }лестница: article → its news section and the section's ancestors → LAYOUT → shop → default. Наследственная ветвь является category, не category_children: ан article Сидит в a news section как в подразделе в section, Таким образом, привязка ON тот news section Он сам достигает своего Для чтения статей result: GET /articles/by-slug/{slug} Ответить на вопрос design field (тот admin GET /articles/{id} не разрешает design и не является Несите его. Пока он не article не имеет никаких привязок к своему own, тот result является таким же, как before — сайты Не замечать ничего.
⚠️ body_article это LAYOUT slot, не является article slot: который body view Статьи решаются по level ABOVE тот article. PUT /design/bindings/article/{id} с тот body_article slot отвечать DESIGN_SLOT_NOT_FOR_RESOURCE.
Раскладывание границ папок
- limits Они такие же, как и widget's: ≤ 500 files, HTML ≤ 2 MB, любой file ≤ 50 MB, ZIP ≤ 60 MB. Скелет старше 64 KB — предупреждение
theme.index_large(это земля на EVERY page); - валидатор тот же, минус правила, которые не имеют смысла для кадра (
head.stripped,page.noindex,img.nosize,file.unusedНа иконах);window.VZ_THEME_BASEпринимается как file Корень наравне сVZ_ASSET_BASE; - Нечитаемый скелет не опубликован:
422 RELEASE_INVALIDс codehtml.parse(Сломанный скелет приземлится на всю землю site сразу); - папка's обслуживание document cannot Редактировать через
/html-documents/*конечные точки (FailedPrecondition THEME_DOCUMENT_PROTECTED), Не видны в том document список, и/contentНе служит ему; - не-layout, иностранный или deleted id в
{id}Всем отдать404 THEME_NOT_FOUND.
2с. Компонент с параметрами — одна разметка на многих pages
Вопрос, который нужно задать BEFORE написание разметки: Эта разметка необходима more не один раз? Если ответ "на пять" pages, Каждый из них со своим own title и image", ан html widget Неправильный объект: code Он будет скопирован пять раз, и Шестое редактирование становится шестью редактированием. компонент.
Компонент — это та же папка.-document и те же релизы (§2a), плюс один file: component.json рядом с index.html с параметром schema, Пока наценка держится {{key}}. ценности live IN THE INSERTION, Не в том code: Продавец заполняет их в form, и а new Папка выпускает обновления всех вставок сразу.
# folder → kind=component document → release → publication, with the schema in the response
node backend-3D/tools/html-transfer/transfer.mjs ./banner --component --name "Баннер акции"{ "type": "text", "payload": { "v": 2, "kind": "component",
"props": { "component": { "ref": 42, "params": { "title": "Осень", "sale": true } } } } }Что нужно знать сразу, остальное в районе GET /docs/components:
- Замена является Типичная и серверная-side:
textускользнула,htmlидти через level-1 санитар,link/image— по схеме Whitelist; сырой{{ }}never появляется на витрине магазина, и key Не в том schema ничего не печатает; - тот schema Зеркала для Shopify theme section (
params=settings); Существует восемь типов:text,html,image,link,color,number,bool,select.products/categoryесть NOT Поддерживается: параметр - это value, не каталог query; - TypeScript допускается в папке:
app.tsСоставлено в сиблингapp.jsна Публикация выпуска; не требуется патрон. level 1 санитар снимает с себя script В любом случае — live code потребности level 3 (--level 3); - тот schema это NOT читать из
component.jsonчерез gateway: Сплюснутый, он приходит какmanifestfield на document (База64) и на каждом выпуске (как объект), рядом сactive_release_id— "...справедливая проверка" может быть размещена вообще".
3. Изображения: загрузка и повторное использование
Загрузить always 3 steps (То же самое для HTML и для images):
# 1) request
POST /v1/storages/files
{ "section": "html-asset", "file_type": "image", "original_name": "hero.webp" }
# → { "result": { "id": "<file_id>" }, "upload": { "id": "<upload_id>", "url": "<upload_url>" } }
# ⚠️ upload.url may be RELATIVE (/_upload/…) — prepend the API host
# 2) the bytes themselves
PUT <upload_url> # body = the file, no Authorization
# 3) confirmation — by upload.id (NOT result.id)
POST /v1/storages/files/{upload_id}/done { }file_type это category, NOT а MIME type: image | video | audio | model | file (отправить MIME type Сервер нормализует его до category Сама по себе. file Прикреплен к a product/document через result.id; /done брать upload.id.
Файлы sections: html-source — тот page HTML само собой, html-asset — его images (Оба они являются hidden от shop's разделяемая медиа-библиотека, поэтому мусор не накапливается).
The image это ALREADY в Интернете — пусть сервер вытащит его, в одном запросе:
POST /v1/storages/files/from-url
{ "source_url": "https://cdn.example.com/hero.jpg",
"section": "html-asset", "file_type": "image" }
# → { "result": { "id": "<file_id>", "public_path": "…webp" }, "file_size": 148213 }Нет. PUT и нет /done: тот file уже сохранен, преобразован и изменен; public_path - Это окончательно. http(s) только адреса — а link входить Внутренняя сеть (localhost, 127.*, 10.*, 192.168.*, 169.254.*, ::1) дарить 400 URL_NOT_ALLOWED, и тот же самый rule Применяется к каждому редиректу (максимум три, тайм-аут 30 s). Неудавшийся download или не быть image → 400 URL_FETCH_FAILED; более 10 MB → 400 FILE_TOO_LARGE. Формат взят из тот bytes сами себя (jpeg|png|gif|webp|bmp|tiff), Не от ответа Content-Type — HTML/3D/archives keep Тот же самый 3 steps выше.
Правила изображения:
- The URL исходит из API ответ (
result.url/file.url); never придумывать. - `src` Удерживает родственник path (
/files/sunset/view/content/…): витрина магазина служить files от своего own домен. - изменение размера — тот prefix
/w/{width}или/w/{width}/webpbefore тот path:/w/1024/webp/files/sunset/view/content/05/d1/…webp. Лестница предварительно сгенерированной ширины: 32 | 64 | 128 | 320 | 640 | 1024 | 1600 | 2048 | 2560 — сделать шаг ≥ 2× CSS size (Герой 1600, content 1024, cards 320/640); То же самое дляsrcset. Качество: default шаг, переопределять?q=40..95. SVG не имеют размера — вставьте их как есть. - повторное использование: уже загруженный file (включая product photo Из каталога) В него вставляется его URL — Не загружайте его снова. Тот же самый
file_idможет быть зарегистрирована в нескольких документах. - Зарегистрируйтесь document's активы — Это защищает file от удаления (
FILE_IN_USE):
PUT /html-documents/{id}
{ "id": 10, "item": { "assets": [ { "file_id": "<uuid>", "path": "img/hero.webp" } ] } }
# non-empty assets = replace-set (list ALL assets at once)path Вот это а пригодный для чтения человеком label бухгалтерия («что это такое?») file находится в document"), NOT alias: рендеринг не переписывается src В ней. HTML always Положите реальный URL от API ответ (rule 1) Реальные относительные пути (assets/hero.webp Как в папке) приходить с HTML релиз проекта (§2а) — там же file's path является его address.
3.1 Правила images от shop STORAGE (level-1 html block Медиа-библиотека)
не применяется к HTML релиз проекта files (assets/…В папке §2a: они Подается байт за байтом,/w/не относится к ним — подготовьте size себя (Рекомендуемый ≤ 400 KB,width/heightна<img>). Продукт images в твой own Разметка уже прибывает с увеличенной ({{ p.preview }}).
Вы загружаете ORIGINAL (Это необходимо для будущих ширин, но в HTML ты never link к оригиналу. Всегда /w/{width}/webp{path}.
Тот, который-line rule: width = Ближайший шаг ≥ 2× пикселей image На самом деле занимает на экране.
Лестница предварительно сгенерированной ширины: 32 · 64 · 128 · 320 · 640 · 1024 · 1600 · 2048 · 2560 (Другие ширины также работают, но вычисляются на лету. first Посетитель ждет).
| На шее page | Занятие | Использовать | С srcset |
|---|---|---|---|
| Полный-width герой | ~1200 х | 1024 | 1024 1x, 1600 2x |
| Изображение в a text колонка | ~600-800 px | 640 | 640 1x, 1024 2x |
| Карта в сетке 3-4 колонки | ~250-300 px | 320 | 320 1x, 640 2x |
| Маленькая плитка, логотип, icon | 40-80 px | 64 | 64 1x, 128 2x |
| Обзор аватара | 48 х | 64 | 64 1x, 128 2x |
Скелеты всех image В посадке:
<img src="/w/320/webp/vizen-prod-files/sunset/view/content/ab/cd/file.webp"
srcset="/w/320/webp/...file.webp 1x, /w/640/webp/...file.webp 2x"
width="300" height="225" loading="lazy" alt="Кровать Аврора 120×90">srcsetс а 1x/2x пара — в противном случае image размыт на сетчатке и без нее дважды столько, сколько необходимо, заряжается;width/height— так page Не прыгать во время загрузки;loading="lazy"— на всех images кроме как first Один на экране (Герой загружается сразу, Дай емуfetchpriority="high");alt— в plain Язык человека: оба SEO и доступность.
Предыстория image в CSS (background-image) cannot использовать srcset — Просто там Сделайте первый шаг выше slot: а ~1200-широкий block → /w/1600.
Что ты должен NOT делать:
| Не надо. | Почему |
|---|---|
src="https://storage.yandexcloud.net/..." или /files/... | Это оригинал; он может весить 10 MB для плитки 300 px |
один width для всех slots (/w/1600 повсюду) | Герой и миниатюра отличаются 20× по весу |
| а width "с маржей, на всякий случай" | каждый extra лестничная ступень ×2-4 в весе |
?q= без необходимости | Качество уже настроено на шаг |
SVG через /w/ | вектор растеризуется и теряет свою точку — вставьте его как есть |
самопроверка before передавать (Проверить его, обязательно):
# 1) the markup has NO direct links to the original — must be 0
curl -s https://{slug}.vizen.shop/{page} | grep -c 'storage.yandexcloud\|"/files/'
# 2) see which widths are actually requested
curl -s https://{slug}.vizen.shop/{page} | grep -oE '/w/[0-9]+' | sort | uniq -c
# 3) weight of the heaviest image on the page (the hero) — normal is up to ~200 KB
curl -s -o /dev/null -w '%{size_download}\n' https://{slug}.vizen.shop/w/1024/webp/...Если first Командный не равен нулю, page Не готов к этому.
4. Загрузите HTML и привязывают его к document (level 1/2; а level-3 папка пропускает этот шаг - §2a)
# 3 upload steps with section=html-source, file_type=file (a category, not MIME)
POST /v1/storages/files
{ "section": "html-source", "file_type": "file", "original_name": "index.html" }
PUT <upload_url> # body = your HTML
POST /v1/storages/files/{upload_id}/done { }
# binding the file to the document
PUT /html-documents/{id}
{ "id": 10, "item": { "source_file_id": "<file_id>" } }Редактировать page = а new file + другой PUT из document (тот source Это правда.). Ан HTML Проект с релизами (§2a) не нуждается в этом шаге: index.html является частью Редактирование и редактирование является new освобождение.
5.Сделай block и настроить его wrapper (boxed level-1 html block)
⚠️ Перенесенная папка (level 3) Не имеет коробки до тех пор, покаprops.html.boxed: trueэто set: wrapper settings не относится к нему; геометрия это set твоей CSS (/docs/transfer§7). section Речь идет о boxed block.
POST /content-blocks
{ "item": { "name": "Промо весна",
"sections": [ { "type": "text", "payload": { "v": 2, "kind": "html",
"props": {
"blockWidth": "full",
"contentWidth": "content",
"marginTop": 24, "marginBottom": 24, "blockRadius": 16,
"html": { "html_document_id": 10, "level": 1 }
} } } ] } }
# → { "result": { "id": 136, … } }Сколько документов на человека page — тот rule
Ты решаешь. Один document для всего page Нормально: переведённая папка (§2а) always работает таким образом; редактирование части = частичное освобождение одной file. отдельно section документы (герой, льготы, отзывы), CTA) Они необходимы только тогда, когда owner Он хочет переставить части в admin, Спрячьте одну mobile или переплетать ваши sections С готовой платформой blocks (galleries, product cards, forms); Стоимость является освобождением своего own для каждой части.
обертка settings Ссылка (точно, что это такое) admin панель имеет)
| Группа | Ключ в props | ценности |
|---|---|---|
| блокировать width | blockWidth | "content" (default, site колонка) / "full" (к краю экрана) |
Контент width (видимым с помощью full) | contentWidth | "content" (Вернуться в колонку) / "full" |
| маржа ↑ / ↓ | marginTop / marginBottom | number, х |
| Побочный предел | marginX | number, px (нет = 0); S/M/L — панель presets) |
| угол radius | blockRadius | number, Px. ⁇ ️ ⁇ ️ STRIP Сам по себе по умолчанию 0 — 16 px является system округление block's INNER элементы (class .vz-radius). Текущий values — GET /docs/widgets, section wrapper |
| Видимость на устройствах | visibility | { "min": 320, "max": 1024 } — окно width, х |
| Таблетка layer (640–1023) | tablet | одинаковые ключи, в field |
| Мобильный layer (< 640) | mobile | одинаковые ключи, в field |
Готовые комбинации:
// full-width hero, text in the site column
{ "blockWidth": "full", "contentWidth": "content" }
// narrow insert with air and radius
{ "blockWidth": "content", "marginX": 24, "blockRadius": 16,
"marginTop": 32, "marginBottom": 32 }
// on mobile, drop side margins and radius
{ "blockWidth": "full", "marginX": 24,
"mobile": { "marginX": 0, "blockRadius": 0 } }
// desktop-only block
{ "blockWidth": "full", "visibility": { "min": 1024 } }бокс block: Начнем с width:100% и wrapper settings; проверить непреднамеренный горизонтальный свиток., query контейнеров и side Эффекты Unboxed HTML Описаны в /docs/own-markup §7.2.
Готовая платформа block: "Category products" (kind: "productListing")
Для готового filter и пагинацию вы можете разместить на платформе block: Сетка из а category's products с а filter своим attributes и патология, то же самое компонент, как на category page. Нет. data хранится в payload — витрина магазина Опубликованы фетиши products через categoryId Для себя. own структура и design есть products[] и vz ключи (§17), /docs/own-markup §2); Разум feed limits.
POST /content-blocks
{ "item": { "name": "Каталог: диваны",
"sections": [ { "type": "text", "payload": { "v": 2, "kind": "productListing",
"props": { "listing": { "categoryId": 12, "showFilter": true, "limit": 24 },
"blockWidth": "content" } } } ] } }props.listing: categoryId (id из одного type='product' category; required — Без него block не render) · showFilter (default true) · limit (4–48, default 24) · template (oneOf ["default"]; empty = shop default; ан unknown value читать как default). The schema является аддитивным; частично объект допускается. Filter/pagination есть INDEPENDENT за block: У каждого есть свой own URL пространство имен параметров b<blockId>_f_* / b<blockId>_page; тот category page's родной listing жить plain ?f_*/?page.
Готовая платформа block: "Продукт card" (kind: "productCard")
Готовый card: props.card.productId — и витрина магазина рисовать full card (тот Simple/Classic/3D template жизнь IN THE PRODUCT, card_template, не дублируется в block). Вы можете построить свой own card из HTML и {{ product.* }}/vz ключей путем связывания template на правой стороне resource (§16).
{ "type": "text", "payload": { "v": 2, "kind": "productCard",
"origin": "builder", "props": { "card": { "productId": 5 } } } }сложенный blocks — created автоматически
А. type='product' category ALREADY получает "Category products" block + тот __page:top manifest Творение (творение) product Получить "Продукт" card" block. После POST /categories / POST /products делать NOT Создать эти blocks рукой — first читать GET /categories/{id} (content_blocks в ответе) и работать с существующих.
Связанный section обозначается как a ROLE: payload.role: "bundled" ("технический"" block). Правила игры: do NOT Удалить такой block (тот editor не позволяет этого, и Миграционная фиксация восстановит ее — чтобы скрыть от page, Поднимите флаг в manifest: __page:top → payload.page.hideSystemBlock: true (тот "Скрыть техническое blocks" checkbox в page settings; Обратимая. own blocks не set тот role — Он зарезервирован для платформы..
2.1.1 Конструкционные привязки (Sh-1): а template вместо копии
А. resource может быть RENDERED В библиотеке block без своей own копия — через привязку:
# assign your body group to a section's products (inherited down the tree)
PUT /design/bindings/category_children/{categoryId}
{ "slots": { "body": <groupId> } }
# what actually applies to the resource (with "inherited from…")
GET /design/bindings/product/{id}
# → { own: [...], resolved: { body: {block_id, source: "category:10"} } }
# a block's reach before editing/deleting ("will affect N pages")
GET /content-blocks/{id}/usage # → { total, refs[] } (refs: bindings + manifests resource_type "layout"/"zone")Типы ресурсов: shop (id 0) · category (тот page само собой) · category_children (тот products внутри) · product · article · theme (id = тот LAYOUT group, §2b) · system (витрина system page посредством своего реестра number, §2b). Слоты из current model — header · feed · body · footer · theme; тот theme resource Кроме того, есть пер-kind тела body_product · body_category · body_news · body_article (Это LAYOUT slots, не page slots); system имеет только theme/header/footer. А. slot иметь THREE Ответы: отсутствует на карте = inherit, 0 = "Здесь ничего нет (лестница останавливается — вот как это происходит). header переключается off), id = это group. Наследственная пара layout/chrome (manifest block kind=chromeKit) держать Работает в старых магазинах, но не используется для new code — тот full model находится в районе GET /docs/chrome §3.1-3.6. chain Резолюция приходит как design field в GET /categories/{id} и public by-slug (для одного article — в GET /articles/by-slug/{slug}). Мутация отправляет webhook `design.changed` {company_id, resource_type, resource_id, slots} — только от prod contour (dev Редактирование приходит с публикацией).
Изображения в HTML: Не оптимизируйте ничего вручную
Поставь plain <img src="…"> Ссылка на ваш file — Сервер выполняет image изменение размера Когда служите document: Добавляет /w/{width} + srcset 640/1024/1600 + sizes="100vw" + loading="lazy". Оригинал never достигать Накладка, даже если вы связали multi-мегабайт PNG.
Что делает сервер NOT касаться (и) why):
- ссылки на OTHER домены — наш резизер не обслуживает их;
svgиdata:— Мы не растрируем векторы и не переписываем в строке data;- разметка где вы set
srcsetYOURSELF — Мы предполагаем, что отзывчивость обрабатывается.
Установить slot size Совпадение стилей и совместительств sizes; для vz-src использовать готовый srcset от scope (GET /docs/own-markup §6) Одной лишь формы недостаточно.
6. Собрать page и скрепить blocks
# slug taken? check BEFOREHAND — otherwise 409 AlreadyExists SLUG_TAKEN
GET /categories/by-slug/promo-vesna?company_id={id} # 404 = the slug is free
# page = a type='page' category
POST /categories
{ "item": { "name": "Промо весна", "type": "page", "is_published": true,
"seo": { "slug": "promo-vesna" } } }
# → { "result": { "id": 42, … } } ← category ids are NUMBERS (see §0)
# ZONE TABLE OF CONTENTS (mandatory!) — a service block named __page:top,
# it tells the editor in which order to show the page's blocks
POST /content-blocks
{ "item": { "name": "__page:top", "sections": [ { "type": "text", "payload": {
"v": 2, "kind": "zone", "refs": [136, 137] } } ] } }
# → { "result": { "id": 140 } } (there is also __page:bottom — the bottom zone)
# binding is a REPLACE-SET: list ALL page blocks at once,
# INCLUDING the table of contents (otherwise its references are "broken")
PUT /categories/42/content-blocks
{ "id": 42, "items": [ { "block_id": 140, "sort_order": 1 },
{ "block_id": 136, "sort_order": 2 },
{ "block_id": 137, "sort_order": 3 } ] }⚠️ PUT /content-blocks/{id} является частичным обновлением; не-empty item.sections заменять ALL sections, Изготовьте их из свежих GET. Для строителя groups keep ссылки последовательны и page_design_version (GET /docs/chrome §3.1).
Аналогично: PUT /products/{id}/content-blocks, PUT /articles/{id}/content-blocks. Один block может быть привязан к нескольким pages.
Почему таблица содержимого обязательна. Вестибюль отдает в page Без него, но в тот admin editor тот owner Видит, что blocks Только как «другие» — они cannot быть С таблицей содержимого все, что вы делаете created немедленно редактируемый Человек, как обычный blocks.
Таблица правил содержания:
- в лучшем случае ONE
__page:topи один__page:bottomза page; refsдержать block ids в render order (номера);- page флаги live ONLY в
__page:top, рядом сrefs:"page": { "hideSiteHeader": true, "hideSiteFooter": true, "hideSystemBlock": true }. Каждый флаг переключается off целое LAYER, Ни одного block:hideSystemBlockУстраняет всю техническую layer — card, feed, breadcrumbs, page title. Нет отдельногоhideBreadcrumbsлюбой more (Удаленный 2026-08-20: он скрывался только наследие breadcrumbs variant И не прикасался к widget, i.e. Это был второй механизм для Что? layer Если вы найдете это в своих записях - флаг мертв.; Сервер сохраняет его и ничего не меняет.; - при редактировании существующего page: Читать полностью
GET /categories/by-slug/{slug}?company_id={id}(один block один:GET /content-blocks/{id}), Найди его. block названный__page:topвcontent_blocks[], взять егоrefs, Добавить свой ids, Отправьте таблицу содержимого черезPUT /content-blocks/{id}и повторять замену-set связывающий; - ⚠️ делать NOT set
"origin": "builder"на blocks — Отметить blocks created через тот editor; С твоим blocks исчезнуть из page В самом first экономить.
7.Сайт. layout и четырех видов разметки а level-1 html block
⚠️ Это section Речь идет о html block в платформе, для перенесённой папки (level 3, без коробки) main document это GET /docs/transfer: Нет ни одного rule для этого здесь.Твой HTML это часть которого page, Не iframe: Он живет внутри site's делиться container, Он наследует свой цвет и шрифт и сидит рядом с ним. header, menu и footer. Так что выложи его FOR это container.
Номера слоёв — возьмите их из API, Не из памяти
GET /v1/storefronts/resolve?slug={shop} → тот layout field (public):
"layout": {
"content_max_px": 1600, // max site content width
"container_padding_mobile_px": 16, // container side padding
"container_padding_tablet_px": 24, // (< 640 / 640-1023 / >= 1024)
"container_padding_desktop_px": 32,
"breakpoint_tablet_from_px": 640, // responsive layer boundaries
"breakpoint_desktop_from_px": 1024,
"content_box_max_px": 1536, // USABLE box = 1600 - 2*32
"margin_x_presets_px": [0,16,32,64], // presets of the "Side margin" panel
"radius_presets_px": [0,8,16,32], // "Corner radius" presets
"radius_default_px": 16,
"font_family": "ui-sans-serif, system-ui, sans-serif, …",
"base_font_size_px": 16
}The mode это set через TWO block settings
Семь рекомендаций для a boxed level-1 html block
- Наружный margins/width через props Если щит должен контролировать их. внешний CSS нуждается в layout или level 3. местная тень CSS Не дойдет туда.
- Проверить text удобочитаемость вблизи краев: сетка платформы или ваша own padding это design выбор.
- Мы не рекомендуем непреднамеренный горизонтальный прокрутка на 360/768/1920 Для карусели, проверьте, что это container что такое свитки.
- Проверь. block в одиночку и с соседями: накладки, поля и меню должны быть keep предназначенный look когда text когда меняется сосед block это hidden.
- Разделить page вводить sections если owner хочет перестроить или повторно использовать Части, целое page как один document Также допускается.
- Используйте платформенную сетку для look согласуясь с тем, что template; твой own columns и Точки останова — для вашего own system. Не смешивайте их случайно..
- Проверить 360/768/1920 px и границ выбранных точек останова: text читаемый, контрольные, доступные, menu Не обрезан. fixed матрица —
/docs/own-markup§7.2.
The key Инструмент - это `.vz-inner` class и `--vz-content-box` переменный. Платформа предоставляет два готовых класса и две переменные внутри вашего компьютера. document. На витрине и в editor level 1 Использование Shadow DOM с базовыми крючками. Однако, level-1 editor пока не получается то же самое layout CSS как витрина; Проверьте финал look на site. Уровень 2 — это отдельный iframe без Автоматическая передача этих классов: include Ваши стили явно.
| Инструмент | Что он делает |
|---|---|
.vz-inner | Сохраняет layer в site колонка: max-width: var(--vz-content-box); margin-inline: auto |
.vz-pad | full width + тот page's side padding: padding-inline: var(--vz-gutter) |
--vz-content-box | site колонка width. ≈1536 х только для одного full-width block на большом экране, в других контекстах value это RELATIVE (block бокс минус маржа) — не зависит от number, Используйте переменную |
--vz-gutter | page side padding: 16/24/32 px шаг за шагом |
Корневой элемент always покрывает full block width — Нарисуйте на нем фон и заверните text и сетка в .vz-inner (на site сетка или .vz-pad (full width но с padding с края. Панель's "Содержимое внутри" переключателя (доступно) ONLY на "full width" block, blockWidth:"full") изменениях, value из --vz-content-box: content → site колонка, full → 100% (limit удалять).
| # | блокировать settings | Как это выглядит | Как YOU выложить |
|---|---|---|---|
| А. | blockWidth:"content" (default) | тот block уже находится в site колонка | выложить на width:100%, нет max-width Нужна, надолго text set читаемый line мера: .text{max-width:66ch} — 1536 px столбец дает ~200 символов, которые невозможно прочитать |
| B | blockWidth:"full" + contentWidth:"content" | фон к краям экрана, content на site сетка | background/gradient на корневой элемент; text и cards внутри <div class="vz-inner">. Наиболее распространенный mode для героев |
| C | blockWidth:"full" + contentWidth:"full" | full width, нет limits | limit сам по себе: .wrap{max-width:1536px;margin-inline:auto;padding:0 32px} (взять number из layout.content_box_max_px) |
| D | blockWidth:"full", contentWidth:"full", marginX:0, blockRadius:0 | от края до края, без края или округления | width:100% Без внутреннего limits — для карт, слайдеров, full-Радиус должен быть 0: в противном случае углы вырезаются прямо на краю экрана |
Скелет героя (mode B) — запомните это pattern:
<section class="hero"> <!-- full-width background -->
<div class="vz-inner hero__body"> <!-- content in the site column -->
<h1>Заголовок</h1><p>Текст</p>
</div>
</section>
<style>
.hero { background: linear-gradient(120deg,#101828,#1d2939); color:#fff; }
.hero__body { padding: 64px 32px; } /* your own inner padding */
</style>Как выбрать: а text section, cards, а form → А.. Герой с а full-width фон и text на site сетка → B. Твой own не-standard широкая сеть → C. А. full-экран image/map/slider → D.
Раздел JSON для каждого mode:
// A — content width
"props": { "blockWidth": "content" }
// B — full-width background, content in the site column ← recommended for heroes
"props": { "blockWidth": "full", "contentWidth": "content" }
// C — full width, you limit it yourself
"props": { "blockWidth": "full", "contentWidth": "full" }
// D — edge to edge, no margins
"props": { "blockWidth": "full", "contentWidth": "full", "marginX": 0, "blockRadius": 0 }Что? wrapper делать с level-1 block content (важный)
- Это не полоса, которая клипы, но хозяин вашей разметки. The block стричь себя (
div.vz-box) ничего не зажимает: тени, свечения и наложения выходят за его пределы полностью. Тень (The Shadow) DOM хозяин одного из html block клипы BY DEFAULT — Округление, с помощью 2 px предел, поэтому свесы глифов не вырезаны. Ваша разметка будет обрезана в block дождевик доprops.overflow: "visible"это set (§17.0. Тем не менее, не нажимайте text Право на границе — Дайте ему его own внутренний padding (.vz-pad,.vz-innerили твой ownpadding). - Вертикальные разрывы между blocks делать NOT добавлять. Между соседями есть один шов:
max(marginBottom of the upper, marginTop of the lower); если EITHER из двух это set явно для0, тот blocks ноль (ноль бьет все); оба empty — тот system 24 × Шов вытягивается один раз, как нижний block's маржа-top;marginBottomПрименяется «как есть» только на последнем block из feed. - Для прямой линии вы можете выбрать `blockRadius: 0`. Округление на краю Также хорошо, если это предназначено. design.
Маржинальные и radius — через панель или через own CSS
Вертикальные пробелы (marginTop/marginBottom), side воздух (marginX) и округление (blockRadius) удобный для set как block props: затем тот owner Они могут быть установлены в панели, а block быть последовательным со своими соседями. Вы можете использовать layout.margin_x_presets_px / radius_presets_px или ваш own CSS.
Типография и цвет
шрифт и text Цвета являются наследуемый от page (layout.font_family, base_font_size_px). Хочу брендированный look для block — set font-family явно; Хочется слиться с page — Не трогать font-family - В общем, заголовки. Можно масштабировать с clamp(), rem или vw — проверка читаемости при разных ширинах.
Позиционирование и block границы
Используйте матрицу /docs/own-markup §7.2: а query container не является а fixed содержащий block Проверь прокрутку и геометрию на всех браузерах. target поверхность; props.overflow:"visible" удаляет вырезку хоста, но не изменяется Тень DOM изоляция.
8а. Уровень-1 html block Правила разметки (теневые) DOM, дезинфицирующий)
не применяется к a level-3 папки: вот она, настоящая DOM, скрипты исполняются, якоря работа, html/body/:root Переписаны в проект wrapper (§2а).- Что полоскает сервер (level 1):
<script>,on*attributes (onclick,onerror…),javascript:ссылки,<iframe>,<form>/<input>. Что выживает:<style>, стильные, классы,id, семантика (section/article/header/footer/figure/picture/source),srcset, data-URI images,<link rel="stylesheet" href="https://…">(Только HTTPS). - Изоляция (level 1): местный CSS Селектор не покидает тень DOM. CSS для наружного wrapper Должно быть, снаружи. layout/shadow/editor и каскадная матрица —
/docs/own-markup§7.2; author Классы - §5.5.
- Наследование: шрифт, text цвет и theme CSS переменные попадают внутрь — Не перезагружайте их без необходимости (
all: initialТолько если вам нужна независимая look). - широта — См. §5, разметка rule.
- Ответственность — медиа-запросы и
max-width: 100%для images; тот page должен Работать по 360 px. - якоря (level 1):
#anchorвнутри block Не прокручивать page (Тень DOM граница) — не строить навигацию на якорях внутри одного document. Над level 3 Якорные работы. - Размер: keep тот document под ~256 KB; тяжелая limit 2. MB (над ним сервер не будет служить ему, и block Отклоняется к iframe без SEO).
- Храните ключи других: при редактировании существующего block, не drop unknown
payloadfields — могут принадлежать другим features.
8. Квизы и интерактивность (level 2)
нужда JS — создавать document с "level": 2. Он отображается в iframe sandbox (sandbox="allow-scripts", Нет доступа к site или cookies), скрипты работают, но content Не дойдет до поиска results. Рецепт: интерактивность в level 2, и SEO text как отдельное level-1 block На том же page.
9.Проверь. result
# 1) the page serves your text in RAW html (no JS) — that is SEO
curl https://{shop}/promo-vesna | grep "Заголовок из моего HTML"
# 2) the document's sanitised content (public)
curl "https://api.vizen.shop/html-documents/10/content?company_id={id}"
# → { "html": "…", "level": 1, "updated_at": "…" }
# 3) where the document is used
curl "https://api.vizen.shop/html-documents/10/usage" -H "Authorization: Bearer …"Если …/content возврат 404, тот document не привязаны к какой-либо block (через design: неприкрепленные черновики не являются публично читаемыми, или они имеют level: 2, или нет file был загружен.
10. Ошибки и limits
The error Форма зависит от транспорта. RPC Конечные точки обычно отвечают { "error": "rpc error: code = <CODE> desc = <MARKER>" }; отдельный HTTP конечные точки return короткий error и extra fields. Например, освобождение публикация: 422 {"error":"RELEASE_INVALID","report":{"errors":[…]}}, где Каждый вывод имеет level, code, message, плюс file и ref Когда присутствуешь. public POST /docs/validate с недействительным body дарить 400 {"error":"invalid_request","hint":"…"}, и с действительным body — 200 с ok и findings[{path,status,why}]. Держи. HTTP status и весь отчет; не look для ref Только в top level. диагностика order и формы отклика — GET /docs/troubleshooting §2а.
| Положение | HTTP | Маркер в desc |
|---|---|---|
| Пропавший scope на PRIVATE конечная точка | 403 | PAT_SCOPE_MISSING (required_scope: …) |
| Конечная точка не открыта для токенов вообще. | 403 | PAT_METHOD_NOT_ALLOWED |
Нет. catalog:read, Конечная точка является PUBLIC | 200 | есть NO error Ответ: анонимный прогноз (без черновиков или сквозняков) hidden items). На твоей own неопубликованный page — 404 …_NOT_FOUND; без company_id — 403 AUTH_COMPANY_PROBLEM (/products) или 403 COMPANY_ID_PROBLEM (/categories, /articles). Так always проход `company_id` |
| Кто-то else's/non-existent/hidden объект | 404 | …_NOT_FOUND (Общая маска — существование не раскрывается) |
| Удаление a document используемый a block | 400 | DOCUMENT_IN_USE: used by N content blocks |
| Удаление a file используемый a document | 400 | FILE_IN_USE |
| Page/category slug взятый | 409 | AlreadyExists … SLUG_TAKEN (проверить by-slug заранее) |
page.number не перешел в /articles | 400 (нумерация от 1; optional для /products) | |
| Изображение 10 MB | 400 | FILE_TOO_LARGE на /done (сжимать before загрузка) |
| Слишком много запросов | 429 | RATE_LIMITED + Retry-After header (секунды. Пер-компания limits в минуту: читает 600, пишет 240, мутация хранения 180 — ждите Retry-After и повторный |
блокировать data (sections) | ≤ 64 KB — делать NOT встраивать HTML, только html_document_id | |
| html document size | Рекомендуемый ≤ 256 KB, максимум 2 MB |
11. "Готовность страницы"
11.1 Готовы site / папка (level 3)
- [ ] А. dev-contour key во время итераций; prod — Только окончательная публикация.
- [ ] Все пути относительны, внутри папки; external ресурсы
https://только. - [ ] собственный base.css Под корнем class: заголовки, списки, ссылки, кнопки не зависит от по умолчанию браузера (предполет магазина сбрасывает их)).
- [ ] The script Начинается немедленно и вновь инициализируется
vz:navigate. - [ ] Магазин data отскок (
vz-for,{{ product.* }},vz-add-to-cart,href="form:<id>"); Ссылки ведут к реальным слизнякам. - [ ] Открытый прямым address AND после чего после link внутри site; Скриншоты в 1440 и 390; консоль без ошибок и без 404 на
/_html/;/cartшоу тот header. - [ ]
--doc <id> --listпоказывает active освобождение; I Знать команду отката. - [ ]
curlиз page Покажи мне text в сыром виде HTML.
11.2 Уровень-1 html block внутри page построенный из widgets
- [ ] Прочти shop Каталог; ссылки ведут к реальным слизням.
- [ ] Изображения, загруженные один раз, вставлены через
/w/{width}/webp, активы зарегистрированный в document. - [ ] Мой CSS и props Дать ожидаемый width; непреднамеренно горизонтальный свиток или content перекрытие.
- [ ] The page Хорошо читается на 360 px.
- [ ]
curlиз page Покажи мне text в сыром виде HTML. - [ ] The block Связанный, page это
is_published: true.
12.Как доложить в owner (MANDATORY)
Вы не работаете в вакууме: человек принимает вас. result. Половина "жуков"" Они видят, что это действительно неясная передача.:
12.1.А dev key делать NOT publish — И это нормально
Исключение: owner может предоставить key тот `publish:write` Правый (Правый) "Разрешить публикацию draft" checkbox Когда он будет выпущен.POST /v1/dev/publishДоступно для вас, но нажмите на него ONLY когда owner явно заданный "publish", never на твоей own Инициатива. WHOLE shop draft публикуется, так before издатель look на что выйдет:POST /v1/dev/publish/preview(сухой пробег, ничего не меняет иGET /v1/dev/status— и показать summary в owner. А.400отказ сFailedPrecondition ORG_NOT_PUBLISHABLE— тот shop блокируется, под обзор или scheduled для удаления: публикация закрыта до момента закрытия платформы Решает; повторная попытка не поможет — скажите owner.
А. key в "draft" contour Создавать объекты в draft version из site. Они являются NOT на live site до тех пор, пока owner нажать «Опубликовать».:
- 404 на одной стороне live site правильно after Ваша работа ожидаема result, не твой error. Не пытайтесь «исправить» его повторяющимися записями..
- самопроверка:
GET /categories/by-slug/{slug}?company_id={id}с токеном →200(тот draft Там есть), Без токена →404(не опубликовано пока). Именно эта пара ответов = работа done правильно. - The draft торрент делает NOT Открытый прямым link, Даже для того, owner: Это Доступно только из admin (owner сессия, так что draft не дотягиваться посетителей и поисковых систем. Не давать ссылки как
slug--dev.vizen.shop— Они не будут работать.
12.2 Ссылки в докладе — full только кликабельные
Плохой (The Bad) owner cannot Открой его, link мертвый):
Done, page /dvukhyarusnye-pod-zakaz — see the adminХорошо.:
Done: page "Двухъярусные кровати под заказ"
Public address (will work after publication):
https://{slug}.vizen.shop/dvukhyarusnye-pod-zakaz
Currently in the draft. To view and publish:
1. Open the shop admin
2. Switch to DEV mode (the orange switch)
3. Press "Publish"Link правила:
- always ан absolute URL С помощью схемы
https://…— относительный path/promoНе кликабельны в чате; - Забрать домен магазина из
GET /v1/orgs/current(slug→https://{slug}.vizen.shop) или от него shop's custom домен; делать NOT придумывать; - link в product/category своим реальным slug от API Ответ, а не путем id, Если у него есть slug;
- Если объект находится в draft, Напиши "Будет работать" after «Публика» рядом с link.
12.3 Что должен содержать окончательный доклад
- Что было? created — В качестве списка, с человеческими именами (не ids).
- Где? look — full URL + a draft note.
- Что? owner следует нажимать — бетон steps до публикации.
- Что случилось? NOT работа — Честно говоря, с reason. Если конечная точка отвечает
PAT_METHOD_NOT_ALLOWED(Например, витрина settings, логотип, currency — Они закрыты для токенов, так сказать: "не доступны через токены". API, делать в admin "Не замолкайте и не проходите частичного result off как полный. - Что останется на потом — Если работа разделена на этапы.
12.5.Привод или draft — определять BEFORE Начать работу
Прежде всего, спросите API Где ты key пишет:
curl https://api.vizen.shop/v1/account/token -H "Authorization: Bearer $TOKEN"И вести себя по-другому, и Обязательно расскажите owner В самом first message который mode Вы работаете в:
| Ключ | Что ты скажешь owner | Что вы можете сделать |
|---|---|---|
| Проект | "I Я работаю в draft — тот live site Не изменишься, пока не поменяешься publish" | create/edit не допускается публикация (или только по прямому запросу, если publish:write был предоставлен) |
| Полный доступ | ⚠️ "Внимание: редактировать STRAIGHT в live site, Клиенты видят это" | Все, попросите подтверждения before объемные правки |
Безопасность rule: с full доступ, не выполнять операции по объему бесшумно. Исключить 50 products или перезагрузить каталог - спросите first, Тогда сделай это.
12.6 Как создавать ссылки (далее - "Ссылки") main source путаница)
The shop address это NOT Творение — оно исходит из API:
curl https://api.vizen.shop/v1/orgs/current -H "Authorization: Bearer $TOKEN"
# → { "slug": "myshop", ... } → storefront: https://myshop.vizen.shopЕсли shop имеет свой own домен подключен, витрина магазина живет там — затем дайте ссылки к нему, а не к *.vizen.shop.
| Что? | Как строить | Пример |
|---|---|---|
| Домой | https://{slug}.vizen.shop/ | https://myshop.vizen.shop/ |
| Page/section | https://{slug}.vizen.shop/{seo.slug} | https://myshop.vizen.shop/promo-vesna |
| Продукт | https://{slug}.vizen.shop/{section}/{seo.slug} | https://myshop.vizen.shop/krovati/avrora-120 |
| Проект | делать NOT давать link | Открывается только от admin |
Проверьте себя before Отправить ответ:
- тот link начинается с
https://(относительный path/promoНе кликабельны в чате); - У него есть реальная slug от API Ответ, а не id или в виде макияжа name;
- Если объект еще не опубликован, "будет работать after "Публикация" стоит рядом с ней.
12.7. Заключительный. message template
Done. I worked in the DRAFT — nothing has changed on the live site yet.
What I did:
• Page "Промо весна" — 4 sections, 12 products from your catalogue
• Uploaded 9 photos (taken from the product cards, no new duplicates)
Where it will be available after publication:
https://myshop.vizen.shop/promo-vesna
To publish:
1. Open the shop admin
2. Switch to DEV mode (the orange switch)
3. Press "Publish"
Did not work: could not set the logo — storefront settings are closed to
API keys (error PAT_METHOD_NOT_ALLOWED). Set it in the admin:
Company → Storefront → Logo. The file is already in the media library.Плохой ответ выглядит так — Не делай этого:
Done, created everything. See /promo-vesna in the admin.(Не показывает, что было created; тот link Не кликабельны; ничего о draft и Публикация; молчание о том, что не работает)
12.4 Во время работы
- Скажите, что вы делаете вкратце steps: "Читать книгу 32 products", "9 фотографий, "сборка" page". Десять минут молчания в чате = owner Думает, что все повесилось.
- Нападай на API error — показать его text, «Нечто не работает»". The owner может пройти мимо error code (
SLUG_TAKEN,FILE_TOO_LARGE) в Разработчик платформы. - Не изобретать data: взять цены, имена и фотографии с shop каталог. Задуманный product На витрине магазина хуже, чем на empty section.
13. FORMS AND LEADS
The full договор forms, leads, CRM песочницы и два режима тестирования — `GET /docs/forms`. Также там: block props.form.formId, тот button form:<id> и level-2 HTML мост (vizen-form-open / vizen-form-submit).
14. MODAL CONTENT FROM HTML
Формы и их кнопки перемещаются в /docs/forms; тот product и design нижеперечисленные формы Оставайтесь частью этого руководства. number §14.3 Сохраняется для существующих ссылок.
14.3 Модальное окно HTML: а product или готовый design group
Продукт: три открывателя forms
The full Окно командования выполняется ONE modal: Струна — что в ней написано Что нарисовано:
modal:product:<id>[?size=small|medium|full&head=0|1&foot=0|1&pad=<px>
&cls=<name>&tpl=<groupId>&nav=ids:42,43&v=<sku>]<a href="modal:product:42?size=full">Кресло</a>
<!-- 2. A real href + the command in the key: the address stays for SEO,
Cmd/Ctrl+click, middle click and working without JavaScript -->
<a href="/catalog/chair" vz-modal="modal:product:42?size=full">Кресло</a>
<button type="button" vz-modal="modal:product:42?foot=1&nav=ids:42,43">
Быстрый просмотр
</button>
<a href="/catalog/chair" vz-quickview="42">Кресло</a>| Параметр | ценности | по умолчанию | Что он делает |
|---|---|---|---|
size | small · medium · full | medium | small = 480px, medium = site content width + предельные значения (до 1648px на широком экране)), full = Весь экран. std Это наследие name из medium, принятый |
head | 0 · 1 | 1 | окно header (product name + close На всех - на всех sizes, включая full. head=0 Удалить его, крест становится плавающим |
foot | 0 · 1 | 0 | окно footer. Только в ней держат ←/→ стрелки, счетчик «3/12» и ↗ link в product page |
pad | целое число 0…96 | 16 под header, 0 без него | тот WINDOW's top внутренний padding выше card, х. pad=0 — тот author's Разметка начинается прямо у края окна |
close | dark · light | dark | Тон плавающего креста: light — Белый круг для dark Надпись: Рама не имеет других цветов |
cls | class name ^[a-z][a-z0-9_-]{0,63}$ | none | CSS крючок THIS Окно: кадр получает class vz-qv-<name>, и глобальный CSS нацелены именно на |
tpl | id из одного product group из body slot | none | Нарисуйте окно body с этим group; Без него обычная product page design лестница должна быть |
nav | ids:42,43 · ns:<ns> · cat:<id> | none | где соседний products для paging изгонять |
v | variant sku | none | тот product's начинать variant |
Платформа записывает в строку только то, что DIFFERS от default, такой address Он строит это always короткий: modal:product:42 и modal:product:42?size=medium одинаковые окно.
окно CSS Крючок: Ваш CSS на раме
Окно FRAME Жить в одном и том же page's document, так layout's глобальный CSS (и level 3) достигает его. ⁇ ️ Он достигает CONTENT только для modal:group:. С modal:product: Рамка содержит отдельный document (iframe): rule как .vz-qv--small .my-card {…} never Попасть туда — Restyle card с его own @media (Вид на окно (Frame Viewport) - окно width). Классы фреймов:
vz-qv — any window
vz-qv--small|medium|full — window size
vz-qv--product|group — what is inside
vz-qv--head, vz-qv--foot — has a header / has a footer
vz-qv-<name> — your own name from ?cls=<name><a href="/catalog/chair" vz-modal="modal:product:42?cls=promo&pad=0">Кресло</a>
<style>.vz-qv-promo { --frame: #111; }</style>Тот же самый values дублируется как attributes data-vz-qv-size и data-vz-qv-kind.
Что? product Окна отсутствуют — предоставьте их сами. Над page а block получать side отрыв от указанного wrapper (.vz-inner); Окно не имеет wrapper — а body template переводится как Он будет сидеть на краю. head=0 Крест плавает ON TOP из content; Нет безопасного zone Для этого — Leave ~48 х free в top Правильно. sticky Купить бар в кадре оставаться sticky. The content не получает a "I «Я в окне» сигнал сегодня (известный) gap, Отслежено как отдельная задача).
⚠️ Две формы окон работают по-разному, и ваша `@media` зависеть от него.
modal:product:<id>— тот content входит в состав каркас с ITS OWN видоискатель Широкий, как рама. card's Медиа-запросы внутри такого окна работают как на узкий экран:size=small(480 px) действительно включает одноколонку layout написанный для@media (max-width: 1000px).modal:group:<id>— тот content нарисован В том же порядке, в том же document, и Видоискатель остается page Видопорт. Рамка меняет width, Не экран, так что@mediaвнутри не замолкайте: на широком мониторе card В окне 480 px остается двухколоночный.
Во втором случае восстанавливается layout По классам кадров (.vz-qv--small .my-card { grid-template-columns: 1fr }) — Именно это и является Они для - или с container Запросы о вашем own container: Окно не создает Платформа's own query контейнеры (/docs/own-markup §7.2).
⚠️ Нет никаких ключей от компаньонов `vz-modal-size`, `vz-modal-tpl`, `vz-modal-nav` в твоем own разметка. The key белый список (GET /docs/vz-keys.json) имеет только vz-quickview и vz-modal Для окна; для окна level-1 санитар полоски компаньоны молча. Так что единственный full path для author Наценка - это modal: струна.
Что происходит в окне
- окно body Это product page's
bodyslot, собранный путем THE SAME design Лестница как бы page само собой (tplЗаменить их на group). The SITE's header, menu и footer не в окне, а в page фон только затемняется; - тот
breadcrumbswidget это hidden в окне мимо default — breadcrumbs есть page Навигация, не card Чтобы вернуть их на определённый маршрут. section:props.showInModal: true; - Нажмите на открыватель INSIDE Окно меняет product В том же окне — нет второго окна открывается, история не растет. new target Молчит о (
size,head,foot,pad,close,nav) Наследуется от открытого Окно, пока все set в ней явно — включая value равным ему по default (?size=medium&head=1&foot=0написанный внутри full Окно) — удары наследство;tpl,vи реальный product address не наследуется; - настольная клавиатура: ←/→ — соседняя product (через
nav), ↑/↓ — галерея image, Эск — close. Вступительный fields под открытым светофором или form Ключи отправляются к ним. phone, скользить боком pages через products, спускаться вниз Закрывает окно; - оконная рама предварительно загружена на вибрацию над открывателем — окно открывается без белая рама.
Готовый внутренний content: а design group
Модаль content не передается как HTML вводимые в строй и не загруженные произвольный public GET group by id. Он собирается из standard blocks, объединенный в group, и button относится к group:
<button type="button" vz-modal="modal:group:1727?size=small">
Подробнее об акции
</button>
<a href="modal:group:1727?size=full">Открыть презентацию</a>Как создать content с API просьбы:
# 1. Create one or more ordinary widget blocks (§5).
POST /content-blocks
{ "item": { "name": "Тело акции", "sections": [ ... ] } }
# → result.id = 1726
# 2. Put them into a design group. The order of widgets = the order inside the window.
POST /content-blocks
{
"item": {
"name": "Модалка акции",
"group_type": "design",
"sections": [{
"type": "text",
"payload": { "v": 2, "kind": "group", "props": { "widgets": [1726] } }
}]
}
}
# → result.id = "1727" (int64 in JSON may be a string)Если внутри всецело ваше own, делать block 1726 kind:"html" в соответствии с §2–§5 и style тот HTML/CSS Внутри него. title, наложение, closing, фокус и отзывчивый лист принадлежат платформе; group's content Это твой. Margins/radius индивидуальный blocks внутри них set с помощью standard wrapper props (§17, не с CSS Селекторы родительского окна.
Объявить зависимость в HTML block payload
Бэкэнд служит только groups На который ссылается уже public page. Для одного HTML document тот target должны быть определены в структурированном виде рядом с document — Это Не второй раз, а список серверов-side зависимостей:
{
"type": "text",
"payload": {
"v": 2,
"kind": "html",
"props": {
"html": {
"html_document_id": 31,
"modalTargets": [{ "type": "group", "id": 1727 }]
}
}
}
}Это является обязательным для modal:group из HTML, особенно level=2: его source Жизнь как file И может быть, он будет id в script, Сервер не догадывается о target из тот text. А. product быстро view не нуждается modalTargets — а product имеет свой own public Не более 20 groups 100 из них – widgets материализуется в page; Иностранец, сломанный или feed group это skipped.
В этом full page ответ (GET /products/{id}, /categories/by-slug/..., /articles/by-slug/...) готовый content Приходит как брат:
"modal_groups": [{
"id": "1727",
"name": "Модалка акции",
"blocks": [{ "id": "1726", "sections": [ ... ] }]
}]Запрашивая group Отдельно от браузера не требуется и не допускается: витрина магазина уже регистры modal_groups в page's единый модальный хост.
Уровень 2: та же разметка или программный вызов
В одном level=2 sandbox Обычный DOM Click не впуклится в page. Сама платформа Ввод безопасного моста для [vz-quickview], [vz-modal] и a[href^="modal:"], Кнопки, показанные выше, работают без вашего JS. Если target вычисляется after Запрос к тому, public Backend, отправьте то же самое Каноническая струна от руки:
parent.postMessage({
type: 'vizen-content-modal-open',
href: 'modal:group:1727?size=medium'
}, '*');
// Product + a real fallback URL:
parent.postMessage({
type: 'vizen-content-modal-open',
href: 'modal:product:42?size=full',
resourceHref: '/catalog/chair'
}, '*');The message проходит через тот же строгий парсер, что и href: ан unknown kind, ноль id, а size снаружи small|medium|full, контрольные символы и чрезмерно длинные струны Не ставьте на место PAT/admin Token Into Browser HTML: Агент или ваш сервер создавать blocks и groups В ней посетитель читает только public page Ответ: Ан. unknown безопасный query параметром является ignored для совместимости.
Платформа Lightbox открывается с sandbox Точно так же, с его own message vizen-lightbox-open — fields и примеры: /docs/modules §2.
15. PAGE DESIGN: видел block → Найденный ею source → изменила
Это section Ответы на вопросы, которые раньше решались с помощью догадок: "I смотреть header с а menu на site — Что это такое и где делать I Редактировать". order следующим образом.
15.1 Прочитайте этикетки page
В каждом магазине block Наносится в маркировке:
| атрибут | Что это значит |
|---|---|
data-vz-kind | widget kind (siteHeader, siteMenu, productListing, …) |
data-vz-block | id из design block тот block из которого |
data-vz-kit | id из chrome KIT — вместо того, чтобы data-vz-block на header/menu/footer |
data-vz-idx | тот section's position внутри block (или комплект) |
⚠️ Chrome не имеет source block number. Витрина магазина уже получает комплект собранных и header, menu и footer blocks сами не были названы в ответе — Это то, что why data-vz-kit Чтобы найти редактируемый block Часть, прочитайте комплект block: его kind: "chromeKit" section держать props.kit как {"top": [2084, 2085], "bottom": [2086]} — Это те, которые header, menu и footer blocks через zone, в output order.
curl -s https://<shop>.vizen.shop/<page> | grep -o 'data-vz-[a-z]*="[^"]*"'Элементы INSIDE а widget маркируются отдельно — data-el (логотип, навигация, иконы) и data-edit (text С ними вы можете быть в состоянии address редактировать more Именно так, как "где-то в header".
15.2 Попросите widget's свойства
curl -s https://<shop>.vizen.shop/api/widgetsThe PLATFORM ссылка (не требуется токен): для каждого kind — ан RU/EN label, Если полоса это chrome или page block, и full Список свойств с по умолчанию. Не переписывается от руки: они приходят из widget's own ПАРТЕР, так что список cannot Отличие от того, что понимает магазин.
15.3 Возьми block и редактировать необходимое section
# 1) the whole block (catalog:read right)
curl -s -H "Authorization: Bearer $VZ" $API/content-blocks
# 2) the edit: sections are a replace-set, so send the WHOLE list,
# changing section No. data-vz-idx in it.
# The method is PUT (not PATCH): partial update here is expressed by the item field.
curl -s -X PUT -H "Authorization: Bearer $VZ" -H 'Content-Type: application/json' \
$API/content-blocks/<data-vz-block> -d '{ "item": { "sections": [ … ] } }'⚠️ sections это заменить-set, Не частичное редактирование: отправьте section и Остальные исчезают. Всегда читайте block, изменить необходимый элемент списка, отправить Весь список.
⚠️ The catalog:write правильно required. The live site Редактируется с помощью a prod contour key; а dev key Вписывается в draft, Человек, который издает (The Human Publishing) publish:write право предоставляется отдельно, с одной checkbox когда выдается токен).
15.4 Проверь свои own глаза
Откройте то же самое page Убедитесь, что изменено block's data-vz-block То же самое и content это new. Если block Исчезла, почти always заменить-set от 15.3.
16. PAGE LAYERS: Ваша наценка вместо кого-то else's, не в top из него
А. page Он состоит из трех частей, и каждая из них имеет свой собственный вид. off а WHOLE LAYER. Отдельных переключателей не существует widget (Например, "спрятаться" breadcrumbs") И будет существовать none: breadcrumbs являются обычными widget; Они могут сидеть в любом layer исчезнуть вместе с их стороны layer.
| слой | Что находится внутри | Флаг |
|---|---|---|
| планировка, top | header, menu и все остальное owner Добавлено в top zone | hideSiteHeader |
| планировка, bottom | footer И все это в дополнение к bottom zone | hideSiteFooter |
| Технический | Все, что приходит с product или section: card, product список, breadcrumbs, page title | hideSystemBlock |
| Твой blocks | Что вы сами себе положили | Нет флага — просто удалите то, что вам не нужно |
Флаги live в kind:"zone" section из block названный __page:top.
16.1 Сценарий А — уникальная посадка (ONE page)
Создать empty page переключаться off Все три слоя на нем. Тогда ваша разметка занимает Вся эта page И ничего с платформы не показывает через него..
# 1) read the page's zone block (its id is in data-vz-block, or look for the name __page:top)
curl -s -H "Authorization: Bearer $VZ" $API/content-blocks
# 2) switch the layers off — sections stays a replace-set, send the WHOLE list
curl -s -X PUT -H "Authorization: Bearer $VZ" -H 'Content-Type: application/json' \
$API/content-blocks/<id> -d '{"page_design_version":1,"item":{"sections":[
{"type":"text","payload":{"v":2,"kind":"zone","refs":[<ids of your blocks>],
"page":{"hideSiteHeader":true,"hideSiteFooter":true,"hideSystemBlock":true}}}
]}}'Ничего не существует. deleted В этом случае: снимите флаг и layer Возвращается, как было.
16.2 Сценарий В — уникальный HEADER (Вся эта site)
⚠️ Распространенная и дорогостоящая ошибка: выкладывание вашего own header - Внутрь посадки. появляется на ONE page, Пока все остальные keep Старый — The Old One site В итоге получается два разных заголовка.
The header Жизнь в layout, в top zone (тот footer в bottom). Разделяемые части между pages, Так что редактирование распространяется по всему shop сразу же. chrome Кит - это kind:"chromeKit" section, props.kit = {top:[id…], bottom:[id…]}; на page Его части маркированы data-vz-kit.
Выбор rule: один page → переключатель off накладки на page; Вся эта site → редактировать layout.
⚠️ Слово "layout" в этом section относится к chrome ZONES (тот chromeKit комплект, старый-model С 2026-09-12 годов платформа имеет SITE LAYOUT — а group_type:"theme" group с его own папка files и code (§2b). Заголовок и footer В ней он избирается вместе с header/footer slots, и site's делиться CSS/JS/fonts Зайдите в его папку, а не внутрь header's html widget: на live site тот CSS из одного level-3 header относится ко всему page, но editor рамка загружает только layout's <head>, тот layout соединенный before тот first рамка и а header удаленный из а section Берет свои стили с собой.
17. BLOCK WRAPPER: width, маржа, radius — Также через Token
Платформа привлекает всех page block внутри standard wrapper, и это сконфигурирован путем THE SAME section как widget Ключи лежат рядом с собой. kind, не внутри widget's writePath:
{ "kind": "html",
"props": {
"blockWidth": "full",
"paddingSides": { "top": 0, "right": 0, "bottom": 0, "left": 0 },
"blockRadius": 0,
"html": { "html_document_id": 13 } } }⚠️ Топоры live в ROOT из props — Рядом с ним widget's гнездо (html, listing, …), Не рядом с kind. Ось, расположенная на section хранится и ничего не вытягивает: POST /docs/validate Ответить на него с ignored и подсказка "Его место - это props.<axis>". Проверьте это, а не глазами.
The full Список топоров с типами и по умолчанию — GET /docs/widgets, section wrapper. Числа есть TAKEN Оттуда не помнят: каждая ось и каждый default из wrapper сами по себе несут anchor.at В ответ — The Storefront file:line это sets тот value. Ссылка перестроена из code На каждой постройке.
curl -s https://api.vizen.shop/docs/widgets | jq -r '
.wrapper.axes[] | [.path, .type, (.default // "-"), .anchor.at] | @tsv'Также есть wrapper.ownDefaults: которые имеют свои OWN дефолт.
The HTML block Именно такой kind. Его платформа padding и radius есть 0: Наценка author контролирует look. Ранее 24/12 Появился оркестр вокруг посадки и углы одного из них full-width Полоса была округлена, хотя block data содержащийся в Ничего, кроме width. Нужен воздух — set paddingSides явно; он выигрывает.
Ответственность: props.tablet и props.mobile с одинаковыми ключами (props.mobile.paddingSides); незавершенный layer принимает value более широкой из них.
⚠️ sections является заменой-set: читать block, Измените нужный элемент, отправьте Весь список.
17.0.Твоя own class на wrapper: Как сделать a block В отличие от платформы
The wrapper это TWO Каждый со своим own дефолт: div.vz-box (Полоса: фон, границы, padding, radius, липкость — и это клипсы NOTHING через default) → div.vz-inner (тот container: width Кепка в site колонка и центрирование. А level-1 html block Внутри есть третий — Тень. DOM ведущий div.vz-radius, и IT клипы default: a выпадающий или модальный author разметка обрезается на block дождевик до props.overflow: "visible" это set. Переведенный level-3 папки без props.html.boxed иметь none из трех nodes: Корневой элемент – это block; тот wrapper Нечего сидеть на.
Два способа сделать его уникальным: выбрать BEFORE выкладывание:
| Что тебе нужно | Путь |
|---|---|
| перераспределить весь block — твой own Сетка, твоя own точки останова | `props.wrapperClass` — твой own class На полосе, потом твоя own CSS |
| переключатель off ONE поведение (клиппинг), width кап, воздух, layer) | направленная ось — props.overflow, props.blockWidth, props.paddingSides, props.position … |
{ "kind": "html",
"props": {
"wrapperClass": "promo-hero grid-2", // up to 3 names separated by spaces
"wrapperId": "prices", // one name: #anchor and your own scripts
"html": { "html_document_id": 13 } } }В разметке Ваш own Имена приходят LAST; Платформа's оставаться на месте:
<div class="vz-box @container vz-edge promo-hero grid-2" id="prices">
<div class="vz-inner">…</div>
</div>.promo-hero { padding: 0; background: #0b0b12 }
.promo-hero > .vz-inner { max-width: none; display: grid;
grid-template-columns: 1fr 1fr; gap: 48px }Имена проверяются WHOLE и сбрасывали целые: до 3 классов, один id, ^[a-zA-Z_][a-zA-Z0-9_-]*$, до 48 символов, и none Может начаться с vz- — Это и есть платформа's пространство (vz-edge Расширяя, block прошлое blockWidth, vz-vis-none Это полностью скрыло бы его. error в name ты стоишь тот class Но не нарушая page.
Работа для chrome слишком. siteHeader, siteMenu, siteFooter Нарисуйте полоску Читайте сами, но читайте то же самое wrapperClass/wrapperId: Вы можете перекрасить и перераспределить платформу header Не заменяя его на свой own Наценка и без потерь live поиск и cart контраргумент.
CSS для наружного wrapper должен live Вне местной тени DOM. Author имена есть free; тот wrapperClass limits Вышеприведенное относится только к API field. Стабильные имена не гарантируют, что глобальный переопределение является безопасным.. Рецепт и единственный CSS матрица — /docs/own-markup §§5.5,7.2.
17.0.1 Слипкость
props.position: "sticky" (+ props.stickyTop в пк, если что-то sticky уже есть на top) делать THE STRIP ITSELF sticky — Единственный узел, который имеет место для move. Твой own Маркировка требует того же с vz-sticky key на document's узел корневых узлов.
Где работает: любой block из одного page zone, любой block из одного layout zone (а chrome комплект Именно то, что "own header" чехол), вложенная колонна blocks. Где это происходит NOT работа: Платформа's siteHeader/siteMenu/siteFooter — тот wrapper Не рисовать свою полосу В общем, то header имеет свой own устанавливать props.sticky: true для этого; vz-sticky внутри columns (Есть только props.position с ним консультируются; editor холст (The Canvas) preview не Посоветуйтесь с ним, live site делать).
За липкость, проверь clipping/overflow От предков. fixed Не оставляет места в потоке, и его крепление должно быть проверено по матрице /docs/own-markup §7.2. Не заменяйте его на sticky Не проверяя поведение прокрутки.
17.1 Изображения в author разметка
Написать plain <img src> указывающий на shop file — Сам сервер меняет его на сетчатка и добавляет srcset (640/1024/1600, Webp. Оригинал never достигать разметка.
Но set `sizes` себя. Только author знает slot width; без намека Сервер ставит 100vw, И браузер принимает LARGEST На ступеньку, на сетку cards 268px в ширину это означало 1600w вместо 640w — шесть extra мегабайт:
<img src="/files/…/item.png"
sizes="(min-width: 1228px) 280px, (min-width: 900px) 32vw, 47vw">Сервер не прикасается к вашему own sizes. И не ваш own srcset: Нет замены Это происходит, когда отзывчивость полностью ваша..