feat: add photos for the 34 training-catalog items that had none

The banquet menu (18 dishes) and extras/supplies/delivery items (16)
in the training catalog were seeded with photo:'', which the catalog
tiles render as a plain red placeholder square. Sourced a real photo
for each item (mostly Pexels, free license, picked and visually
reviewed one by one to avoid mismatches -- several first search hits
were wrong: a live chicken for "chicken caesar salad", a person in
frame, branded bottles/cups) and two AI-generated (banquet-caprese,
banquet-roastbeef). Converted to square 1024x1024 WebP with sharp.

Wires the paths through the actual source of truth: ops/demo/trial-
banquet-data.mjs and trial-extras-data.mjs now set photo to the real
path, then ops/demo/build-trial-*.mjs regenerated public/demo/*.json
and the matching Supabase seed-function migrations, so the client's
static JSON fetch and the server-side company-provisioning function
stay in sync.

Bumped the demo-catalog fetch's own cache-busting query string in
training-catalog.js (and its script-tag/precache version), since it's
fetched by the client with a separate version from the outer script
tags -- the same stale-cache class of bug fixed earlier this session.

Verified: all 34 referenced files resolve (200) and decode as real
1024x1024 images; confirmed via the actual rendered catalog tiles
(#tiles img[src]) that the extras category -- where the user's
red-square screenshot was taken -- now serves the real photo paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
pavlov346346-source 2026-09-22 17:00:45 +03:00
parent 72d8ca6c1a
commit 40b5c146c2
44 changed files with 311 additions and 277 deletions

View File

@ -0,0 +1,39 @@
{
"note": "Photos for the 34 training-catalog items that previously had none (banquet-v1 dishes, extras-v1 sets/supplies/delivery). Sourced from Pexels (free license, no attribution required) by search + manual visual review; two items (banquet-caprese, banquet-roastbeef) were AI-generated instead. Files live in public/demo/images/<file>.webp, 1024x1024, resized/converted with sharp. Regenerate the catalog JSON with ops/demo/build-trial-banquet.mjs and ops/demo/build-trial-extras.mjs after editing the *-data.mjs sources.",
"items": [
{"itemId": "demo-banquet-v1-caprese", "file": "banquet-caprese", "source": "ai-generated"},
{"itemId": "demo-banquet-v1-roastbeef", "file": "banquet-roastbeef", "source": "ai-generated"},
{"itemId": "demo-banquet-v1-salmon-roll", "file": "banquet-salmon-roll", "source": "pexels"},
{"itemId": "demo-banquet-v1-hummus", "file": "banquet-hummus", "source": "pexels"},
{"itemId": "demo-banquet-v1-caesar", "file": "banquet-caesar", "source": "pexels"},
{"itemId": "demo-banquet-v1-olivier", "file": "banquet-olivier", "source": "pexels"},
{"itemId": "demo-banquet-v1-greek", "file": "banquet-greek", "source": "pexels"},
{"itemId": "demo-banquet-v1-julienne", "file": "banquet-julienne", "source": "pexels"},
{"itemId": "demo-banquet-v1-stuffed-mushrooms", "file": "banquet-stuffed-mushrooms", "source": "pexels"},
{"itemId": "demo-banquet-v1-chicken", "file": "banquet-chicken", "source": "pexels"},
{"itemId": "demo-banquet-v1-cod", "file": "banquet-cod", "source": "pexels"},
{"itemId": "demo-banquet-v1-beef-hot", "file": "banquet-beef-hot", "source": "pexels"},
{"itemId": "demo-banquet-v1-mash", "file": "banquet-mash", "source": "pexels"},
{"itemId": "demo-banquet-v1-rice", "file": "banquet-rice", "source": "pexels"},
{"itemId": "demo-banquet-v1-berry-cream", "file": "banquet-berry-cream", "source": "pexels"},
{"itemId": "demo-banquet-v1-cheese-honey", "file": "banquet-cheese-honey", "source": "pexels"},
{"itemId": "demo-banquet-v1-fruit", "file": "banquet-fruit", "source": "pexels"},
{"itemId": "demo-banquet-v1-bread", "file": "banquet-bread", "source": "pexels"},
{"itemId": "demo-extras-v1-salmon-caprese", "file": "extras-salmon-caprese", "source": "pexels"},
{"itemId": "demo-extras-v1-meat-cheese", "file": "extras-meat-cheese", "source": "pexels"},
{"itemId": "demo-extras-v1-mini-buffet", "file": "extras-mini-buffet", "source": "pexels"},
{"itemId": "demo-extras-v1-water", "file": "extras-water", "source": "pexels"},
{"itemId": "demo-extras-v1-sparkling", "file": "extras-sparkling", "source": "pexels"},
{"itemId": "demo-extras-v1-juice", "file": "extras-juice", "source": "pexels"},
{"itemId": "demo-extras-v1-mors", "file": "extras-mors", "source": "pexels"},
{"itemId": "demo-extras-v1-plate", "file": "extras-plate", "source": "pexels"},
{"itemId": "demo-extras-v1-fork", "file": "extras-fork", "source": "pexels"},
{"itemId": "demo-extras-v1-glass", "file": "extras-glass", "source": "pexels"},
{"itemId": "demo-extras-v1-napkin", "file": "extras-napkin", "source": "pexels"},
{"itemId": "demo-extras-v1-ice", "file": "extras-ice", "source": "pexels"},
{"itemId": "demo-extras-v1-tablecloth", "file": "extras-tablecloth", "source": "pexels"},
{"itemId": "demo-extras-v1-serving-kit", "file": "extras-serving-kit", "source": "pexels"},
{"itemId": "demo-extras-v1-delivery-city", "file": "extras-delivery-city", "source": "pexels"},
{"itemId": "demo-extras-v1-delivery-outer", "file": "extras-delivery-outer", "source": "pexels"}
]
}

View File

@ -68,7 +68,7 @@ const recipes=[
export const banquet=recipes.map(([key,name,catalogSection,price,quantities,allergens,steps,waterGrams=0],index)=>{ export const banquet=recipes.map(([key,name,catalogSection,price,quantities,allergens,steps,waterGrams=0],index)=>{
const rows=quantities.map(([key,gross,net])=>{const p=products.get(key);return {productId:p.id,name:p.name,unit:p.unit,gross:gross/1000,net:net/1000,unitCost:p.cost}}); const rows=quantities.map(([key,gross,net])=>{const p=products.get(key);return {productId:p.id,name:p.name,unit:p.unit,gross:gross/1000,net:net/1000,unitCost:p.cost}});
const outputGrams=Math.round(rows.reduce((sum,r)=>sum+r.net*1000,waterGrams)); const outputGrams=Math.round(rows.reduce((sum,r)=>sum+r.net*1000,waterGrams));
return {id:`demo-banquet-v1-${key}`,name,category:6,catalogSection,price,weight:`${outputGrams} г`,pieces:1,photo:'',demo:true, return {id:`demo-banquet-v1-${key}`,name,category:6,catalogSection,price,weight:`${outputGrams} г`,pieces:1,photo:`demo/images/banquet-${key}.webp`,demo:true,
composition:[rows.map(r=>r.name).join(', ')],ingredients:rows.map(r=>[r.name,r.gross,r.unit]), composition:[rows.map(r=>r.name).join(', ')],ingredients:rows.map(r=>[r.name,r.gross,r.unit]),
ttk:{number:`ДЕМО-Б${String(index+1).padStart(2,'0')}`,basis:'На 1 порцию / 1 гостя',outputGrams,waterGrams,pieces:1,rows,steps,allergens, ttk:{number:`ДЕМО-Б${String(index+1).padStart(2,'0')}`,basis:'На 1 порцию / 1 гостя',outputGrams,waterGrams,pieces:1,rows,steps,allergens,
ingredientCost:Math.round(rows.reduce((sum,r)=>sum+r.gross*r.unitCost,0)*100)/100, ingredientCost:Math.round(rows.reduce((sum,r)=>sum+r.gross*r.unitCost,0)*100)/100,

View File

@ -10,7 +10,7 @@ const premium=mixes.map(([key,name,price,pieces,sourceIds],index)=>{
const sources=sourceIds.map(id=>boxes.find(b=>b.id===`demo-v1-${id}`)),map=new Map(); const sources=sourceIds.map(id=>boxes.find(b=>b.id===`demo-v1-${id}`)),map=new Map();
for(const b of sources)for(const r of b.ttk.rows){const old=map.get(r.productId)||{...r,gross:0,net:0};old.gross=round(old.gross+r.gross/2);old.net=round(old.net+r.net/2);map.set(r.productId,old)} for(const b of sources)for(const r of b.ttk.rows){const old=map.get(r.productId)||{...r,gross:0,net:0};old.gross=round(old.gross+r.gross/2);old.net=round(old.net+r.net/2);map.set(r.productId,old)}
const rows=[...map.values()],outputGrams=Math.round(rows.filter(r=>r.unit==='кг').reduce((s,r)=>s+r.net*1000,0)); const rows=[...map.values()],outputGrams=Math.round(rows.filter(r=>r.unit==='кг').reduce((s,r)=>s+r.net*1000,0));
return {id:`demo-extras-v1-${key}`,name,category:5,catalogSection:'Демонстрационные премиум-сеты',price,pieces,weight:`${outputGrams} г`,photo:'',demo:true, return {id:`demo-extras-v1-${key}`,name,category:5,catalogSection:'Демонстрационные премиум-сеты',price,pieces,weight:`${outputGrams} г`,photo:`demo/images/extras-${key}.webp`,demo:true,
composition:sources.map(b=>'Половина стандартного бокса: '+b.name),ingredients:rows.map(r=>[r.name,r.gross,r.unit]), composition:sources.map(b=>'Половина стандартного бокса: '+b.name),ingredients:rows.map(r=>[r.name,r.gross,r.unit]),
ttk:{number:`ДЕМО-П${index+1}`,basis:'На 1 готовый премиум-сет',outputGrams,pieces,rows, ttk:{number:`ДЕМО-П${index+1}`,basis:'На 1 готовый премиум-сет',outputGrams,pieces,rows,
steps:['Приготовить две мини-подборки в половинном объёме по указанным нормам.',...sources.flatMap(b=>b.ttk.steps.map(s=>b.name+': '+s)),'Уложить обе подборки в одну общую коробку.'], steps:['Приготовить две мини-подборки в половинном объёме по указанным нормам.',...sources.flatMap(b=>b.ttk.steps.map(s=>b.name+': '+s)),'Уложить обе подборки в одну общую коробку.'],
@ -31,6 +31,6 @@ const specs=[
['serving-kit',2,'Набор для подачи: щипцы и лопатка',190,'Демо: набор щипцы и лопатка',85,'шт.','pack','Один комплект одноразовых приборов для подачи'] ['serving-kit',2,'Набор для подачи: щипцы и лопатка',190,'Демо: набор щипцы и лопатка',85,'шт.','pack','Один комплект одноразовых приборов для подачи']
]; ];
const stock=specs.map(([key,category,name,price,productName,cost,unit,supplier])=>({id:`demo-extras-v1-stock-${key}`,name:productName,cost,unit,qty:0,min:unit==='кг'?1:10,supplierId:`demo-v1-supplier-${supplier}`,lastPurchaseDate:'',lastPurchasePrice:0})); const stock=specs.map(([key,category,name,price,productName,cost,unit,supplier])=>({id:`demo-extras-v1-stock-${key}`,name:productName,cost,unit,qty:0,min:unit==='кг'?1:10,supplierId:`demo-v1-supplier-${supplier}`,lastPurchaseDate:'',lastPurchasePrice:0}));
const packaged=specs.map(([key,category,name,price,productName,cost,unit,supplier,description])=>({id:`demo-extras-v1-${key}`,name,category,catalogSection:({1:'Одноразовая посуда',2:'Для сервировки',3:'Безалкогольные напитки'})[category],price,pieces:1,weight:key==='ice'?'1 кг':'',photo:'',composition:[description],ingredients:[[productName,1,unit]],demo:true})); const packaged=specs.map(([key,category,name,price,productName,cost,unit,supplier,description])=>({id:`demo-extras-v1-${key}`,name,category,catalogSection:({1:'Одноразовая посуда',2:'Для сервировки',3:'Безалкогольные напитки'})[category],price,pieces:1,weight:key==='ice'?'1 кг':'',photo:`demo/images/extras-${key}.webp`,composition:[description],ingredients:[[productName,1,unit]],demo:true}));
const delivery=[['city','Доставка по городу',600,'Учебный тариф за один адрес в пределах города.'],['outer','Доставка за город',1200,'Учебный тариф за один адрес в ближайшем пригороде.']].map(([key,name,price,note])=>({id:`demo-extras-v1-delivery-${key}`,name,category:4,catalogSection:'Учебные тарифы доставки',price,pieces:0,weight:'',photo:'',composition:[note],ingredients:[],demo:true})); const delivery=[['city','Доставка по городу',600,'Учебный тариф за один адрес в пределах города.'],['outer','Доставка за город',1200,'Учебный тариф за один адрес в ближайшем пригороде.']].map(([key,name,price,note])=>({id:`demo-extras-v1-delivery-${key}`,name,category:4,catalogSection:'Учебные тарифы доставки',price,pieces:0,weight:'',photo:`demo/images/extras-delivery-${key}.webp`,composition:[note],ingredients:[],demo:true}));
export const extrasDemo={version:1,items:[...premium,...packaged,...delivery],stock}; export const extrasDemo={version:1,items:[...premium,...packaged,...delivery],stock};

View File

@ -33,7 +33,7 @@
const controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),15000); const controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),15000);
try{ try{
const [base,banquet,extras]=await Promise.all(['catalog-v1','banquet-v1','extras-v1'].map(async name=>{ const [base,banquet,extras]=await Promise.all(['catalog-v1','banquet-v1','extras-v1'].map(async name=>{
const r=await fetch(`demo/${name}.json?v=20260920-training`,{signal:controller.signal,credentials:'same-origin'}); const r=await fetch(`demo/${name}.json?v=20260922-training-photos`,{signal:controller.signal,credentials:'same-origin'});
if(!r.ok)throw new Error('Не удалось загрузить учебный каталог. Проверьте интернет и повторите.'); if(!r.ok)throw new Error('Не удалось загрузить учебный каталог. Проверьте интернет и повторите.');
const value=await r.json();if(value?.version!==1)throw new Error('Неизвестная версия учебного каталога.');return value; const value=await r.json();if(value?.version!==1)throw new Error('Неизвестная версия учебного каталога.');return value;
})); }));

View File

@ -9,7 +9,7 @@
"price": 290, "price": 290,
"weight": "120 г", "weight": "120 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-caprese.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Моцарелла мини, Томаты, Базилик, Масло оливковое" "Моцарелла мини, Томаты, Базилик, Масло оливковое"
@ -95,7 +95,7 @@
"price": 390, "price": 390,
"weight": "100 г", "weight": "100 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-roastbeef.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Ростбиф готовый, Корнишоны, Горчица, Сливки" "Ростбиф готовый, Корнишоны, Горчица, Сливки"
@ -182,7 +182,7 @@
"price": 350, "price": 350,
"weight": "90 г", "weight": "90 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-salmon-roll.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Лосось слабосолёный, Сыр сливочный, Огурцы, Укроп" "Лосось слабосолёный, Сыр сливочный, Огурцы, Укроп"
@ -269,7 +269,7 @@
"price": 230, "price": 230,
"weight": "160 г", "weight": "160 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-hummus.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Морковь, Огурцы, Перец сладкий, Хумус готовый" "Морковь, Огурцы, Перец сладкий, Хумус готовый"
@ -355,7 +355,7 @@
"price": 340, "price": 340,
"weight": "180 г", "weight": "180 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-caesar.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Филе курицы готовое, Салат листовой, Томаты черри, Хлеб тостовый, Сыр гауда, Сливки, Горчица" "Филе курицы готовое, Салат листовой, Томаты черри, Хлеб тостовый, Сыр гауда, Сливки, Горчица"
@ -483,7 +483,7 @@
"price": 280, "price": 280,
"weight": "180 г", "weight": "180 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-olivier.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Картофель, Морковь, Ветчина из индейки, Яйцо куриное без скорлупы, Горошек зелёный консервированный, Корнишоны, Сливки, Горчица" "Картофель, Морковь, Ветчина из индейки, Яйцо куриное без скорлупы, Горошек зелёный консервированный, Корнишоны, Сливки, Горчица"
@ -624,7 +624,7 @@
"price": 290, "price": 290,
"weight": "180 г", "weight": "180 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-greek.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Томаты, Огурцы, Перец сладкий, Моцарелла мини, Маслины без косточек, Масло оливковое" "Томаты, Огурцы, Перец сладкий, Моцарелла мини, Маслины без косточек, Масло оливковое"
@ -736,7 +736,7 @@
"price": 310, "price": 310,
"weight": "130 г", "weight": "130 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-julienne.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Филе курицы готовое, Шампиньоны, Лук репчатый, Сливки, Сыр гауда, Масло оливковое" "Филе курицы готовое, Шампиньоны, Лук репчатый, Сливки, Сыр гауда, Масло оливковое"
@ -849,7 +849,7 @@
"price": 270, "price": 270,
"weight": "120 г", "weight": "120 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-stuffed-mushrooms.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Шампиньоны, Сыр сливочный, Сыр гауда, Укроп, Масло оливковое" "Шампиньоны, Сыр сливочный, Сыр гауда, Укроп, Масло оливковое"
@ -949,7 +949,7 @@
"price": 490, "price": 490,
"weight": "220 г", "weight": "220 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-chicken.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Филе куриное сырое, Перец сладкий, Морковь, Сливки, Масло оливковое" "Филе куриное сырое, Перец сладкий, Морковь, Сливки, Масло оливковое"
@ -1049,7 +1049,7 @@
"price": 590, "price": 590,
"weight": "200 г", "weight": "200 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-cod.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Филе трески, Томаты, Перец сладкий, Лимон, Масло оливковое" "Филе трески, Томаты, Перец сладкий, Лимон, Масло оливковое"
@ -1148,7 +1148,7 @@
"price": 690, "price": 690,
"weight": "220 г", "weight": "220 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-beef-hot.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Ростбиф готовый, Шампиньоны, Лук репчатый, Сливки, Масло оливковое" "Ростбиф готовый, Шампиньоны, Лук репчатый, Сливки, Масло оливковое"
@ -1247,7 +1247,7 @@
"price": 160, "price": 160,
"weight": "150 г", "weight": "150 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-mash.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Картофель, Молоко, Масло сливочное" "Картофель, Молоко, Масло сливочное"
@ -1320,7 +1320,7 @@
"price": 170, "price": 170,
"weight": "150 г", "weight": "150 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-rice.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Рис сухой, Морковь, Перец сладкий, Горошек зелёный консервированный, Масло оливковое" "Рис сухой, Морковь, Перец сладкий, Горошек зелёный консервированный, Масло оливковое"
@ -1417,7 +1417,7 @@
"price": 240, "price": 240,
"weight": "110 г", "weight": "110 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-berry-cream.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Сыр сливочный, Сливки, Крошка бисквитная, Клубника, Голубика, Пудра сахарная" "Сыр сливочный, Сливки, Крошка бисквитная, Клубника, Голубика, Пудра сахарная"
@ -1531,7 +1531,7 @@
"price": 310, "price": 310,
"weight": "100 г", "weight": "100 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-cheese-honey.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Сыр бри, Виноград, Орех грецкий очищенный, Мёд" "Сыр бри, Виноград, Орех грецкий очищенный, Мёд"
@ -1618,7 +1618,7 @@
"price": 290, "price": 290,
"weight": "180 г", "weight": "180 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-fruit.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Виноград, Клубника, Голубика" "Виноград, Клубника, Голубика"
@ -1689,7 +1689,7 @@
"price": 120, "price": 120,
"weight": "60 г", "weight": "60 г",
"pieces": 1, "pieces": 1,
"photo": "", "photo": "demo/images/banquet-bread.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Багет, Хлеб ржаной, Масло сливочное, Укроп" "Багет, Хлеб ржаной, Масло сливочное, Укроп"

View File

@ -9,7 +9,7 @@
"price": 3500, "price": 3500,
"pieces": 18, "pieces": 18,
"weight": "710 г", "weight": "710 г",
"photo": "", "photo": "demo/images/extras-salmon-caprese.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Половина стандартного бокса: Канапе с лососем и сливочным сыром", "Половина стандартного бокса: Канапе с лососем и сливочным сыром",
@ -194,7 +194,7 @@
"price": 3900, "price": 3900,
"pieces": 0, "pieces": 0,
"weight": "750 г", "weight": "750 г",
"photo": "", "photo": "demo/images/extras-meat-cheese.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Половина стандартного бокса: Мясное ассорти с корнишонами", "Половина стандартного бокса: Мясное ассорти с корнишонами",
@ -432,7 +432,7 @@
"price": 2900, "price": 2900,
"pieces": 22, "pieces": 22,
"weight": "820 г", "weight": "820 г",
"photo": "", "photo": "demo/images/extras-mini-buffet.webp",
"demo": true, "demo": true,
"composition": [ "composition": [
"Половина стандартного бокса: Брускетты с томатами и базиликом", "Половина стандартного бокса: Брускетты с томатами и базиликом",
@ -603,7 +603,7 @@
"price": 90, "price": 90,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-water.webp",
"composition": [ "composition": [
"Одна бутылка 0,5 л" "Одна бутылка 0,5 л"
], ],
@ -624,7 +624,7 @@
"price": 100, "price": 100,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-sparkling.webp",
"composition": [ "composition": [
"Одна бутылка 0,5 л" "Одна бутылка 0,5 л"
], ],
@ -645,7 +645,7 @@
"price": 220, "price": 220,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-juice.webp",
"composition": [ "composition": [
"Одна упаковка 1 л" "Одна упаковка 1 л"
], ],
@ -666,7 +666,7 @@
"price": 280, "price": 280,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-mors.webp",
"composition": [ "composition": [
"Одна бутылка готового морса 1 л" "Одна бутылка готового морса 1 л"
], ],
@ -687,7 +687,7 @@
"price": 25, "price": 25,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-plate.webp",
"composition": [ "composition": [
"Одна сервировочная тарелка" "Одна сервировочная тарелка"
], ],
@ -708,7 +708,7 @@
"price": 10, "price": 10,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-fork.webp",
"composition": [ "composition": [
"Одна вилка" "Одна вилка"
], ],
@ -729,7 +729,7 @@
"price": 15, "price": 15,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-glass.webp",
"composition": [ "composition": [
"Один стакан" "Один стакан"
], ],
@ -750,7 +750,7 @@
"price": 10, "price": 10,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-napkin.webp",
"composition": [ "composition": [
"Одна бумажная салфетка" "Одна бумажная салфетка"
], ],
@ -771,7 +771,7 @@
"price": 180, "price": 180,
"pieces": 1, "pieces": 1,
"weight": "1 кг", "weight": "1 кг",
"photo": "", "photo": "demo/images/extras-ice.webp",
"composition": [ "composition": [
"Один пакет пищевого льда, 1 кг" "Один пакет пищевого льда, 1 кг"
], ],
@ -792,7 +792,7 @@
"price": 250, "price": 250,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-tablecloth.webp",
"composition": [ "composition": [
"Одна одноразовая скатерть 120 × 180 см" "Одна одноразовая скатерть 120 × 180 см"
], ],
@ -813,7 +813,7 @@
"price": 190, "price": 190,
"pieces": 1, "pieces": 1,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-serving-kit.webp",
"composition": [ "composition": [
"Один комплект одноразовых приборов для подачи" "Один комплект одноразовых приборов для подачи"
], ],
@ -834,7 +834,7 @@
"price": 600, "price": 600,
"pieces": 0, "pieces": 0,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-delivery-city.webp",
"composition": [ "composition": [
"Учебный тариф за один адрес в пределах города." "Учебный тариф за один адрес в пределах города."
], ],
@ -849,7 +849,7 @@
"price": 1200, "price": 1200,
"pieces": 0, "pieces": 0,
"weight": "", "weight": "",
"photo": "", "photo": "demo/images/extras-delivery-outer.webp",
"composition": [ "composition": [
"Учебный тариф за один адрес в ближайшем пригороде." "Учебный тариф за один адрес в ближайшем пригороде."
], ],

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,4 +1,4 @@
<!doctype html><html lang="ru"><head><script src="core/training-catalog.js?v=20260921-training-notice"></script><link rel="stylesheet" href="core/login-signature-v1776.css?v=20260918-ui-stability"><script src="core/cloud-transport.js?v=20260918-ui-stability"></script><script src="core/banquet-menu.js?v=20260918-ui-stability-20260919-client-menu"></script><script src="core/access-policy.js?v=20260918-ui-stability"></script><style>body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important;pointer-events:none!important}</style><meta charset="utf-8"><script defer src="core/import-archive.js?v=20260918-ui-stability"></script><script src="core/company-branding.js?v=20260920-employee-session"></script><script src="core/brand-theme.js?v=20260918-ui-stability"></script><script id="sun-startup-stability-guard"> <!doctype html><html lang="ru"><head><script src="core/training-catalog.js?v=20260922-training-photos"></script><link rel="stylesheet" href="core/login-signature-v1776.css?v=20260918-ui-stability"><script src="core/cloud-transport.js?v=20260918-ui-stability"></script><script src="core/banquet-menu.js?v=20260918-ui-stability-20260919-client-menu"></script><script src="core/access-policy.js?v=20260918-ui-stability"></script><style>body.sun-cloud-auth-required>header,body.sun-cloud-auth-required>.view{visibility:hidden!important;pointer-events:none!important}</style><meta charset="utf-8"><script defer src="core/import-archive.js?v=20260918-ui-stability"></script><script src="core/company-branding.js?v=20260920-employee-session"></script><script src="core/brand-theme.js?v=20260918-ui-stability"></script><script id="sun-startup-stability-guard">
(()=>{ (()=>{
'use strict'; 'use strict';
const stamp=()=>new Date().toISOString().replace(/[:.]/g,'-'); const stamp=()=>new Date().toISOString().replace(/[:.]/g,'-');

View File

@ -4,7 +4,7 @@ const CORE=[
'./core/support-form.js?v=20260921-support-recipient', './core/support-form.js?v=20260921-support-recipient',
'./core/trial-promo-developer-v181.js?v=20260921-promo-entry', './core/trial-promo-developer-v181.js?v=20260921-promo-entry',
'./core/banquet-client-menu.js?v=20260920-onepage', './core/banquet-client-menu.js?v=20260920-onepage',
'./core/training-catalog.js?v=20260920-training', './core/training-catalog.js?v=20260922-training-photos',
'./core/catalog-pricing.js?v=20260919-client-menu','./core/client-menu.css?v=20260919-client-menu', './core/catalog-pricing.js?v=20260919-client-menu','./core/client-menu.css?v=20260919-client-menu',
'./vendor/supabase-2.112.4.min.js', './vendor/supabase-2.112.4.min.js',
`./core/help-center.js?v=${VERSION}`,`./core/help-center.css?v=${VERSION}`,`./help/knowledge-v1.json?v=${VERSION}`, `./core/help-center.js?v=${VERSION}`,`./core/help-center.css?v=${VERSION}`,`./help/knowledge-v1.json?v=${VERSION}`,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long