Vizen Shop

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 и pagesGET /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/newsGET /articles?company_id={id}&page.number=1&page.limit=10 (⚠️ page.number это required, ≥ 1)
Твой html документыGET /html-documents
Что уже есть на pageGET /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_idproducts из одного section (витрина магазина's main filter)
filter.idsнесколько products через id сразу
filter.queryПоиск по name
filter.is_publishedопубликовано только
filter.min_price / filter.max_priceprice range
page.number / page.limitpages; 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Что дает поколение
resultname, 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_infoattributes, рейтинг, 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 переписывать.

  1. Виджет = папка. index.html В корневой + styles.css + app.js + assets/… Все пути относительны, все пути относительны. — из корня папки. Над тот site тот files Они служат для widget корень /_html/{doc}/{release}/… с тем же самым гнездо; site page не принимает участия в обращении., предварительный просмотр, README) входит в .vizenignore.
  2. `index.html` это plain разметка: разметка + <link>/<style>/<script>, без <!doctype>/<html>/<head>/<body>. У меня есть целое page — Без проблем, платформа снимает wrapper Сама по себе публикация.
  3. А. 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 или defer script больше не имеет document.currentScript — использовать document.querySelector('.vz-body-<id>')?.dataset.vzBase или window.VZ_ASSETS[<id>], где <id> Это document id от API ответ.
  4. локально Открыть через npx serve . (не file://). Это показывает Скриншоты из игры - Without the site header и footer, без своего каскада и без vz- Замены; это exact preview Публикация с a dev key (<slug>--dev). Что меняется в site — Подраздел "Каскад и время выполнения на береговой линии" ниже.
  5. передача: 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.
  6. Демо template = Та же папка. new page от template — копировать папку и --page another-slug; а version — a release; a edit (отправить) — file + Перезапустить команду.
  7. Проверить after передача (обязательное: открыть url Из ответа в браузере (безголовый считается браузером), возьмите рабочий стол + mobile скриншоты, look В консоли: нет JS Ошибки, нет 404 files; compare с местный version. Несоответствие — исправьте папку и повторите шаг 5.
  8. Не надо.: использовать absolute URL вместо относительных, вырезать page вводить blocks, использовать iframe/shadow "для изоляции", загрузить files Один за другим через хранение API, поставьте второй .html page В освобождение.

Рабочий процесс чата: читайте 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> и в .css files) Переписывается для .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 в освобождение.

Правила папки (проверяется валидатором на публикацию))

  1. index.html В корне это - widget's только page; другой .html files в Освобождение - предупреждение page.extra (тот gateway не служит HTML).
  2. Все пути относительны, от корня папки и внутри него.. ../something прошлое корень — error ref.outside; а file Которого не существует — ref.missing. Струна file пути в .js без window.VZ_ASSET_BASE — предупреждение js.assets (на site такой path Решения против page URL, Не то widget Если вы читаете корень через dataset.vzBase, keep window.VZ_ASSET_BASE как fallback И нет никакого предостережения..
  3. <head> не требуется: на публикации остаются только привязки (link/style/script/noscript), title/meta description Войди в себя site page's SEO, остальное (<base>, og:*, http-equiv, manifest) Упал (предупреждение) head.stripped).
  4. Внешние ресурсы (шрифты Google), CDN) — https:// только; http:// — error ref.http. Лучше вставить шрифты в папку.
  5. Готовься images при необходимости size себя: /w/… изменение размера не применяется проектировать files (Они подаются байт за байт. Рекомендовано ≤ 400 KB и width/height на <img> — иначе warnings img.large / img.nosize.
  6. Пределы: ≤ 500 files, HTML file ≤ 2 MB, любой file ≤ 50 MB.
  7. Не допускается: исполняемые сервера (php/py/sh/exe…) — file.forbidden; navigator.serviceWorker.register — error sw.register (работник в Корень перехватит весь shop, включая payment).
  8. Ссылки на The Links site — ан absolute path (/catalog/x) или vz:page/<id>; forms — href="form:<id>" (§13). А. <form> Без обработчика — предупреждение form.nohandler.
  9. Не кладите в папку: макеты, previews*/, originals/, README, qa.mjs — использовать .vizenignore В корневой (.gitignore синтаксис); hidden files (.DS_Store, .git/) есть skipped молча.
  10. Один проект = один 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-ui 16x; ваш 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:navigate event на 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 only

ZIP без 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/releasesdraft освобождение от 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:

  1. кэш public, max-age=300 + ETag Вместо неизменного (публикация должна быть обязательной) Достучаться до клиента на его own);
  2. арендатор header это required — адресация начинается с хоста; без него тот "active layout" это undefined: прямой API Запрос 404;
  3. освобождение 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 с code html.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: Сплюснутый, он приходит как manifest field на 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 выше.

Правила изображения:

  1. The URL исходит из API ответ (result.url / file.url); never придумывать.
  2. `src` Удерживает родственник path (/files/sunset/view/content/…): витрина магазина служить files от своего own домен.
  3. изменение размера — тот prefix /w/{width} или /w/{width}/webp before тот 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 не имеют размера — вставьте их как есть.
  4. повторное использование: уже загруженный file (включая product photo Из каталога) В него вставляется его URL — Не загружайте его снова. Тот же самый file_id может быть зарегистрирована в нескольких документах.
  5. Зарегистрируйтесь 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 х10241024 1x, 1600 2x
Изображение в a text колонка~600-800 px640640 1x, 1024 2x
Карта в сетке 3-4 колонки~250-300 px320320 1x, 640 2x
Маленькая плитка, логотип, icon40-80 px6464 1x, 128 2x
Обзор аватара48 х6464 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ценности
блокировать widthblockWidth"content" (default, site колонка) / "full" (к краю экрана)
Контент width (видимым с помощью full)contentWidth"content" (Вернуться в колонку) / "full"
маржа ↑ / ↓marginTop / marginBottomnumber, х
Побочный пределmarginXnumber, px (нет = 0); S/M/L — панель presets)
угол radiusblockRadiusnumber, 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 srcset YOURSELF — Мы предполагаем, что отзывчивость обрабатывается.

Установить 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

  1. Наружный margins/width через props Если щит должен контролировать их. внешний CSS нуждается в layout или level 3. местная тень CSS Не дойдет туда.
  2. Проверить text удобочитаемость вблизи краев: сетка платформы или ваша own padding это design выбор.
  3. Мы не рекомендуем непреднамеренный горизонтальный прокрутка на 360/768/1920 Для карусели, проверьте, что это container что такое свитки.
  4. Проверь. block в одиночку и с соседями: накладки, поля и меню должны быть keep предназначенный look когда text когда меняется сосед block это hidden.
  5. Разделить page вводить sections если owner хочет перестроить или повторно использовать Части, целое page как один document Также допускается.
  6. Используйте платформенную сетку для look согласуясь с тем, что template; твой own columns и Точки останова — для вашего own system. Не смешивайте их случайно..
  7. Проверить 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-padfull width + тот page's side padding: padding-inline: var(--vz-gutter)
--vz-content-boxsite колонка width. ≈1536 х только для одного full-width block на большом экране, в других контекстах value это RELATIVE (block бокс минус маржа) — не зависит от number, Используйте переменную
--vz-gutterpage 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 символов, которые невозможно прочитать
BblockWidth:"full" + contentWidth:"content"фон к краям экрана, content на site сеткаbackground/gradient на корневой элемент; text и cards внутри <div class="vz-inner">. Наиболее распространенный mode для героев
CblockWidth:"full" + contentWidth:"full"full width, нет limitslimit сам по себе: .wrap{max-width:1536px;margin-inline:auto;padding:0 32px} (взять number из layout.content_box_max_px)
DblockWidth:"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 или твой own padding).
  • Вертикальные разрывы между 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а).
  1. Что полоскает сервер (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).
  2. Изоляция (level 1): местный CSS Селектор не покидает тень DOM. CSS для наружного wrapper Должно быть, снаружи. layout/shadow/editor и каскадная матрица — /docs/own-markup §7.2; author Классы - §5.5.
  1. Наследование: шрифт, text цвет и theme CSS переменные попадают внутрь — Не перезагружайте их без необходимости (all: initial Только если вам нужна независимая look).
  2. широта — См. §5, разметка rule.
  3. Ответственность — медиа-запросы и max-width: 100% для images; тот page должен Работать по 360 px.
  4. якоря (level 1): #anchor внутри block Не прокручивать page (Тень DOM граница) — не строить навигацию на якорях внутри одного document. Над level 3 Якорные работы.
  5. Размер: keep тот document под ~256 KB; тяжелая limit 2. MB (над ним сервер не будет служить ему, и block Отклоняется к iframe без SEO).
  6. Храните ключи других: при редактировании существующего block, не drop unknown payload fields — могут принадлежать другим 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 конечная точка403PAT_SCOPE_MISSING (required_scope: …)
Конечная точка не открыта для токенов вообще.403PAT_METHOD_NOT_ALLOWED
Нет. catalog:read, Конечная точка является PUBLIC200есть 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 block400DOCUMENT_IN_USE: used by N content blocks
Удаление a file используемый a document400FILE_IN_USE
Page/category slug взятый409AlreadyExists … SLUG_TAKEN (проверить by-slug заранее)
page.number не перешел в /articles400 (нумерация от 1; optional для /products)
Изображение 10 MB400FILE_TOO_LARGE на /done (сжимать before загрузка)
Слишком много запросов429RATE_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 Что должен содержать окончательный доклад

  1. Что было? created — В качестве списка, с человеческими именами (не ids).
  2. Где? look — full URL + a draft note.
  3. Что? owner следует нажимать — бетон steps до публикации.
  4. Что случилось? NOT работа — Честно говоря, с reason. Если конечная точка отвечает PAT_METHOD_NOT_ALLOWED (Например, витрина settings, логотип, currency — Они закрыты для токенов, так сказать: "не доступны через токены". API, делать в admin "Не замолкайте и не проходите частичного result off как полный.
  5. Что останется на потом — Если работа разделена на этапы.

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/sectionhttps://{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>
Параметрценностипо умолчаниюЧто он делает
sizesmall · medium · fullmediumsmall = 480px, medium = site content width + предельные значения (до 1648px на широком экране)), full = Весь экран. std Это наследие name из medium, принятый
head0 · 11окно header (product name + close На всех - на всех sizes, включая full. head=0 Удалить его, крест становится плавающим
foot0 · 10окно footer. Только в ней держат ←/→ стрелки, счетчик «3/12» и ↗ link в product page
padцелое число 0…9616 под header, 0 без неготот WINDOW's top внутренний padding выше card, х. pad=0 — тот author's Разметка начинается прямо у края окна
closedark · lightdarkТон плавающего креста: light — Белый круг для dark Надпись: Рама не имеет других цветов
clsclass name ^[a-z][a-z0-9_-]{0,63}$noneCSS крючок THIS Окно: кадр получает class vz-qv-<name>, и глобальный CSS нацелены именно на
tplid из одного product group из body slotnoneНарисуйте окно body с этим group; Без него обычная product page design лестница должна быть
navids:42,43 · ns:<ns> · cat:<id>noneгде соседний products для paging изгонять
vvariant skunoneтот 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 body slot, собранный путем THE SAME design Лестница как бы page само собой (tpl Заменить их на group). The SITE's header, menu и footer не в окне, а в page фон только затемняется;
  • тот breadcrumbs widget это 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-kindwidget kind (siteHeader, siteMenu, productListing, …)
data-vz-blockid из design block тот block из которого
data-vz-kitid из 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/widgets

The 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.

слойЧто находится внутриФлаг
планировка, topheader, menu и все остальное owner Добавлено в top zonehideSiteHeader
планировка, bottomfooter И все это в дополнение к bottom zonehideSiteFooter
ТехническийВсе, что приходит с product или section: card, product список, breadcrumbs, page titlehideSystemBlock
Твой 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: Нет замены Это происходит, когда отзывчивость полностью ваша..

Исходник: https://api.vizen.shop/docs/webcoding?full=1

Работает в демо-режиме