caterium-app/public/app-runtime.js
pavlov346346-source 14e3d6b45b fix: merge addon pills into one card, wire it to editable Settings
Two related fixes reported directly against live screenshots:

1. addonRow rendered each addon ("Напитки", "Чайная станция", ...) as
   its own tall bordered pill sitting side by side -- disjointed, and
   unlike the single-card "Полезные дополнения" treatment in the
   referenced design. Now draws one card with a heading
   ("Рекомендуем добавить") containing all the icon+label items,
   matching how checklistCard already works.

2. The addon list and the "Всё под контролем" checklist were hardcoded
   constants in the canvas module, completely bypassing the app's
   existing editable Client Offer Settings (Настройки → Предложение →
   "Дополнительно к заказу", textarea-backed extraServices/controlLines
   the user can already edit for the old templates). preparedSnapshot
   now attaches the resolved controlLines/extraServices/titles onto
   every snapshot; the 8 signature templates read them via a local
   shadowing const (falls back to the previous defaults when nothing
   is configured), so editing that Settings textarea now actually
   changes what shows up in these templates too, on all 8 of them from
   one change since addonRow/checklistCard are shared helpers.

Verified locally: default list (6 items) lays out cleanly in one card
with 2-line wrapping where needed; a snapshot with custom
controlLines/extraServices renders those exact custom strings instead
of the defaults, confirming the wiring actually works end to end and
isn't just falling back silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 11:28:18 +03:00

5781 lines
850 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ===== MODULE: legacy-catalog-v175.js ===== */
window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс № 1 Канапе мясные","category":0,"price":3690,"photo":"catalog/001.jpg","weight":"600 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. канапе с мортаделлой и дроблённой фисташкой","5 шт. канапе с салями мадеро","5 шт. канапе с салями парма","5 шт. канапе с прошуто","5 шт. канапе с шейками капоколло с вялеными томатами"],"ingredients":[["канапе с мортаделлой и дроблённой фисташкой",5,"шт."],["канапе с салями мадеро",5,"шт."],["канапе с салями парма",5,"шт."],["канапе с прошуто",5,"шт."],["канапе с шейками капоколло с вялеными томатами",5,"шт."]],"catalogOrder":1},{"id":"2","name":"Фуршетный бокс № 2 Канапе рыбные","category":0,"price":4590,"photo":"catalog/002.jpg","weight":"600 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. тартинка с красной икрой и кремчизом","5 шт. кростини с лососем и кремчизом","5 шт. тартинка с креветкой и соусом цезарь","5 шт. кростини с креветкой и соусом чили","5 шт. кростини с форелью и сливочным соусом"],"ingredients":[["тартинка с красной икрой и кремчизом",5,"шт."],["кростини с лососем и кремчизом",5,"шт."],["тартинка с креветкой и соусом цезарь",5,"шт."],["кростини с креветкой и соусом чили",5,"шт."],["кростини с форелью и сливочным соусом",5,"шт."]],"catalogOrder":2},{"id":"3","name":"Фуршетный бокс № 3 Микс тарталеток","category":0,"price":4590,"photo":"catalog/003.jpg","weight":"790 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. тартинка с креветкой и соусом цезарь","5 шт. тартинка с щучьей икрой и мягким сыром","5 шт. тартинка с красной икрой и взбитым маслом","5 шт. тартинка с прошуто и грушей","5 шт. тартинка с мортаделлой"],"ingredients":[["тартинка с креветкой и соусом цезарь",5,"шт."],["тартинка с щучьей икрой и мягким сыром",5,"шт."],["тартинка с красной икрой и взбитым маслом",5,"шт."],["тартинка с прошуто и грушей",5,"шт."],["тартинка с мортаделлой",5,"шт."]],"catalogOrder":3},{"id":"4","name":"Фуршетный бокс № 4 Премиум морепродукты","category":0,"price":6990,"photo":"catalog/004.jpg","weight":"625 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. тартинка с тунцом","5 шт. канапе с тигровой креветкой с чили","5 шт. ролл из шпината с кремчизом и красной икрой","5 шт. ролл с чернилами каракатицы, лососем и сливочным сыром","5 шт. тартинка с тигровой креветкой и соусом из тунца"],"ingredients":[["тартинка с тунцом",5,"шт."],["канапе с тигровой креветкой с чили",5,"шт."],["ролл из шпината с кремчизом и красной икрой",5,"шт."],["ролл с чернилами каракатицы, лососем и сливочным сыром",5,"шт."],["тартинка с тигровой креветкой и соусом из тунца",5,"шт."]],"catalogOrder":4},{"id":"5","name":"Фуршетный бокс № 5 Брускетты мясные","category":0,"price":3990,"photo":"catalog/005.jpg","weight":"750 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. говядина с печёным перцем и горчицей","5 шт. салями мадеро с оливкой","5 шт. салями парма с маслинами и огурцом","5 шт. прошуто с грушой и кремчизом","5 шт. шейка капоколло с вяленым томатом"],"ingredients":[["говядина с печёным перцем и горчицей",5,"шт."],["салями мадеро с оливкой",5,"шт."],["салями парма с маслинами и огурцом",5,"шт."],["прошуто с грушой и кремчизом",5,"шт."],["шейка капоколло с вяленым томатом",5,"шт."]],"catalogOrder":5},{"id":"6","name":"Фуршетный бокс № 6 Сыры в шотах","category":0,"price":5490,"photo":"catalog/006.jpg","weight":"900 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. моцарелла с томатами и песто","5 шт. дорблю с грушей и малиной","5 шт. бри с миндалём и медовыми сотами","5 шт. пармезан с голубикой и пеканом","5 шт. чеддер с виноградом"],"ingredients":[["моцарелла с томатами и песто",5,"шт."],["дорблю с грушей и малиной",5,"шт."],["бри с миндалём и медовыми сотами",5,"шт."],["пармезан с голубикой и пеканом",5,"шт."],["чеддер с виноградом",5,"шт."]],"catalogOrder":6},{"id":"catalog-007","name":"Фуршетный бокс № 7 Сыр канапе","category":0,"price":3510,"photo":"catalog/007.jpg","weight":"450 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. бри с клубникой","5 шт. эдам с курагой","5 шт. пармезан с инжиром и голубикой","10 шт. чеддер с виноградом","5 шт. камамбер с голубикой"],"ingredients":[["бри с клубникой",5,"шт."],["эдам с курагой",5,"шт."],["пармезан с инжиром и голубикой",5,"шт."],["чеддер с виноградом",10,"шт."],["камамбер с голубикой",5,"шт."]],"catalogOrder":7},{"id":"catalog-008","name":"Фуршетный бокс № 50 Мини-эклеры","category":0,"price":4490,"photo":"catalog/008.jpg","weight":"500 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. мини-эклер с паштетом из куриных жареных печёнок — 20 г","5 шт. мини-эклер с риетом из тунца — 20 г","5 шт. мини-эклер с красной икрой и крем-чизом — 20 г","5 шт. мини-эклер с чёрной структурированной икрой и крем-чизом — 20 г","5 шт. мини-эклер с форелью и крем-чизом — 20 г"],"ingredients":[["мини-эклер с паштетом из куриных жареных печёнок — 20 г",5,"шт."],["мини-эклер с риетом из тунца — 20 г",5,"шт."],["мини-эклер с красной икрой и крем-чизом — 20 г",5,"шт."],["мини-эклер с чёрной структурированной икрой и крем-чизом — 20 г",5,"шт."],["мини-эклер с форелью и крем-чизом — 20 г",5,"шт."]],"catalogOrder":8},{"id":"catalog-009","name":"Фуршетный бокс № 21 Антипасти","category":0,"price":5590,"photo":"catalog/009.jpg","weight":"675 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. перчики греко с мягким сыром","5 шт. канапе с салями мадеро","5 шт. антипасти с оливками, томатами и виноградом","5 шт. ассорти сыров","5 шт. артишоки, вяленые томаты и печёный перец"],"ingredients":[["перчики греко с мягким сыром",5,"шт."],["канапе с салями мадеро",5,"шт."],["антипасти с оливками, томатами и виноградом",5,"шт."],["ассорти сыров",5,"шт."],["артишоки, вяленые томаты и печёный перец",5,"шт."]],"catalogOrder":9},{"id":"catalog-010","name":"Фуршетный бокс № 48 Мини-эклеры","category":0,"price":3790,"photo":"catalog/010.jpg","weight":"500 г","catalogSection":"Канапе и фуршетные закуски","composition":["15 шт. мини-эклер с паштетом из куриных жареных печёнок — 20 г","10 шт. мини-эклер с красной икрой и крем-чизом — 20 г"],"ingredients":[["мини-эклер с паштетом из куриных жареных печёнок — 20 г",15,"шт."],["мини-эклер с красной икрой и крем-чизом — 20 г",10,"шт."]],"catalogOrder":10},{"id":"catalog-011","name":"Фуршетный бокс № 15 Канапе рыбные","category":0,"price":5490,"photo":"catalog/011.jpg","weight":"700 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. крости с тар-таром из креветок","5 шт. канапе с тунцом и юдзу","5 шт. канапе с креветкой в панко и чили-соусом","5 шт. канапе с лососем и мягким сыром","5 шт. канапе с креветкой и соусом Цезарь"],"ingredients":[["крости с тар-таром из креветок",5,"шт."],["канапе с тунцом и юдзу",5,"шт."],["канапе с креветкой в панко и чили-соусом",5,"шт."],["канапе с лососем и мягким сыром",5,"шт."],["канапе с креветкой и соусом Цезарь",5,"шт."]],"catalogOrder":11},{"id":"catalog-012","name":"Фуршетный бокс № 16 Вегетарианский","category":0,"price":3790,"photo":"catalog/012.jpg","weight":"700 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. крости с моцареллой, черри и песто","5 шт. крости с баклажановой икрой и фетой","5 шт. крости с грибной икрой и трюфельной пастой","5 шт. крости с печёным перцем и фетой","5 шт. крости с вялеными томатами"],"ingredients":[["крости с моцареллой, черри и песто",5,"шт."],["крости с баклажановой икрой и фетой",5,"шт."],["крости с грибной икрой и трюфельной пастой",5,"шт."],["крости с печёным перцем и фетой",5,"шт."],["крости с вялеными томатами",5,"шт."]],"catalogOrder":12},{"id":"catalog-013","name":"Фуршетный бокс № 18 Тосты ассорти","category":0,"price":3190,"photo":"catalog/013.jpg","weight":"820 г","catalogSection":"Канапе и фуршетные закуски","composition":["25 шт. тостов ассорти","Тосты с селёдкой на свекольном муссе","Тосты с салом и солёным огурчиком"],"ingredients":[["тостов ассорти",25,"шт."],["Тосты с селёдкой на свекольном муссе",1,"поз."],["Тосты с салом и солёным огурчиком",1,"поз."]],"catalogOrder":13},{"id":"catalog-014","name":"Фуршетный бокс № 43 Тарталетки с икрой","category":0,"price":4890,"photo":"catalog/014.jpg","weight":"625 г","catalogSection":"Канапе и фуршетные закуски","composition":["15 шт. тарталетка с красной икрой и крем-чизом","10 шт. тарталетка с чёрной структурированной икрой и крем-чизом"],"ingredients":[["тарталетка с красной икрой и крем-чизом",15,"шт."],["тарталетка с чёрной структурированной икрой и крем-чизом",10,"шт."]],"catalogOrder":14},{"id":"catalog-015","name":"Фуршетный бокс № 49 Мини-эклеры","category":0,"price":3490,"photo":"catalog/015.jpg","weight":"500 г","catalogSection":"Канапе и фуршетные закуски","composition":["15 шт. мини-эклер с паштетом из куриных жареных печёнок — 20 г","10 шт. мини-эклер с риетом из тунца — 20 г"],"ingredients":[["мини-эклер с паштетом из куриных жареных печёнок — 20 г",15,"шт."],["мини-эклер с риетом из тунца — 20 г",10,"шт."]],"catalogOrder":15},{"id":"catalog-016","name":"Фуршетный бокс № 19 Рулетики ветчина-сыр","category":0,"price":3690,"photo":"catalog/016.jpg","weight":"1000 г","catalogSection":"Канапе и фуршетные закуски","composition":["50 шт. рулетики из ветчины с сыром"],"ingredients":[["рулетики из ветчины с сыром",50,"шт."]],"catalogOrder":16},{"id":"catalog-017","name":"Фуршетный бокс № 51 Брускетты с говядиной и томаты капрезе","category":0,"price":3990,"photo":"catalog/017.jpg","weight":"840 г","catalogSection":"Канапе и фуршетные закуски","composition":["12 шт. брускетта с пастрами из говядины — 35 г","12 шт. брускетта с моцареллой и томатами черри — 35 г"],"ingredients":[["брускетта с пастрами из говядины — 35 г",12,"шт."],["брускетта с моцареллой и томатами черри — 35 г",12,"шт."]],"catalogOrder":17},{"id":"catalog-018","name":"Фуршетный бокс № 27 Брускетты с тунцом и лососем","category":0,"price":4290,"photo":"catalog/018.jpg","weight":"840 г","catalogSection":"Канапе и фуршетные закуски","composition":["12 шт. брускетта с тунцом и апельсином — 35 г","12 шт. брускетта с лососем и крем-чизом — 35 г"],"ingredients":[["брускетта с тунцом и апельсином — 35 г",12,"шт."],["брускетта с лососем и крем-чизом — 35 г",12,"шт."]],"catalogOrder":18},{"id":"catalog-019","name":"Фуршетный бокс № 20 Профитроли","category":0,"price":4890,"photo":"catalog/019.jpg","weight":"700 г","catalogSection":"Канапе и фуршетные закуски","composition":["5 шт. профитроли с говядиной и соусом вителло тонато","5 шт. профитроли с красной икрой и сливочным маслом","5 шт. профитроли с риетом из копчёной форели","5 шт. профитроли с лососем и мягким сыром","5 шт. профитроли с паштетом из жареных куриных печёнок"],"ingredients":[["профитроли с говядиной и соусом вителло тонато",5,"шт."],["профитроли с красной икрой и сливочным маслом",5,"шт."],["профитроли с риетом из копчёной форели",5,"шт."],["профитроли с лососем и мягким сыром",5,"шт."],["профитроли с паштетом из жареных куриных печёнок",5,"шт."]],"catalogOrder":19},{"id":"catalog-020","name":"Фуршетный бокс № 26 Брускетты с креветками и муссом из лосося","category":0,"price":3690,"photo":"catalog/020.jpg","weight":"780 г","catalogSection":"Канапе и фуршетные закуски","composition":["12 шт. брускетта с креветкой и чили-соусом — 35 г","12 шт. брускетта с муссом из копчёного лосося — 30 г"],"ingredients":[["брускетта с креветкой и чили-соусом — 35 г",12,"шт."],["брускетта с муссом из копчёного лосося — 30 г",12,"шт."]],"catalogOrder":20},{"id":"catalog-021","name":"Фуршетный бокс № 28 Брускетты с говядиной и индейкой","category":0,"price":4290,"photo":"catalog/021.jpg","weight":"780 г","catalogSection":"Канапе и фуршетные закуски","composition":["12 шт. брускетта с говядиной — 35 г","12 шт. брускетта с пастрами из индейки — 30 г"],"ingredients":[["брускетта с говядиной — 35 г",12,"шт."],["брускетта с пастрами из индейки — 30 г",12,"шт."]],"catalogOrder":21},{"id":"catalog-022","name":"Фуршетный бокс № 29 Брускетты с мортаделлой и салями мадера","category":0,"price":3690,"photo":"catalog/022.jpg","weight":"720 г","catalogSection":"Канапе и фуршетные закуски","composition":["12 шт. брускетта с мортаделлой — 25 г","12 шт. брускетта с салями мадера — 35 г"],"ingredients":[["брускетта с мортаделлой — 25 г",12,"шт."],["брускетта с салями мадера — 35 г",12,"шт."]],"catalogOrder":22},{"id":"catalog-023","name":"Фуршетный бокс № 9 Салаты Цезарь микс в шотах","category":0,"price":4390,"photo":"catalog/023.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["8 пор. цезарь с цыплёнком в хрустящей корочке — 75 г","8 пор. цезарь с креветкой в темпуре — 75 г"],"ingredients":[["цезарь с цыплёнком в хрустящей корочке — 75 г",8,"пор."],["цезарь с креветкой в темпуре — 75 г",8,"пор."]],"catalogOrder":23},{"id":"catalog-024","name":"Фуршетный бокс № 8 Салат Цезарь с цыплёнком в панировке","category":0,"price":3460,"photo":"catalog/024.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. салата Цезарь: ромэн, черри, пармезан, цыплёнок в панировке, соус Цезарь, кростини — 75 г"],"ingredients":[["салата Цезарь: ромэн, черри, пармезан, цыплёнок в панировке, соус Цезарь, кростини — 75 г",16,"пор."]],"catalogOrder":24},{"id":"catalog-025","name":"Фуршетный бокс № 8 Салат Цезарь с креветкой в темпуре","category":0,"price":3790,"photo":"catalog/025.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. салата Цезарь: ромэн, черри, пармезан, креветка в темпуре, соус Цезарь, кростини — 75 г"],"ingredients":[["салата Цезарь: ромэн, черри, пармезан, креветка в темпуре, соус Цезарь, кростини — 75 г",16,"пор."]],"catalogOrder":25},{"id":"catalog-026","name":"Фуршетный бокс № 8 Салат Мимоза с лососем г/к","category":0,"price":4250,"photo":"catalog/026.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. салата Мимоза с лососем горячего копчения"],"ingredients":[["салата Мимоза с лососем горячего копчения",16,"пор."]],"catalogOrder":26},{"id":"catalog-027","name":"Фуршетный бокс № 8 Салат Греческий","category":0,"price":3290,"photo":"catalog/027.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. салата Греческий — 75 г"],"ingredients":[["салата Греческий — 75 г",16,"пор."]],"catalogOrder":27},{"id":"catalog-028","name":"Фуршетный бокс № 8 Салат Оливье с говядиной","category":0,"price":4690,"photo":"catalog/028.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. Оливье с говядиной — 75 г"],"ingredients":[["Оливье с говядиной — 75 г",16,"пор."]],"catalogOrder":28},{"id":"catalog-029","name":"Фуршетный бокс № 8 Салат Столичный с цыплёнком","category":0,"price":3890,"photo":"catalog/029.jpg","weight":"1200 г","catalogSection":"Салаты","composition":["16 пор. салата Столичный с цыплёнком в креманке — 75 г"],"ingredients":[["салата Столичный с цыплёнком в креманке — 75 г",16,"пор."]],"catalogOrder":29},{"id":"catalog-030","name":"Фуршетный бокс № 45 Салат Столичный в тарталетках","category":0,"price":3190,"photo":"catalog/030.jpg","weight":"900 г","catalogSection":"Салаты","composition":["20 шт. тарталеток","Салат Столичный с цыплёнком в тарталетках из песочного теста — 45 г"],"ingredients":[["тарталеток",20,"шт."],["Салат Столичный с цыплёнком в тарталетках из песочного теста",45,"г"]],"catalogOrder":30},{"id":"catalog-031","name":"Фуршетный бокс № 46 Салат Крабовый в тарталетках","category":0,"price":2790,"photo":"catalog/031.jpg","weight":"900 г","catalogSection":"Салаты","composition":["20 шт. тарталеток","Салат Крабовый классический в тарталетках из песочного теста — 45 г"],"ingredients":[["тарталеток",20,"шт."],["Салат Крабовый классический в тарталетках из песочного теста",45,"г"]],"catalogOrder":31},{"id":"catalog-032","name":"Фуршетный бокс № 47 Салат Оливье в тарталетках","category":0,"price":3890,"photo":"catalog/032.jpg","weight":"900 г","catalogSection":"Салаты","composition":["20 шт. тарталеток","Салат Оливье с говядиной в тарталетках из песочного теста — 45 г"],"ingredients":[["тарталеток",20,"шт."],["Салат Оливье с говядиной в тарталетках из песочного теста",45,"г"]],"catalogOrder":32},{"id":"catalog-033","name":"Фуршетный бокс № 47 Салат Оливье и Крабовый в тарталетках","category":0,"price":4290,"photo":"catalog/033.jpg","weight":"900 г","catalogSection":"Салаты","composition":["10 шт. тарталеток с Оливье и говядиной — 45 г","10 шт. тарталеток с классическим Крабовым салатом — 45 г"],"ingredients":[["тарталеток с Оливье и говядиной — 45 г",10,"шт."],["тарталеток с классическим Крабовым салатом — 45 г",10,"шт."]],"catalogOrder":33},{"id":"catalog-034","name":"Фуршетный бокс № 17 Круассаны с говядиной пастрами","category":0,"price":3500,"photo":"catalog/034.jpg","weight":"1050 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["20 шт. круассаны с говядиной, помидором и огурцом"],"ingredients":[["круассаны с говядиной, помидором и огурцом",20,"шт."]],"catalogOrder":34},{"id":"catalog-035","name":"Фуршетный бокс № 17 Круассаны с лососем","category":0,"price":4390,"photo":"catalog/035.jpg","weight":"1200 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["20 шт. круассаны с лососем с/с, салатом, свежим огурцом и сливочным соусом"],"ingredients":[["круассаны с лососем с/с, салатом, свежим огурцом и сливочным соусом",20,"шт."]],"catalogOrder":35},{"id":"catalog-036","name":"Фуршетный бокс № 10 Мини-бургеры классика","category":0,"price":4590,"photo":"catalog/036.jpg","weight":"1000 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["12 шт. бургеры с говяжьей котлетой","13 шт. бургеры с куриным стрипсом"],"ingredients":[["бургеры с говяжьей котлетой",12,"шт."],["бургеры с куриным стрипсом",13,"шт."]],"catalogOrder":36},{"id":"catalog-037","name":"Фуршетный бокс № 11 Сэндвичи с ветчиной и сыром","category":0,"price":2890,"photo":"catalog/037.jpg","weight":"1200 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["25 шт. сэндвич с ветчиной и сыром — 45 г"],"ingredients":[["сэндвич с ветчиной и сыром — 45 г",25,"шт."]],"catalogOrder":37},{"id":"catalog-038","name":"Фуршетный бокс № 12 Сэндвичи с лососем","category":0,"price":3990,"photo":"catalog/038.jpg","weight":"1200 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["25 шт. сэндвич с огурцом и лососем — 45 г"],"ingredients":[["сэндвич с огурцом и лососем — 45 г",25,"шт."]],"catalogOrder":38},{"id":"catalog-039","name":"Фуршетный бокс № 13 Мини-бургеры ассорти","category":0,"price":5990,"photo":"catalog/039.jpg","weight":"1000 г","catalogSection":"Бургеры, сэндвичи, круассаны","composition":["5 шт. бургер с говяжьей котлетой","5 шт. чизбургер с говяжьей котлетой","5 шт. бургер с креветкой","5 шт. бургер с куриной котлетой","5 шт. чикенбургер"],"ingredients":[["бургер с говяжьей котлетой",5,"шт."],["чизбургер с говяжьей котлетой",5,"шт."],["бургер с креветкой",5,"шт."],["бургер с куриной котлетой",5,"шт."],["чикенбургер",5,"шт."]],"catalogOrder":39},{"id":"catalog-040","name":"Фуршетный бокс № 14 Жюльен","category":0,"price":3290,"photo":"catalog/040.jpg","weight":"625 г","catalogSection":"Тёплые блюда и выпечка","composition":["25 шт. грибной жюльен с сыром пармезан в тарталетках — 25 г"],"ingredients":[["грибной жюльен с сыром пармезан в тарталетках — 25 г",25,"шт."]],"catalogOrder":40},{"id":"catalog-041","name":"Фуршетный бокс № 14 Жюльен с белыми грибами","category":0,"price":5290,"photo":"catalog/041.jpg","weight":"625 г","catalogSection":"Тёплые блюда и выпечка","composition":["25 шт. жюльен: белые грибы 70%, шампиньоны 30%, пармезан, тарталетки — 25 г"],"ingredients":[["жюльен: белые грибы 70%, шампиньоны 30%, пармезан, тарталетки — 25 г",25,"шт."]],"catalogOrder":41},{"id":"catalog-042","name":"Фуршетный бокс № 44 Пирог с сыром","category":0,"price":2690,"photo":"catalog/042.jpg","weight":"450 г","catalogSection":"Тёплые блюда и выпечка","composition":["16 пор. закрытого пирога с сыром"],"ingredients":[["закрытого пирога с сыром",16,"пор."]],"catalogOrder":42},{"id":"catalog-043","name":"Фуршетный бокс № 42 Блинчики с печенью","category":0,"price":2430,"photo":"catalog/043.jpg","weight":"875 г","catalogSection":"Тёплые блюда и выпечка","composition":["25 пор. блины с куриной печенью, луком и крем-чизом"],"ingredients":[["блины с куриной печенью, луком и крем-чизом",25,"пор."]],"catalogOrder":43},{"id":"catalog-044","name":"Фуршетный бокс № 24 Кулебяка и пирожки","category":0,"price":3890,"photo":"catalog/044.jpg","weight":"1070 г","catalogSection":"Тёплые блюда и выпечка","composition":["6 шт. пирожков с картошкой — 60 г","6 шт. пирожков с мясом — 60 г","1 шт. кулебяка с капустой — 350 г"],"ingredients":[["пирожков с картошкой — 60 г",6,"шт."],["пирожков с мясом — 60 г",6,"шт."],["кулебяка с капустой — 350 г",1,"шт."]],"catalogOrder":44},{"id":"catalog-045","name":"Фуршетный бокс № 33 Пирожки","category":0,"price":3690,"photo":"catalog/045.jpg","weight":"1080 г","catalogSection":"Тёплые блюда и выпечка","composition":["6 шт. пирожков с картошкой — 60 г","6 шт. пирожков с мясом — 60 г","6 шт. пирожков с капустой — 60 г"],"ingredients":[["пирожков с картошкой — 60 г",6,"шт."],["пирожков с мясом — 60 г",6,"шт."],["пирожков с капустой — 60 г",6,"шт."]],"catalogOrder":45},{"id":"catalog-046","name":"Фуршетный бокс № 25 Детский","category":0,"price":3690,"photo":"catalog/046.jpg","weight":"1150 г","catalogSection":"Тёплые блюда и выпечка","composition":["10 шт. чизбургеров — 500 г","Наггетсы — 300 г","Картофель фри — 200 г","Сырный соус — 50 г","Кетчуп — 50 г","Соус барбекю — 50 г"],"ingredients":[["чизбургеров — 500 г",10,"шт."],["Наггетсы",300,"г"],["Картофель фри",200,"г"],["Сырный соус",50,"г"],["Кетчуп",50,"г"],["Соус барбекю",50,"г"]],"catalogOrder":46},{"id":"catalog-047","name":"Фуршетный бокс № 42 Блинчики с мясом","category":0,"price":2590,"photo":"catalog/047.jpg","weight":"875 г","catalogSection":"Тёплые блюда и выпечка","composition":["25 пор. блины с мясным фаршем и крем-чизом"],"ingredients":[["блины с мясным фаршем и крем-чизом",25,"пор."]],"catalogOrder":47},{"id":"catalog-048","name":"Фуршетный набор № 30 Куриные шашлычки","category":0,"price":3790,"photo":"catalog/048.jpg","weight":"1120 г","catalogSection":"Тёплые блюда и выпечка","composition":["Куриные шашлычки — 560 г","Картофель бейби — 500 г","Соус — 60 г"],"ingredients":[["Куриные шашлычки",560,"г"],["Картофель бейби",500,"г"],["Соус",60,"г"]],"catalogOrder":48},{"id":"catalog-049","name":"Фуршетный бокс № 31 Свиной шашлычок","category":0,"price":3990,"photo":"catalog/049.jpg","weight":"1120 г","catalogSection":"Тёплые блюда и выпечка","composition":["Свиной шашлык — 560 г","Картофель бейби — 500 г","Соус — 60 г"],"ingredients":[["Свиной шашлык",560,"г"],["Картофель бейби",500,"г"],["Соус",60,"г"]],"catalogOrder":49},{"id":"catalog-050","name":"Фуршетный бокс № 32 Лосось на шпажке","category":0,"price":5690,"photo":"catalog/050.jpg","weight":"1120 г","catalogSection":"Тёплые блюда и выпечка","composition":["Шашлычки из лосося — 560 г","Картофель бейби — 500 г","Соус — 60 г"],"ingredients":[["Шашлычки из лосося",560,"г"],["Картофель бейби",500,"г"],["Соус",60,"г"]],"catalogOrder":50},{"id":"catalog-051","name":"Фуршетный бокс № 22 Фрукты","category":0,"price":3890,"photo":"catalog/051.jpg","weight":"1150 г","catalogSection":"Десерты и фрукты","composition":["16 шт. фрукты в ассортименте в креманке"],"ingredients":[["фрукты в ассортименте в креманке",16,"шт."]],"catalogOrder":51},{"id":"catalog-052","name":"Фуршетный бокс № 23 Слоёные десерты","category":0,"price":5690,"photo":"catalog/052.jpg","weight":"750 г","catalogSection":"Десерты и фрукты","composition":["12 шт. трайфл со сливочным бисквитом «Клубника со сливками»","13 шт. трайфл с шоколадным бисквитом «Сникерс» с орехами и карамелью"],"ingredients":[["трайфл со сливочным бисквитом «Клубника со сливками»",12,"шт."],["трайфл с шоколадным бисквитом «Сникерс» с орехами и карамелью",13,"шт."]],"catalogOrder":52},{"id":"catalog-053","name":"Фуршетный бокс № 35 Десерты","category":0,"price":3500,"photo":"catalog/053.jpg","weight":"750 г","catalogSection":"Десерты и фрукты","composition":["25 десертных порций ассорти","15 шт. одного вида","10 шт. второго вида"],"ingredients":[["25 десертных порций ассорти",1,"поз."],["одного вида",15,"шт."],["второго вида",10,"шт."]],"catalogOrder":53},{"id":"catalog-054","name":"Фуршетный бокс № 41 Сырники","category":0,"price":2690,"photo":"catalog/054.jpg","weight":"750 г","catalogSection":"Десерты и фрукты","composition":["25 шт. творожных сырников"],"ingredients":[["творожных сырников",25,"шт."]],"catalogOrder":54},{"id":"catalog-055","name":"Фуршетный бокс № 36 Десерты","category":0,"price":2990,"photo":"catalog/055.jpg","weight":"750 г","catalogSection":"Десерты и фрукты","composition":["25 десертных порций ассорти","10 шт. первого вида","10 шт. второго вида","5 шт. третьего вида"],"ingredients":[["25 десертных порций ассорти",1,"поз."],["первого вида",10,"шт."],["второго вида",10,"шт."],["третьего вида",5,"шт."]],"catalogOrder":55},{"id":"catalog-056","name":"Фуршетный бокс № 34 Десерты микс","category":0,"price":4690,"photo":"catalog/056.jpg","weight":"750 г","catalogSection":"Десерты и фрукты","composition":["Мафины","Бисквитный рулетик","Шоколадный вафельный торт","Бисквит с орехами и карамелью"],"ingredients":[["Мафины",1,"поз."],["Бисквитный рулетик",1,"поз."],["Шоколадный вафельный торт",1,"поз."],["Бисквит с орехами и карамелью",1,"поз."]],"catalogOrder":56},{"id":"catalog-057","name":"Морс","category":3,"price":450,"photo":"catalog/057.jpg","weight":"1 л","catalogSection":"Напитки","composition":["Морс ягодный"],"ingredients":[["Морс ягодный",1,"поз."]],"catalogOrder":57},{"id":"catalog-058","name":"Компот","category":3,"price":450,"photo":"catalog/058.jpg","weight":"1 л","catalogSection":"Напитки","composition":["Компот фруктовый"],"ingredients":[["Компот фруктовый",1,"поз."]],"catalogOrder":58},{"id":"catalog-059","name":"Готовое меню «Сытный» 1520 гостей","category":0,"price":19900,"photo":"catalog/059.jpg","weight":"5700 г","catalogSection":"Готовые меню","composition":["Бокс № 5: 25 мясных брускетт","Бокс № 8: 16 пор. Оливье с говядиной","Бокс № 10: 12 бургеров с говядиной и 13 с курицей","Бокс № 11: 25 сэндвичей с ветчиной и сыром","Бокс № 24: 10 пирожков с картошкой, 10 с мясом и кулебяка с капустой"],"ingredients":[["Бокс № 5: 25 мясных брускетт",1,"поз."],["Бокс № 8: 16 пор. Оливье с говядиной",1,"поз."],["Бокс № 10: 12 бургеров с говядиной и 13 с курицей",1,"поз."],["Бокс № 11: 25 сэндвичей с ветчиной и сыром",1,"поз."],["Бокс № 24: 10 пирожков с картошкой, 10 с мясом и кулебяка с капустой",1,"поз."]],"catalogOrder":59},{"id":"catalog-060","name":"Готовое меню «Корпоратив» 5070 гостей","category":0,"price":71640,"photo":"catalog/060.jpg","weight":"18540 г","catalogSection":"Готовые меню","composition":["Бокс № 1: 50 мясных канапе","Бокс № 2: 50 рыбных канапе","Бокс № 6: 50 сырных закусок в шотах","Бокс № 9: 32 пор. салата Цезарь","Бокс № 8: 32 пор. Мимозы с лососем","Бокс № 8: 32 пор. Оливье с говядиной","Бокс № 11: 25 сэндвичей с ветчиной и сыром","Бокс № 22: 32 пор. фруктов в креманке","Бокс № 23: 50 десертов ассорти"],"ingredients":[["Бокс № 1: 50 мясных канапе",1,"поз."],["Бокс № 2: 50 рыбных канапе",1,"поз."],["Бокс № 6: 50 сырных закусок в шотах",1,"поз."],["Бокс № 9: 32 пор. салата Цезарь",1,"поз."],["Бокс № 8: 32 пор. Мимозы с лососем",1,"поз."],["Бокс № 8: 32 пор. Оливье с говядиной",1,"поз."],["Бокс № 11: 25 сэндвичей с ветчиной и сыром",1,"поз."],["Бокс № 22: 32 пор. фруктов в креманке",1,"поз."],["Бокс № 23: 50 десертов ассорти",1,"поз."]],"catalogOrder":60}];
;
/* ===== MODULE: pdf-viewer-patch.js ===== */
(function(){
'use strict';
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const IMAGE_DATA=window.SUN_CATALOG_IMAGE_DATA||{};
const LOGO_DATA=window.SUN_LOGO_DATA||'';
let lastPdfUrl='';
function toast(message,type){
try{ if(window.SunEnterprise?.toast) return window.SunEnterprise.toast(message,type); }catch(_){}
console[type==='warn'?'warn':'log'](message);
}
function currentCategory(){
const buttons=qa('#new .sun-catalog-top-toolbar .cats button,#new .cats button');
const active=buttons.find(b=>b.style.outline&&b.style.outline!=='none')||buttons.find(b=>b.getAttribute('aria-current')==='true');
if(active?.dataset?.category!=null)return Number(active.dataset.category);
const title=document.querySelector('#new .catalog h1')?.textContent?.trim()||'';
const names=[['Боксы',0],['Премиум',5],['Посуда',1],['Дополнения',2],['Напитки',3],['Доставка',4]];
return names.find(([name])=>title.startsWith(name))?.[1]??0;
}
const categoryName=cat=>{try{if(typeof window.sunCatalogCategoryName==='function')return window.sunCatalogCategoryName(cat)}catch(_){}return ({0:'Боксы',1:'Посуда',2:'Дополнения',3:'Напитки',4:'Доставка',5:'Премиум'})[cat]||'Каталог'};
function allBoxes(){
try{if(Array.isArray(boxes))return boxes}catch(_){}
try{const saved=JSON.parse(localStorage.getItem('sunBoxes')||'[]');return Array.isArray(saved)?saved:[]}catch(_){return[]}
}
function ingredientWeight(row){if(!Array.isArray(row))return 0;const direct=Math.max(0,Number(row[3]||0));if(direct)return direct;const m=String(row[0]||'').match(/[—–-]\s*(\d+(?:[.,]\d+)?)\s*г(?:\s*\/\s*шт\.?)?\s*$/i);return m?Math.max(0,Number(String(m[1]).replace(',','.'))):0}
function cleanIngredientName(name){return String(name||'').replace(/\s*[—–-]\s*\d+(?:[.,]\d+)?\s*г(?:\s*\/\s*шт\.?)?\s*$/i,'').trim()}
function composition(item){
if(Array.isArray(item?.composition)&&item.composition.length)return item.composition.map(x=>String(x||'').trim()).filter(Boolean);
if(Array.isArray(item?.ingredients)&&item.ingredients.some(x=>Array.isArray(x)&&x[0]))return item.ingredients.filter(x=>Array.isArray(x)&&x[0]).map(x=>{const q=Math.max(0,Number(x[1]||0)),u=String(x[2]||'').trim(),name=cleanIngredientName(x[0]),w=ingredientWeight(x),shown=Number.isInteger(q)?String(q):q.toLocaleString('ru-RU',{maximumFractionDigits:2});if(/^поз\.?$/i.test(u)&&q<=1)return `${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`;return `${shown} ${u} ${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`.replace(/\s+/g,' ').trim()});
return [];
}
const money=value=>`${Number(value||0).toLocaleString('ru-RU')}`;
const mm=v=>v*5;
function roundedRect(ctx,x,y,w,h,r){
const rr=Math.min(r,w/2,h/2);
ctx.beginPath();ctx.moveTo(x+rr,y);ctx.arcTo(x+w,y,x+w,y+h,rr);ctx.arcTo(x+w,y+h,x,y+h,rr);ctx.arcTo(x,y+h,x,y,rr);ctx.arcTo(x,y,x+w,y,rr);ctx.closePath();
}
function wrapText(ctx,text,maxWidth,maxLines=20){
const words=String(text||'').replace(/\s+/g,' ').trim().split(' ').filter(Boolean), lines=[];
let line='';
for(const word of words){
const test=line?line+' '+word:word;
if(ctx.measureText(test).width<=maxWidth||!line) line=test;
else {lines.push(line);line=word;if(lines.length>=maxLines-1)break;}
}
if(line&&lines.length<maxLines)lines.push(line);
if(words.length&&lines.length===maxLines){
let last=lines[maxLines-1];
while(last.length>2&&ctx.measureText(last+'…').width>maxWidth)last=last.slice(0,-1);
lines[maxLines-1]=last.replace(/[\s,.;:-]+$/,'')+'…';
}
return lines;
}
function drawTextLines(ctx,lines,x,y,lineHeight){lines.forEach((line,i)=>ctx.fillText(line,x,y+i*lineHeight));}
function imageSource(src){
src=String(src||'').trim();
if(!src)return LOGO_DATA;
if(/^data:image\//i.test(src))return src;
return IMAGE_DATA[src]||IMAGE_DATA[src.replace(/^\.\//,'')]||(/^sun-logo\.png$/i.test(src)?LOGO_DATA:src);
}
function loadImage(src){
return new Promise(resolve=>{
const raw=imageSource(src),img=new Image();let done=false;
const finish=value=>{if(done)return;done=true;clearTimeout(timer);resolve(value)};
try{const url=new URL(raw,document.baseURI||location.href);if(/^https?:$/i.test(url.protocol)&&url.origin!==location.origin)img.crossOrigin='anonymous'}catch(_){}
img.onload=()=>finish(img);
img.onerror=()=>finish(null);
const timer=setTimeout(()=>finish(null),15000);
img.src=raw;
});
}
function drawContain(ctx,img,x,y,w,h,pad=0){
if(!img)return;
const iw=img.naturalWidth||img.width, ih=img.naturalHeight||img.height;
if(!iw||!ih)return;
const aw=Math.max(1,w-pad*2),ah=Math.max(1,h-pad*2),scale=Math.min(aw/iw,ah/ih);
const dw=iw*scale,dh=ih*scale;
ctx.drawImage(img,x+(w-dw)/2,y+(h-dh)/2,dw,dh);
}
async function renderPageJpeg(item,cat,index,total,dateText){
const W=1050,H=1485,canvas=document.createElement('canvas');canvas.width=W;canvas.height=H;
const ctx=canvas.getContext('2d',{alpha:false});
ctx.fillStyle='#f8f4eb';ctx.fillRect(0,0,W,H);
ctx.textBaseline='alphabetic';ctx.textAlign='left';
const logo=await loadImage('sun-logo.png');
if(logo)drawContain(ctx,logo,85,50,72,72,1);
ctx.fillStyle='#1f1f1d';ctx.font='800 36px Arial, sans-serif';ctx.fillText('СОЛНЦЕ КЕЙТЕРИНГ',177,83);
const subtitle=cat===0?'ПОЛНЫЙ КАТАЛОГ ФУРШЕТНЫХ БОКСОВ':`КАТАЛОГ ${categoryName(cat).toUpperCase()}`;
ctx.fillStyle='#88827a';ctx.font='600 16px Arial, sans-serif';ctx.fillText(subtitle,178,112);
ctx.textAlign='right';ctx.font='500 15px Arial, sans-serif';ctx.fillText(`${String(index+1).padStart(2,'0')} / ${String(total).padStart(2,'0')}`,965,69);ctx.textAlign='left';
ctx.fillStyle='#c99a32';ctx.fillRect(85,143,880,2);
const vx=85,vy=178,vw=880,vh=480;
ctx.fillStyle='#f0ebdf';roundedRect(ctx,vx,vy,vw,vh,20);ctx.fill();
const photo=await loadImage(item.photo||'sun-logo.png');
if(photo)drawContain(ctx,photo,vx,vy,vw,vh,item.photo?14:165);
const section=String(item.catalogSection||categoryName(cat)).toUpperCase();
ctx.font='800 17px Arial, sans-serif';
const sectionW=Math.min(720,Math.max(175,ctx.measureText(section).width+54));
ctx.fillStyle='#191919';roundedRect(ctx,115,201,sectionW,43,22);ctx.fill();
ctx.fillStyle='#fff';ctx.fillText(section,142,229);
const titleY=717;
ctx.fillStyle='#1f1f1d';ctx.font='800 36px Arial, sans-serif';
const nameLines=wrapText(ctx,item.name||'',650,2);drawTextLines(ctx,nameLines,85,titleY,41);
ctx.textAlign='right';ctx.fillStyle='#c99a32';ctx.font='800 37px Arial, sans-serif';ctx.fillText(money(item.price||0),965,titleY);ctx.textAlign='left';
const factsY=820;
ctx.fillStyle='#77736d';ctx.font='800 14px Arial, sans-serif';ctx.fillText('ВЕС',85,factsY);ctx.fillText('КОЛ-ВО',525,factsY);
ctx.fillStyle='#232321';ctx.font='800 19px Arial, sans-serif';ctx.fillText(String(item.weight||'—').toUpperCase(),85,factsY+31);ctx.fillText(Number(item.pieces||0)>0?`${Math.round(Number(item.pieces))} ШТ.`:'—',525,factsY+31);
ctx.fillStyle='#d9d0c2';ctx.fillRect(85,889,880,2);
ctx.fillStyle='#1f1f1d';ctx.font='800 23px Arial, sans-serif';ctx.fillText('СОСТАВ',85,939);
let lines=composition(item),rendered=[];
ctx.font='400 19px Arial, sans-serif';
for(const raw of lines){
const wrapped=wrapText(ctx,raw,795,2);
for(const line of wrapped){if(rendered.length<9)rendered.push(line)}
if(rendered.length>=9)break;
}
if(!rendered.length){ctx.fillStyle='#77736d';ctx.font='400 18px Arial, sans-serif';ctx.fillText('Состав не указан.',85,985)}
else{
let y=985;ctx.font='400 19px Arial, sans-serif';
for(const line of rendered){
ctx.fillStyle='#c99a32';ctx.beginPath();ctx.arc(93,y-6,5,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#33312e';ctx.fillText(line,113,y);y+=32;
}
}
ctx.fillStyle='#d9d0c2';ctx.fillRect(85,1400,880,2);
ctx.fillStyle='#88827a';ctx.font='400 14px Arial, sans-serif';ctx.fillText('+7 (921) 954-04-40 • solnce-keytering.ru',85,1435);
ctx.textAlign='right';ctx.fillText(`Цены актуальны на ${dateText}`,965,1435);ctx.textAlign='left';
let dataUrl;
try{dataUrl=canvas.toDataURL('image/jpeg',0.88)}catch(_){dataUrl=''}
if(!dataUrl)throw new Error('Не удалось сформировать страницу PDF.');
const binary=atob(dataUrl.split(',')[1]),bytes=new Uint8Array(binary.length);
for(let i=0;i<binary.length;i++)bytes[i]=binary.charCodeAt(i);
return {bytes,width:W,height:H};
}
const buildPdf=pages=>window.SunPdfEngine.fromJpegs(pages);
async function createCatalogPdfBlob(cat,progress){
const items=allBoxes().filter(item=>Number(item.category||0)===Number(cat));
if(!items.length)throw new Error('В этом разделе нет позиций.');
const pages=[],dateText=new Date().toLocaleDateString('ru-RU');
for(let i=0;i<items.length;i++){
progress?.(i+1,items.length);
pages.push(await renderPageJpeg(items[i],cat,i,items.length,dateText));
await new Promise(resolve=>setTimeout(resolve,0));
}
return buildPdf(pages);
}
function loadingPage(win,title){
try{
win.document.open();
win.document.write(`<!doctype html><meta charset="utf-8"><title>${title}</title><style>body{margin:0;display:grid;place-items:center;min-height:100vh;background:#f5f2eb;font:16px Arial;color:#15364c}.box{text-align:center;background:#fff;padding:28px 34px;border-radius:14px;box-shadow:0 10px 35px #0002}.sun{font-size:34px;color:#c99a32}.p{margin-top:10px;color:#68717a}</style><div class="box"><div class="sun">☼</div><b>Формируем актуальный PDF-каталог</b><div class="p" id="p">Подготовка…</div></div>`);
win.document.close();
}catch(_){}
}
function updateProgress(win,current,total){try{const p=win.document.getElementById('p');if(p)p.textContent=`Страница ${current} из ${total}`;}catch(_){} }
async function openCatalogPdfViewer(){
const cat=currentCategory();
if(cat===4)return;
const viewer=window.open('about:blank','_blank');
if(!viewer){toast('Браузер заблокировал новую вкладку. Разрешите всплывающие окна для приложения.','warn');return}
loadingPage(viewer,'Каталог — Солнце Кейтеринг');
try{
const blob=await createCatalogPdfBlob(cat,(n,t)=>updateProgress(viewer,n,t));
if(lastPdfUrl)URL.revokeObjectURL(lastPdfUrl);
lastPdfUrl=URL.createObjectURL(blob);
viewer.location.replace(lastPdfUrl);
}catch(err){
try{viewer.document.body.innerHTML=`<div style="font:16px Arial;padding:30px;color:#7a2e2e"><b>Не удалось открыть PDF.</b><p>${String(err?.message||err)}</p></div>`}catch(_){}
toast(err?.message||'Не удалось сформировать PDF.','warn');
}
}
function installButton(){
const head=document.querySelector('#new .catalog .catalog-head');if(!head)return;
const configure=qa('button',head).find(b=>(b.textContent||'').trim()==='Настроить');if(!configure)return;
let old=$('sunLiveCatalogPdfButton'),btn;
if(old){btn=old.cloneNode(true);old.replaceWith(btn)}
else{btn=document.createElement('button');btn.id='sunLiveCatalogPdfButton';btn.type='button';btn.className='outline';configure.insertAdjacentElement('beforebegin',btn)}
btn.textContent='PDF';
btn.title='Открыть актуальный PDF-каталог. В просмотрщике браузера можно скачать или распечатать.';
btn.onclick=openCatalogPdfViewer;
const hide=currentCategory()===4;btn.hidden=hide;btn.style.display=hide?'none':'inline-flex';
}
window.sunOpenCatalogPdfViewer=openCatalogPdfViewer;
window.sunBuildCatalogPdfBlob=()=>createCatalogPdfBlob(currentCategory());
window.sunCatalogDebugPdfPages=async(cat=currentCategory(),limit=3)=>{const items=allBoxes().filter(item=>Number(item.category||0)===Number(cat)).slice(0,Math.max(1,Number(limit||3)));const dateText=new Date().toLocaleDateString('ru-RU'),pages=[];for(let i=0;i<items.length;i++)pages.push(await renderPageJpeg(items[i],cat,i,items.length,dateText));return pages};
installButton();setTimeout(installButton,150);setTimeout(installButton,800);
document.addEventListener('click',e=>{if(e.target.closest('#new .cats button,#new .sun-catalog-top-toolbar .cats button'))setTimeout(installButton,30)},true);
const catalog=document.querySelector('#new .catalog');if(catalog&&'MutationObserver'in window)new MutationObserver(()=>{const b=$('sunLiveCatalogPdfButton');if(!b||b.textContent!=='PDF'||b.onclick!==openCatalogPdfViewer)queueMicrotask(installButton)}).observe(catalog,{childList:true,subtree:true});
})();
;
/* ===== MODULE: feature-pack-v1.js ===== */
(function(){
'use strict';
if(window.__sunFeaturePackV1)return; window.__sunFeaturePackV1=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const esc=window.SunSafe.escapeHTML;
const money=v=>`${Math.round(Number(v||0)).toLocaleString('ru-RU')}`;
const toast=(m,t='success')=>{try{return window.SunEnterprise?.toast?.(m,t)}catch(_){}; if(t==='warn'||t==='error')console.warn(m);};
const style=document.createElement('style');
style.id='sun-feature-pack-v1-style';
style.textContent=`
#new .catalog-head{gap:8px;flex-wrap:wrap}
#new .sun-catalog-search{height:35px;min-width:180px;width:min(235px,28vw);padding:6px 10px;border:1px solid #d9dde0;border-radius:8px;background:#fff;color:#203040}
#new .sun-catalog-tabs-button{white-space:nowrap}
#manager .sun-manager-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 12px}
#manager .sun-manager-toolbar .primary{margin:0}
#manager .sun-manager-search{flex:1 1 220px;min-width:180px}
.sun-category-list{display:grid;gap:8px;margin-top:12px}
.sun-category-row{display:grid;grid-template-columns:minmax(180px,1fr) 58px auto auto;gap:8px;align-items:center;padding:9px;border:1px solid #e0e4e8;border-radius:10px;background:#fff}
.sun-category-row.is-hidden{opacity:.6;background:#f6f7f8}
.sun-category-row input{width:100%}.sun-category-color{display:grid;place-items:center;margin:0}.sun-category-color input{width:42px;height:34px;padding:2px;border:1px solid #d9dde0;border-radius:8px;background:#fff;cursor:pointer}
.sun-category-row small{color:#7b858d;white-space:nowrap}
.sun-category-row-actions{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}
.sun-category-add{display:grid;grid-template-columns:1fr auto;gap:8px;margin-top:14px;padding-top:12px;border-top:1px solid #e6e8eb}
.sun-order-requisites{display:none;margin-top:4px}
.sun-order-requisites-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.sun-order-requisites-grid .wide{grid-column:1/-1}
.sun-order-requisites h3{margin:6px 0 12px;color:#15364c}
.sun-order-qr-box{display:grid;grid-template-columns:110px minmax(0,1fr);gap:14px;align-items:center;margin-top:16px;padding:12px;border:1px solid #e1e4e7;border-radius:12px;background:#fafbfc}
.sun-order-qr-preview{width:100px;height:100px;border:1px dashed #c7cdd2;border-radius:8px;background:#fff;display:grid;place-items:center;overflow:hidden;color:#8a949b;font-size:11px;text-align:center;padding:5px}
.sun-order-qr-preview img{width:100%;height:100%;object-fit:contain}
.sun-order-qr-actions{display:flex;gap:7px;flex-wrap:wrap;margin-top:8px}
.sun-discount-panel{display:grid;grid-template-columns:1fr;gap:10px;margin-top:10px;padding:11px;border:1px solid #e3e6e9;border-radius:10px;background:#fafbfc}
.sun-discount-combo{display:grid;grid-template-columns:minmax(0,1fr) 78px;gap:6px}
.sun-promo-select-wrap{display:grid;gap:5px}
.sun-promo-select-wrap select{width:100%}
.sun-promo-meta{font-size:12px;color:#68717a;line-height:1.35;min-height:16px}
.sun-discount-panel label>span{display:block;color:#68717a;font-size:12px;margin-bottom:5px}
.sun-order-summary-discount{color:#8a5b00}
.sun-order-summary-net{font-weight:800;color:#15364c}
.sun-order-summary-promo small{color:#7b858d;margin-left:5px}
.sun-doc-qr{width:92px;height:92px;object-fit:contain;border:1px solid #e0e3e5;border-radius:7px;padding:4px;background:#fff}
.sun-doc-title-with-qr{display:flex;align-items:flex-start;gap:14px;justify-content:flex-end}
.sun-doc-title-with-qr .sun-doc-title{text-align:right}
.sun-doc-qr-wrap{display:flex;align-items:center;gap:9px;max-width:295px}
.sun-doc-qr-note{min-width:115px;max-width:180px;padding:7px 8px;border:1px dashed #c8cdd2;border-radius:8px;background:#fff;color:#4f5962;font-size:11px;line-height:1.35;text-align:left;white-space:pre-wrap;overflow-wrap:anywhere}
.sun-doc-qr-note:empty:before{content:attr(data-placeholder);color:#9aa1a8}
.sun-doc-qr-note:focus{outline:2px solid #d8b766;outline-offset:1px;border-color:transparent}
@media print{.sun-doc-qr-note{border:0!important;padding:0!important;background:transparent!important;outline:0!important}.sun-doc-qr-note:empty{display:none!important}}
.sun-receipt .sun-receipt-party{margin:4px 0;padding:6px 8px;border-radius:6px}
.sun-receipt .sun-receipt-party h3{font-size:11px;margin:0 0 4px}
.sun-receipt .sun-receipt-party-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:2px 10px;font-size:9.5px}
.sun-receipt .sun-receipt-party-grid div{padding:1px 0;border-bottom:0;line-height:1.15}
.sun-receipt .sun-receipt-party-grid span{font-size:7.5px;margin-bottom:1px}
.sun-receipt .sun-receipt-party-grid b{font-size:9.5px;line-height:1.15}
#sunReceiptSettingsCard .sun-buyer-settings-hidden{display:none!important}
.sun-default-qr-setting{display:grid;grid-template-columns:100px minmax(0,1fr);gap:14px;align-items:center}
.sun-default-qr-setting .preview{width:92px;height:92px;border:1px dashed #cbd1d6;border-radius:8px;background:#fff;display:grid;place-items:center;overflow:hidden;color:#8a949b;font-size:11px;text-align:center;padding:4px}
.sun-default-qr-setting .preview img{width:100%;height:100%;object-fit:contain}
.sun-default-qr-setting .actions{justify-content:flex-start;margin-top:8px}
.sun-promo-stats{margin-top:18px;border:1px solid #e0e4e8;border-radius:12px;background:#fff;padding:14px}
.sun-promo-stats h2{margin:0 0 4px;color:#15364c}
.sun-promo-stats table{width:100%;border-collapse:collapse;margin-top:12px}
.sun-promo-stats th,.sun-promo-stats td{padding:8px;border-bottom:1px solid #e8eaec;text-align:left}
.sun-promo-stats th{font-size:12px;color:#68717a;background:#fafbfc}.sun-promo-stats td.num,.sun-promo-stats th.num{text-align:right}
.sun-box-weight-field,.sun-box-pieces-field{margin-top:8px}.sun-box-weight-field input,.sun-box-pieces-field input{max-width:220px}.sun-box-pieces-field small{display:block;margin-top:3px;color:#7b858d;line-height:1.3}.sun-ingredient-head,.ingredient.sun-ingredient-v175{display:grid;grid-template-columns:minmax(220px,1fr) 88px 78px 112px 34px;gap:6px;align-items:center}.sun-ingredient-head{margin:8px 0 4px;color:#68717a;font-size:11px;font-weight:700}.sun-ingredient-head span:nth-child(n+2){text-align:center}.ingredient.sun-ingredient-v175 input{text-align:left;min-width:0}.ingredient.sun-ingredient-v175 input[type=number]{text-align:center}.sun-order-line-name small{display:block;margin-top:3px;color:#7b858d;font-size:10px;font-weight:600;line-height:1.25}.sun-order-line-name small b{color:#53616d}.sun-doc .doc-sub.sun-doc-weight{margin-top:2px;color:#66727c;font-weight:600}
@media(max-width:850px){.sun-receipt .sun-receipt-party-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:650px){#new .sun-catalog-search{width:100%;flex:1 1 100%}.sun-order-requisites-grid{grid-template-columns:1fr}.sun-order-qr-box,.sun-default-qr-setting{grid-template-columns:1fr}.sun-category-row{grid-template-columns:1fr}.sun-category-row-actions{justify-content:flex-start}.sun-ingredient-head{display:none}.ingredient.sun-ingredient-v175{grid-template-columns:minmax(0,1fr) 64px 58px 84px 32px}.ingredient.sun-ingredient-v175 input{padding:7px 5px;font-size:12px}}
@media print{.sun-doc-title-with-qr{gap:8mm}.sun-doc-qr{width:25mm;height:25mm}.sun-receipt .sun-receipt-party{padding:1.5mm 2mm;margin:1mm 0}.sun-receipt .sun-receipt-party-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:.5mm 2mm}}
`;
document.head.appendChild(style);
// ---------------- Catalog categories + search ----------------
const CAT_KEY='sunCatalogCategoriesV2';
const defaultCats=[
{id:0,name:'Боксы',accent:'#d49a2a',prep:true,hidden:false,order:0},
{id:5,name:'Премиум',accent:'#7a5aa6',prep:true,hidden:false,order:1},
{id:3,name:'Напитки',accent:'#4f9bd8',prep:false,hidden:false,order:2},
{id:2,name:'Дополнения',accent:'#56a889',prep:false,hidden:false,order:3},
{id:1,name:'Посуда',accent:'#a78bfa',prep:false,hidden:false,order:4},
{id:4,name:'Доставка',accent:'#cf9f83',prep:false,hidden:false,order:5}
];
function readCats(){
let saved=[];try{saved=JSON.parse(localStorage.getItem(CAT_KEY)||'[]');if(!Array.isArray(saved))saved=[]}catch(_){saved=[]}
const map=new Map(saved.filter(x=>x&&Number.isFinite(Number(x.id))).map(x=>[Number(x.id),x]));
defaultCats.forEach(d=>{if(!map.has(d.id))map.set(d.id,d);else map.set(d.id,{...d,...map.get(d.id),id:d.id})});
try{(Array.isArray(boxes)?boxes:[]).forEach(item=>{const id=Number(item?.category||0);if(Number.isFinite(id)&&!map.has(id))map.set(id,{id,name:`Вкладка ${id}`,accent:'#708090',prep:true,hidden:false,order:map.size})})}catch(_){}
const arr=[...map.values()].map((c,i)=>({id:Number(c.id),name:String(c.name||`Вкладка ${i+1}`).trim()||`Вкладка ${i+1}`,accent:c.accent||'#708090',prep:c.prep!==false,hidden:Boolean(c.hidden),order:Number.isFinite(Number(c.order))?Number(c.order):i}));
arr.sort((a,b)=>a.order-b.order||a.id-b.id);arr.forEach((c,i)=>c.order=i);return arr;
}
let catalogCats=readCats();
const CAT_LAYOUT_VERSION_KEY='sunCatalogCategoryLayoutVersion';
const CAT_LAYOUT_VERSION='2026-09-02-premium-between-boxes-and-drinks';
if(localStorage.getItem(CAT_LAYOUT_VERSION_KEY)!==CAT_LAYOUT_VERSION){const desired=[0,5,3,2,1,4];const rank=new Map(desired.map((id,i)=>[id,i]));catalogCats.sort((a,b)=>(rank.has(a.id)?rank.get(a.id):100+a.order)-(rank.has(b.id)?rank.get(b.id):100+b.order));catalogCats.forEach((c,i)=>c.order=i);try{localStorage.setItem(CAT_KEY,JSON.stringify(catalogCats));localStorage.setItem(CAT_LAYOUT_VERSION_KEY,CAT_LAYOUT_VERSION)}catch(_){}}
function saveCats(){catalogCats.sort((a,b)=>a.order-b.order||a.id-b.id).forEach((c,i)=>c.order=i);try{localStorage.setItem(CAT_KEY,JSON.stringify(catalogCats))}catch(_){}window.dispatchEvent(new CustomEvent('sun:catalog-categories-changed'));}
function tabTextColor(value){const hex=String(value||'').trim();const m=/^#([0-9a-f]{6})$/i.exec(hex);if(!m)return '#fff';const n=parseInt(m[1],16),r=(n>>16)&255,g=(n>>8)&255,b=n&255;return (r*299+g*587+b*114)>160000?'#15364c':'#fff'}
const catById=id=>catalogCats.find(c=>Number(c.id)===Number(id));
const catName=id=>catById(id)?.name||'Каталог';
const visibleCats=()=>catalogCats.filter(c=>!c.hidden).sort((a,b)=>a.order-b.order);
let activeCat=visibleCats()[0]?.id??0;
let catalogQuery='';
let managerQuery='';
window.sunCatalogCategoryName=catName;
window.sunCatalogCategories=()=>catalogCats.map(c=>({...c}));
window.sunCurrentCatalogCategory=()=>activeCat;
function ensureCategoryModal(){
if($('sunCategoryManager'))return;
const modal=document.createElement('div');modal.id='sunCategoryManager';modal.className='modal';
modal.innerHTML=`<div class="dialog"><div class="dialog-head"><div><h2>Вкладки каталога</h2><p class="hint" style="margin:4px 0 0">Скрытая вкладка и её товары не удаляются — её можно вернуть.</p></div><button type="button" onclick="closeModal('sunCategoryManager')">×</button></div><div id="sunCategoryRows" class="sun-category-list"></div><div class="sun-category-add"><input id="sunNewCategoryName" placeholder="Название новой вкладки"><button id="sunAddCategoryBtn" class="primary" type="button">Добавить вкладку</button></div></div>`;
document.body.appendChild(modal);
$('sunAddCategoryBtn').onclick=()=>{
const name=$('sunNewCategoryName').value.trim();if(!name)return;
const nextId=Math.max(4,...catalogCats.map(c=>Number(c.id)||0))+1;
catalogCats.push({id:nextId,name,accent:'#708090',prep:true,hidden:false,order:catalogCats.length});saveCats();$('sunNewCategoryName').value='';renderCategoryManager();renderCategoryTabs();syncEditorCategoryOptions();
};
}
function renderCategoryManager(){
ensureCategoryModal();const root=$('sunCategoryRows');if(!root)return;
root.innerHTML=catalogCats.slice().sort((a,b)=>a.order-b.order).map(c=>{
const count=(typeof boxes!=='undefined'?boxes:[]).filter(b=>Number(b.category||0)===Number(c.id)).length;
return `<div class="sun-category-row ${c.hidden?'is-hidden':''}" data-cat-row="${c.id}"><input value="${esc(c.name)}" data-cat-name="${c.id}" aria-label="Название вкладки"><label class="sun-category-color" title="Цвет вкладки"><input type="color" value="${esc(c.accent||'#708090')}" data-cat-color="${c.id}" aria-label="Цвет вкладки"></label><small>${count} поз.</small><div class="sun-category-row-actions"><button class="outline" type="button" data-cat-up="${c.id}" title="Выше">↑</button><button class="outline" type="button" data-cat-down="${c.id}" title="Ниже">↓</button><button class="outline" type="button" data-cat-toggle="${c.id}">${c.hidden?'Восстановить':'Скрыть'}</button></div></div>`;
}).join('');
qa('[data-cat-name]',root).forEach(input=>input.onchange=()=>{const c=catById(input.dataset.catName);if(!c)return;c.name=input.value.trim()||c.name;saveCats();renderCategoryTabs();syncEditorCategoryOptions();renderCatalogV5();renderCategoryManager();});
qa('[data-cat-color]',root).forEach(input=>{input.oninput=()=>{const c=catById(input.dataset.catColor);if(!c)return;c.accent=input.value||c.accent;renderCategoryTabs();};input.onchange=()=>{const c=catById(input.dataset.catColor);if(!c)return;c.accent=input.value||c.accent;saveCats();renderCategoryTabs();};});
qa('[data-cat-toggle]',root).forEach(btn=>btn.onclick=()=>{const c=catById(btn.dataset.catToggle);if(!c)return;c.hidden=!c.hidden;if(c.hidden&&Number(activeCat)===Number(c.id))activeCat=visibleCats().find(x=>Number(x.id)!==Number(c.id))?.id??0;saveCats();renderCategoryTabs();syncEditorCategoryOptions();renderCatalogV5();renderCategoryManager();});
const move=(id,dir)=>{const arr=catalogCats.slice().sort((a,b)=>a.order-b.order),i=arr.findIndex(c=>Number(c.id)===Number(id)),j=i+dir;if(i<0||j<0||j>=arr.length)return;[arr[i],arr[j]]=[arr[j],arr[i]];arr.forEach((c,k)=>c.order=k);catalogCats=arr;saveCats();renderCategoryTabs();renderCategoryManager();};
qa('[data-cat-up]',root).forEach(btn=>btn.onclick=()=>move(btn.dataset.catUp,-1));qa('[data-cat-down]',root).forEach(btn=>btn.onclick=()=>move(btn.dataset.catDown,1));
}
window.sunOpenCategoryManager=()=>{renderCategoryManager();window.modal?.('sunCategoryManager');};
function renderCategoryTabs(){
const panel=document.querySelector('#new .cats');if(!panel)return;
const visible=visibleCats();if(!visible.some(c=>Number(c.id)===Number(activeCat)))activeCat=visible[0]?.id??0;
panel.innerHTML=visible.map(c=>`<button type="button" data-category="${c.id}" aria-current="${Number(c.id)===Number(activeCat)?'true':'false'}">${esc(c.name)}</button>`).join('')+'<p>Выберите раздел. Любую позицию можно добавить, изменить или удалить.</p>';
qa('button',panel).forEach(btn=>{const c=catById(btn.dataset.category),on=Number(btn.dataset.category)===Number(activeCat);btn.style.background=c?.accent||'#708090';btn.style.color=tabTextColor(c?.accent||'#708090');btn.style.opacity=on?'1':'.72';btn.style.outline=on?'2px solid #15364c':'none';btn.style.outlineOffset=on?'2px':'0';btn.onclick=()=>{activeCat=Number(btn.dataset.category);catalogQuery='';const search=$('sunCatalogSearch');if(search)search.value='';renderCategoryTabs();renderCatalogV5();};});
}
function syncEditorCategoryOptions(){
const select=$('itemCategory');if(!select)return;const current=select.value;
select.innerHTML=catalogCats.slice().sort((a,b)=>a.order-b.order).map(c=>`<option value="${c.id}">${esc(c.name)}${c.hidden?' (скрыта)':''}</option>`).join('');
if([...select.options].some(o=>o.value===current))select.value=current;
}
function ensureCatalogTools(){
const head=document.querySelector('#new .catalog .catalog-head');if(!head)return;
const configure=qa('button',head).find(b=>(b.textContent||'').trim()==='Настроить');if(!configure)return;
let search=$('sunCatalogSearch');if(!search){search=document.createElement('input');search.id='sunCatalogSearch';search.className='sun-catalog-search';search.type='search';search.placeholder='Поиск по № бокса';search.inputMode='numeric';search.oninput=()=>{catalogQuery=search.value;renderCatalogV5();};configure.insertAdjacentElement('beforebegin',search)}
let tabs=$('sunCatalogTabsButton');if(!tabs){tabs=document.createElement('button');tabs.id='sunCatalogTabsButton';tabs.className='outline sun-catalog-tabs-button';tabs.type='button';tabs.textContent='Вкладки';tabs.onclick=window.sunOpenCategoryManager;configure.insertAdjacentElement('beforebegin',tabs)}
}
function lineUnitPrice(line){const direct=Number(line?.price);if(Number.isFinite(direct)&&direct>=0)return direct;const item=boxes.find(x=>String(x.id)===String(line?.id));return Math.max(0,Number(item?.price||0));}
function baseTotal(order){return (order?.lines||[]).reduce((s,l)=>s+lineUnitPrice(l)*Math.max(0,Number(l.qty||0)),0)}
window.sunBaseOrderTotal=baseTotal;
function parseWeightGrams(value){const raw=String(value??'').trim().toLowerCase().replace(',','.');if(!raw)return 0;const m=raw.match(/(\d+(?:\.\d+)?)/);if(!m)return 0;let n=Math.max(0,Number(m[1]||0));if(/кг/.test(raw))n*=1000;return Math.round(n)}
const pieceUnitRe=/^(?:шт\.?|пор\.?|кус\.?|шарик(?:а|ов|и)?|креманк(?:а|и|е)?)$/i;
function ingredientUnitWeight(row){if(!Array.isArray(row))return 0;const direct=Math.max(0,Number(row[3]||0));if(direct)return direct;const m=String(row[0]||'').match(/[—–-]\s*(\d+(?:[.,]\d+)?)\s*г(?:\s*\/\s*шт\.?)?\s*$/i);return m?Math.max(0,Number(String(m[1]).replace(',','.'))):0}
function cleanIngredientName(name){return String(name||'').replace(/\s*[—–-]\s*\d+(?:[.,]\d+)?\s*г(?:\s*\/\s*шт\.?)?\s*$/i,'').trim()}
function inferBoxPieces(item){const explicit=Math.max(0,Number(item?.pieces||0));if(explicit)return Math.round(explicit);let sum=0;for(const row of item?.ingredients||[]){if(!Array.isArray(row))continue;const unit=String(row[2]||'').trim().toLowerCase();if(pieceUnitRe.test(unit))sum+=Math.max(0,Number(row[1]||0));}return Math.round(sum)}
function ingredientDisplayLine(row){if(!Array.isArray(row)||!String(row[0]||'').trim())return'';const name=cleanIngredientName(row[0]),qty=Math.max(0,Number(row[1]||0)),unit=String(row[2]||'').trim(),w=ingredientUnitWeight(row);if(/^поз\.?$/i.test(unit)&&qty<=1)return `${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`;const q=Number.isInteger(qty)?String(qty):qty.toLocaleString('ru-RU',{maximumFractionDigits:2});return `${q} ${unit} ${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`.replace(/\s+/g,' ').trim()}
function normalizeBoxItem(item,{cleanup=false}={}){if(!item||![0,5].includes(Number(item.category||0)))return item;const legacyComposition=Array.isArray(item.composition)?item.composition.slice():[];item.ingredients=Array.isArray(item.ingredients)?item.ingredients.filter(r=>Array.isArray(r)&&String(r[0]||'').trim()).map((r,index)=>{const row=[String(r[0]||'').trim(),Math.max(0,Number(r[1]||0)),String(r[2]||'').trim()||'шт.',Math.max(0,Number(r[3]||0))];if(!row[3])row[3]=ingredientUnitWeight(r)||ingredientUnitWeight([legacyComposition[index]||'',0,'',0]);return row;}):[];
// Box №18 had a total row "25 шт. тостов ассорти" mixed into two real varieties. The total belongs in pieces, not in composition.
if(String(item.id)==='catalog-013'||/№\s*18\b.*тосты\s+ассорти/i.test(String(item.name||''))){const idx=item.ingredients.findIndex(r=>/тостов?\s+ассорти/i.test(String(r[0]||''))&&pieceUnitRe.test(String(r[2]||''))&&Number(r[1]||0)>1);if(idx>=0){item.pieces=Math.max(Number(item.pieces||0),Number(item.ingredients[idx][1]||0));item.ingredients.splice(idx,1);}}
if(!Number(item.pieces||0))item.pieces=inferBoxPieces(item);
if((!Array.isArray(item.composition)||!item.composition.filter(Boolean).length)&&item.ingredients.length)item.composition=item.ingredients.map(ingredientDisplayLine).filter(Boolean);
return item;}
function weightText(item,prefix=true){const raw=String(item?.weight||'').trim();if(!raw)return'';return prefix?`Вес бокса: ${raw}`:raw}
function piecesText(item,prefix=true){const n=inferBoxPieces(item);if(!n)return'';return prefix?`Количество: ${n.toLocaleString('ru-RU')} шт.`:`${n.toLocaleString('ru-RU')} шт.`}
function ensureBoxWeightEditor(){
const price=$('boxPrice'),dialog=document.querySelector('#editor .dialog');if(!price||!dialog)return null;
let label=$('sunBoxWeightLabel');
if(!label){label=document.createElement('label');label.id='sunBoxWeightLabel';label.className='sun-box-weight-field';label.innerHTML='<span>Вес бокса, г</span><input id="boxWeightGrams" type="number" min="0" step="1" inputmode="numeric" placeholder="Например, 600">';price.closest('label')?.insertAdjacentElement('afterend',label)}
return label;
}
function ensureBoxPiecesEditor(){const weight=ensureBoxWeightEditor(),dialog=document.querySelector('#editor .dialog');if(!dialog)return null;let label=$('sunBoxPiecesLabel');if(!label){label=document.createElement('label');label.id='sunBoxPiecesLabel';label.className='sun-box-pieces-field';label.innerHTML='<span>Количество канапе / шт. в боксе</span><input id="boxPiecesCount" type="number" min="0" step="1" inputmode="numeric" placeholder="Например, 30"><small>Это общее количество готовых единиц в одном боксе. Не добавляйте его отдельной строкой в «Состав / ТТК».</small>';weight?.insertAdjacentElement('afterend',label)}return label}
function syncBoxWeightEditor(category,item){const label=ensureBoxWeightEditor(),input=$('boxWeightGrams'),piecesLabel=ensureBoxPiecesEditor(),piecesInput=$('boxPiecesCount');if(!label||!input)return;const isBox=[0,5].includes(Number(category));label.style.display=isBox?'flex':'none';if(piecesLabel)piecesLabel.style.display=isBox?'flex':'none';if(isBox){input.value=parseWeightGrams(item?.weight)||'';if(piecesInput)piecesInput.value=inferBoxPieces(item)||''}}
function installIngredientEditorV175(){window.renderIngredients=()=>{if(!edited)return;normalizeBoxItem(edited);const root=$('ingredients');if(!root)return;const head='<div class="sun-ingredient-head"><span>Вид / название</span><span>Кол-во</span><span>Ед.</span><span>Вес 1 шт., г</span><span></span></div>';root.innerHTML=head+edited.ingredients.map((row,i)=>{const w=ingredientUnitWeight(row);return `<div class="ingredient sun-ingredient-v175"><input value="${esc(cleanIngredientName(row[0]))}" onchange="edited.ingredients[${i}][0]=this.value"><input type="number" min="0" step="0.01" value="${Number(row[1]||0)}" onchange="edited.ingredients[${i}][1]=+this.value"><input value="${esc(row[2]||'шт.')}" onchange="edited.ingredients[${i}][2]=this.value"><input type="number" min="0" step="0.1" inputmode="decimal" placeholder="г" value="${w||''}" onchange="edited.ingredients[${i}][3]=Math.max(0,+this.value||0)"><button type="button" onclick="edited.ingredients.splice(${i},1);renderIngredients()">×</button></div>`}).join('')};window.addIngredient=()=>{if(!edited)return;if(!Array.isArray(edited.ingredients))edited.ingredients=[];edited.ingredients.push(['Новая позиция',1,'шт.',0]);window.renderIngredients();};}
installIngredientEditorV175();
function renderOrderLines(){
if(!$('lines'))return;
if(!(draft.lines||[]).length){$('lines').innerHTML='<p class="empty">Выберите позиции из каталога слева.</p>';return}
const rows=draft.lines.map((line,index)=>{const item=boxes.find(x=>String(x.id)===String(line.id));if(!item)return'';const qty=Math.max(1,Number(line.qty||1)),price=lineUnitPrice(line),isBox=[0,5].includes(Number(item.category||0));return `<div class="sun-order-line-row" data-line-index="${index}"><div class="sun-order-line-name" title="${esc(item.name)}">${esc(String(item.name||'').replace(/^(?:Фуршетный\s+)?бокс\s*№\s*(\d+)\s*(?:[—–-]\s*)?/i,'№ $1 '))}${isBox&&(item.weight||inferBoxPieces(item))?`<small>${item.weight?`<b>Вес:</b> ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`<b>Количество:</b> ${inferBoxPieces(item)} шт.`:''}</small>`:''}</div><div class="sun-order-line-qty"><button type="button" onclick="qty('${esc(line.id)}',${qty-1})"></button><input type="number" min="1" value="${qty}" onchange="qty('${esc(line.id)}',+this.value)"><button type="button" onclick="qty('${esc(line.id)}',${qty+1})"></button></div><div class="sun-order-line-price"><input type="text" inputmode="numeric" pattern="[0-9]*" autocomplete="off" value="${Math.round(price)}" onchange="sunOrderLinePriceChanged(${index},this)"></div><div class="sun-order-line-sum"><b>${money(price*qty)}</b></div></div>`}).join('');
$('lines').innerHTML=`<div class="sun-order-lines-table"><div class="sun-order-line-head"><span>Наименование</span><span>Кол.</span><span>Цена</span><span>Стоимость</span></div><div class="sun-order-line-body">${rows}</div><div class="sun-order-line-total"><span>Стоимость позиций</span><b>${money(baseTotal(draft))}</b></div></div>`;
}
window.sunOrderLinePriceChanged=(index,input)=>{const line=draft.lines?.[index];if(!line)return;line.price=Math.max(0,Math.round(Number(input.value||0)));const row=input.closest('.sun-order-line-row'),sum=row?.querySelector('.sun-order-line-sum b');if(sum)sum.textContent=money(line.price*Math.max(0,Number(line.qty||0)));const total=$('lines')?.querySelector('.sun-order-line-total b');if(total)total.textContent=money(baseTotal(draft));updateOrderSummary();};
function boxNumber(item){const explicit=Number(item?.boxNumber);if(Number.isInteger(explicit)&&explicit>=0)return explicit;const name=String(item?.name||'');const match=name.match(/(?:бокс\s*)?№\s*0*(\d+)/i)||name.match(/^\s*№\s*0*(\d+)/i);return match?Number(match[1]):null}
function wantedBoxNumber(query){const raw=String(query||'').trim();if(!raw)return null;const match=raw.match(/^(?:бокс\s*)?(?:№\s*)?0*(\d+)\s*$/i);return match?Number(match[1]):null}
function itemMatches(item,query){if(!query)return true;if(Number(activeCat)===0){const wanted=wantedBoxNumber(query);return wanted!==null&&boxNumber(item)===wanted}const q=query.trim().toLowerCase();const parts=[item.name,item.catalogSection,item.weight,...(Array.isArray(item.composition)?item.composition:[]),...(Array.isArray(item.ingredients)?item.ingredients.map(x=>x?.[0]):[])];return parts.join(' ').toLowerCase().includes(q)}
function renderCatalogV5(){
renderCategoryTabs();ensureCatalogTools();const catalogSearch=$('sunCatalogSearch');if(catalogSearch){const boxMode=[0,5].includes(Number(activeCat));catalogSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';catalogSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#new .catalog h1');if(title)title.textContent=catName(activeCat);
const all=boxes.filter(item=>Number(item.category||0)===Number(activeCat)),items=all.filter(item=>itemMatches(item,catalogQuery));const cat=catById(activeCat)||{name:'Каталог',prep:true};
const cards=items.map(item=>`<button class="tile" type="button" onclick="add('${esc(item.id)}')" title="Добавить в заказ">${item.photo?`<img src="${esc(item.photo)}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='sun-logo.png'">`:'<div class="ph"><img src="sun-logo.png" class="sun-ph-logo" alt="Логотип Солнце Кейтеринг"></div>'}<span>${esc(item.name)}</span>${item.catalogSection?`<small class="tile-section">${esc(item.catalogSection)}</small>`:''}${(item.weight||inferBoxPieces(item))?`<small class="tile-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}<span class="tile-price-wrap"><small class="tile-price">${money(item.price||0)}</small>${Number(item.oldPrice||0)>Number(item.price||0)?`<small class="old-price">${money(item.oldPrice)}</small>`:''}</span>${Number(item.oldPrice||0)>Number(item.price||0)?`<span class="sale-badge">АКЦИЯ</span>`:''}</button>`).join('');
const addText=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию');
const empty=items.length?'':`<div class="catalog-empty">${catalogQuery?'По вашему запросу ничего не найдено.':`В разделе «${esc(cat.name)}» пока нет позиций.`}</div>`;
$('tiles').innerHTML=`<button class="tile add" type="button" onclick="editBox(null)"><br>${addText}</button>${cards}${empty}`;
renderOrderLines();updateOrderSummary();
}
window.render=renderCatalogV5;
window.add=id=>{const item=boxes.find(x=>String(x.id)===String(id));if(!item)return;let line=(draft.lines||[]).find(x=>String(x.id)===String(id));if(line)line.qty=Math.max(0,Number(line.qty||0))+1;else{if(!Array.isArray(draft.lines))draft.lines=[];draft.lines.push({id:item.id,qty:1,price:Math.max(0,Number(item.price||0))})}renderCatalogV5();};
window.qty=(id,n)=>{if(!Array.isArray(draft.lines))draft.lines=[];const next=Math.max(0,Number(n||0));if(next<1)draft.lines=draft.lines.filter(x=>String(x.id)!==String(id));else{const line=draft.lines.find(x=>String(x.id)===String(id));if(line)line.qty=next}renderCatalogV5();};
function ensureManagerToolbar(){
const dialog=document.querySelector('#manager .dialog');if(!dialog)return;let bar=dialog.querySelector('.sun-manager-toolbar');const add=qa(':scope > .primary',dialog)[0]||qa('.primary',dialog).find(b=>(b.textContent||'').includes('Добавить'));
if(!bar&&add){bar=document.createElement('div');bar.className='sun-manager-toolbar';add.parentNode.insertBefore(bar,add);bar.appendChild(add);const input=document.createElement('input');input.id='sunManagerSearch';input.className='sun-manager-search';input.type='search';input.placeholder=Number(activeCat)===0?'Поиск по № бокса':'Поиск по позиции';input.inputMode=Number(activeCat)===0?'numeric':'search';input.oninput=()=>{managerQuery=input.value;renderManagerV5();};bar.appendChild(input);}
}
function renderManagerV5(){
ensureManagerToolbar();const managerSearch=$('sunManagerSearch');if(managerSearch){const boxMode=[0,5].includes(Number(activeCat));managerSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';managerSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#manager .dialog-head h2'),add=document.querySelector('#manager .sun-manager-toolbar .primary')||document.querySelector('#manager .primary');if(title)title.textContent=catName(activeCat);if(add){add.textContent=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию');add.onclick=()=>window.editBox(null)}
const items=boxes.filter(i=>Number(i.category||0)===Number(activeCat)&&itemMatches(i,managerQuery)),cat=catById(activeCat)||{prep:true};
$('managerList').innerHTML=items.length?items.map(item=>`<button type="button" onclick="editBox('${esc(item.id)}')">${item.photo?`<img src="${esc(item.photo)}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='sun-logo.png'">`:'<div class="ph"><img src="sun-logo.png" class="sun-ph-logo" alt="Логотип Солнце Кейтеринг"></div>'}<b>${esc(item.name)}</b><span class="manager-price-wrap"><small class="manager-card-price">${money(item.price||0)}</small>${Number(item.oldPrice||0)>Number(item.price||0)?`<small class="old-price">${money(item.oldPrice)}</small>`:''}</span>${(item.weight||inferBoxPieces(item))?`<small class="manager-card-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}${cat.prep?`<small>${(item.ingredients||[]).length} позиций в составе</small>`:''}</button>`).join(''):'<p class="empty">Ничего не найдено.</p>';
}
window.openManager=()=>{managerQuery='';ensureManagerToolbar();if($('sunManagerSearch'))$('sunManagerSearch').value='';renderManagerV5();window.modal?.('manager');};
window.editBox=id=>{
edited=id?structuredClone(boxes.find(x=>String(x.id)===String(id))):{id:'',name:Number(activeCat)===0?'Новый бокс':(Number(activeCat)===5?'Новая премиум позиция':'Новая позиция'),category:Number(activeCat),price:0,oldPrice:0,ingredients:[]};if(!edited)return;
if(!Array.isArray(edited.ingredients))edited.ingredients=[];normalizeBoxItem(edited);syncEditorCategoryOptions();$('itemCategory').value=String(edited.category??activeCat);$('boxName').value=edited.name||'';$('boxPrice').value=Number(edited.price||0);if($('boxOldPrice'))$('boxOldPrice').value=Number(edited.oldPrice||0);if($('boxCompositionText'))$('boxCompositionText').value=(Array.isArray(edited.composition)?edited.composition:[]).join('\n');syncBoxWeightEditor(edited.category??activeCat,edited);$('editPhoto').style.display=edited.photo?'block':'none';$('editPhoto').src=edited.photo||'';$('deleteBox').style.display=edited.id?'block':'none';
const cat=catById(edited.category)||{name:'Каталог',prep:true};$('editTitle').textContent=edited.id?`Изменить: ${cat.name}`:(Number(edited.category)===0?'Новый бокс':(Number(edited.category)===5?'Новая премиум позиция':'Новая позиция'));const zone=document.querySelector('#editor .ingredient-zone');if(zone)zone.style.display=cat.prep?'block':'none';window.renderIngredients?.();window.modal?.('editor');
};
if($('itemCategory'))$('itemCategory').onchange=function(){if(!edited)return;edited.category=Number(this.value);const c=catById(edited.category)||{name:'Каталог',prep:true};const zone=document.querySelector('#editor .ingredient-zone');if(zone)zone.style.display=c.prep?'block':'none';syncBoxWeightEditor(edited.category,edited);$('editTitle').textContent=edited.id?`Изменить: ${c.name}`:(Number(edited.category)===0?'Новый бокс':(Number(edited.category)===5?'Новая премиум позиция':'Новая позиция'));};
window.saveBox=()=>{if(!edited)return;edited.name=$('boxName').value.trim();edited.price=Math.max(0,Number($('boxPrice').value||0));edited.oldPrice=Math.max(0,Number($('boxOldPrice')?.value||0));if(!(edited.oldPrice>edited.price))delete edited.oldPrice;edited.sale=Boolean(edited.oldPrice&&edited.oldPrice>edited.price);edited.category=Number($('itemCategory').value);if([0,5].includes(edited.category)){const grams=Math.max(0,Math.round(Number($('boxWeightGrams')?.value||0)));edited.weight=grams?`${grams} г`:'';edited.pieces=Math.max(0,Math.round(Number($('boxPiecesCount')?.value||0)));}edited.ingredients=Array.isArray(edited.ingredients)?edited.ingredients:[];if([0,5].includes(edited.category)&&$('boxCompositionText')){const next=$('boxCompositionText').value.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);const before=JSON.stringify(Array.isArray(edited.composition)?edited.composition:[]);edited.composition=next;if(JSON.stringify(next)!==before)edited.compositionSource='manual';}normalizeBoxItem(edited,{cleanup:true});if(!edited.name)return alert('Введите название.');if(!edited.id){edited.id=(typeof sunUUID==='function'?sunUUID():`item-${Date.now()}`);boxes.push(edited)}else{const i=boxes.findIndex(x=>String(x.id)===String(edited.id));if(i>=0)boxes[i]=edited}if(!catById(edited.category)?.hidden)activeCat=edited.category;persist();closeModal('editor');closeModal('manager');renderCatalogV5();renderOrders?.();window.sunClientOfferCatalogChanged?.(edited.id);};
window.removeBox=()=>{if(!edited?.id||!confirm(`Удалить «${edited.name}» из каталога?`))return;boxes=boxes.filter(x=>String(x.id)!==String(edited.id));draft.lines=(draft.lines||[]).filter(x=>String(x.id)!==String(edited.id));persist();closeModal('editor');closeModal('manager');renderCatalogV5();renderOrders?.();};
function fileAsDataUrl(file){return new Promise((resolve,reject)=>{const reader=new FileReader();reader.onerror=()=>reject(reader.error||new Error('FileReader error'));reader.onload=()=>resolve(String(reader.result||''));reader.readAsDataURL(file)})}
function compactCatalogPhotoData(dataUrl,{maxSide=900,maxChars=130000,quality=.78,force=false}={}){return new Promise(resolve=>{if(!/^data:image\//i.test(String(dataUrl||'')))return resolve(dataUrl);const image=new Image();image.onerror=()=>resolve(dataUrl);image.onload=()=>{const iw=Math.max(1,image.naturalWidth||image.width||1),ih=Math.max(1,image.naturalHeight||image.height||1);if(!force&&String(dataUrl).length<=maxChars&&Math.max(iw,ih)<=maxSide)return resolve(dataUrl);let scale=Math.min(1,maxSide/Math.max(iw,ih)),q=quality,best=dataUrl;for(let attempt=0;attempt<5;attempt++){const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.round(iw*scale));canvas.height=Math.max(1,Math.round(ih*scale));const ctx=canvas.getContext('2d');if(!ctx)break;ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(image,0,0,canvas.width,canvas.height);try{best=canvas.toDataURL('image/jpeg',q)||best}catch(_){break}if(best.length<=maxChars)break;scale*=.78;q=Math.max(.52,q-.07)}resolve(best)};image.src=dataUrl})}
window.sunCompactCatalogPhotoData=compactCatalogPhotoData;
window.readPhoto=async input=>{const file=input?.files?.[0];if(!file||!edited)return;try{const original=await fileAsDataUrl(file);const optimized=await compactCatalogPhotoData(original);edited.photo=optimized;$('editPhoto').src=optimized;$('editPhoto').style.display='block';if(optimized.length+5000<original.length)toast('Фото оптимизировано для стабильной синхронизации.','success')}catch(err){console.warn('Catalog photo optimization failed',err);toast('Не удалось обработать фото.','warn')}};
// ---------------- Order requisites ----------------
const buyerDefaults=()=>({name:'',inn:'',kpp:'',ogrn:'',legalAddress:'',phone:'',email:'',bank:'',bik:'',account:'',corrAccount:'',contact:''});
function legacyBuyer(){try{const s=JSON.parse(localStorage.getItem('sunReceiptSettingsV2')||'{}');return {name:s.buyerName||'',inn:s.buyerInn||'',kpp:s.buyerKpp||'',ogrn:s.buyerOgrn||'',legalAddress:s.buyerLegalAddress||'',phone:s.buyerPhone||'',email:s.buyerEmail||'',bank:s.buyerBank||'',bik:s.buyerBik||'',account:s.buyerAccount||'',corrAccount:s.buyerCorrAccount||'',contact:s.buyerContact||''}}catch(_){return buyerDefaults()}}
function ensureRequisitesTab(){
const sub=document.querySelector('#new .subnav');if(!sub)return;
let btn=$('requisitesTab');if(!btn){btn=document.createElement('button');btn.id='requisitesTab';btn.type='button';btn.textContent='Реквизиты';sub.appendChild(btn)}
let panel=$('orderRequisites');if(!panel){panel=document.createElement('div');panel.id='orderRequisites';panel.className='sun-order-requisites';panel.innerHTML=`<h3>Реквизиты покупателя</h3><div class="sun-order-requisites-grid"><label>Компания / ИП<input id="orderBuyerName"></label><label>Контактное лицо<input id="orderBuyerContact"></label><label>ИНН<input id="orderBuyerInn"></label><label>КПП<input id="orderBuyerKpp"></label><label>ОГРН / ОГРНИП<input id="orderBuyerOgrn"></label><label>Телефон<input id="orderBuyerPhone"></label><label class="wide">Юридический адрес<input id="orderBuyerLegalAddress"></label><label class="wide">E-mail<input id="orderBuyerEmail"></label><label class="wide">Банк<input id="orderBuyerBank"></label><label>БИК<input id="orderBuyerBik"></label><label>Расчётный счёт<input id="orderBuyerAccount"></label><label class="wide">Корреспондентский счёт<input id="orderBuyerCorrAccount"></label></div>`;$('details').insertAdjacentElement('afterend',panel)}
$('orderTab').onclick=()=>setOrderPane('order');$('detailTab').onclick=()=>setOrderPane('details');btn.onclick=()=>setOrderPane('requisites');
qa('#orderRequisites input:not([type=file])').forEach(input=>input.addEventListener('input',captureBuyerFromFields));
}
function setOrderPane(tab){ensureRequisitesTab();$('orderForm').style.display=tab==='order'?'block':'none';$('details').style.display=tab==='details'?'block':'none';$('orderRequisites').style.display=tab==='requisites'?'block':'none';$('orderTab').classList.toggle('on',tab==='order');$('detailTab').classList.toggle('on',tab==='details');$('requisitesTab').classList.toggle('on',tab==='requisites');if(tab==='requisites')syncBuyerFields();}
window.details=on=>setOrderPane(on?'details':'order');
function buyerObject(){return {name:$('orderBuyerName')?.value.trim()||'',contact:$('orderBuyerContact')?.value.trim()||'',inn:$('orderBuyerInn')?.value.trim()||'',kpp:$('orderBuyerKpp')?.value.trim()||'',ogrn:$('orderBuyerOgrn')?.value.trim()||'',phone:$('orderBuyerPhone')?.value.trim()||'',legalAddress:$('orderBuyerLegalAddress')?.value.trim()||'',email:$('orderBuyerEmail')?.value.trim()||'',bank:$('orderBuyerBank')?.value.trim()||'',bik:$('orderBuyerBik')?.value.trim()||'',account:$('orderBuyerAccount')?.value.trim()||'',corrAccount:$('orderBuyerCorrAccount')?.value.trim()||''}}
function captureBuyerFromFields(){if(typeof draft==='undefined'||!draft)return;draft.buyerRequisites=buyerObject();}
function syncBuyerFields(){ensureRequisitesTab();let b=draft?.buyerRequisites;if(!b){const legacy=legacyBuyer(),hasLegacy=Object.values(legacy).some(Boolean);b=hasLegacy?legacy:buyerDefaults();draft.buyerRequisites={...b}};const map={orderBuyerName:'name',orderBuyerContact:'contact',orderBuyerInn:'inn',orderBuyerKpp:'kpp',orderBuyerOgrn:'ogrn',orderBuyerPhone:'phone',orderBuyerLegalAddress:'legalAddress',orderBuyerEmail:'email',orderBuyerBank:'bank',orderBuyerBik:'bik',orderBuyerAccount:'account',orderBuyerCorrAccount:'corrAccount'};Object.entries(map).forEach(([id,key])=>{if($(id))$(id).value=b?.[key]||''});}
// ---------------- QR default + image handling ----------------
const QR_KEY='sunDefaultOrderQrV1';
const defaultQr=()=>{try{return localStorage.getItem(QR_KEY)||''}catch(_){return''}};
function imageData(file,max=512){return new Promise((resolve,reject)=>{const r=new FileReader();r.onerror=reject;r.onload=()=>{const img=new Image();img.onerror=reject;img.onload=()=>{const scale=Math.min(1,max/Math.max(img.width||1,img.height||1)),c=document.createElement('canvas');c.width=Math.max(1,Math.round(img.width*scale));c.height=Math.max(1,Math.round(img.height*scale));const ctx=c.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,c.width,c.height);ctx.drawImage(img,0,0,c.width,c.height);resolve(c.toDataURL('image/png'))};img.src=r.result};r.readAsDataURL(file)})}
function ensureDefaultQrSetting(){
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;let card=$('sunDefaultQrCard');if(!card){card=document.createElement('section');card.id='sunDefaultQrCard';card.className='enterprise-card wide';card.innerHTML=`<h2>QR-код для бланка заказа</h2><p class="hint">Загрузите QR-код один раз. Он будет автоматически использоваться во всех бланках заказов, пока вы не загрузите другой.</p><div class="sun-default-qr-setting"><div id="sunDefaultQrPreview" class="preview">QR не загружен</div><div><input id="sunDefaultQrFile" type="file" accept="image/*" hidden><div class="actions"><button class="outline" id="sunDefaultQrUpload" type="button">Загрузить / заменить QR</button></div></div></div>`;const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');window.SunSafe.insertBefore(grid,card,audit||null);$('sunDefaultQrUpload').onclick=()=>$('sunDefaultQrFile').click();$('sunDefaultQrFile').onchange=async()=>{const f=$('sunDefaultQrFile').files?.[0];if(!f)return;try{localStorage.setItem(QR_KEY,await imageData(f));renderDefaultQr();toast('QR-код для бланков сохранён.')}catch(_){toast('Не удалось загрузить QR-код.','warn')}};}
renderDefaultQr();const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');if(audit&&grid.lastElementChild!==audit)grid.appendChild(audit);
}
function renderDefaultQr(){const root=$('sunDefaultQrPreview');if(!root)return;const src=defaultQr();root.innerHTML=src?`<img src="${src}" alt="QR">`:'QR не загружен';}
// ---------------- Backup to/from JSON file ----------------
function backupFileState(){const storage={};for(let i=0;i<localStorage.length;i++){const key=localStorage.key(i);if(!key||!key.startsWith('sun'))continue;storage[key]=localStorage.getItem(key);}return {format:'sun-catering-backup',version:1,createdAt:new Date().toISOString(),storage};}
function downloadBackupFile(){try{const state=backupFileState();const blob=new Blob([JSON.stringify(state)],{type:'application/json'});const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=`caterium-backup-${new Date().toISOString().slice(0,19).replace(/[:T]/g,'-')}.json`;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),2000);toast('Файл резервной копии скачан.','success');}catch(_){toast('Не удалось создать файл резервной копии.','warn')}}
function restoreIdbFromExport(indexedDbDump){
function openDb(name){return new Promise((resolve,reject)=>{const req=indexedDB.open(name);req.onsuccess=()=>resolve(req.result);req.onerror=()=>reject(req.error);});}
function putRecord(store,record){return new Promise((resolve,reject)=>{let req;try{req=store.put(record);}catch(_){const guessKey=record&&(record.id??record.key??record.path);try{req=store.put(record,guessKey);}catch(e2){reject(e2);return;}}req.onsuccess=()=>resolve();req.onerror=()=>reject(req.error);});}
return (async()=>{
for(const [dbName,stores] of Object.entries(indexedDbDump||{})){
let db;try{db=await openDb(dbName);}catch(_){continue;}
for(const [storeName,records] of Object.entries(stores||{})){
if(!db.objectStoreNames.contains(storeName))continue;
const tx=db.transaction(storeName,'readwrite'),store=tx.objectStore(storeName);
for(const record of (records||[])){try{await putRecord(store,record);}catch(_){}}
await new Promise(resolve=>{tx.oncomplete=resolve;tx.onerror=resolve;});
}
db.close();
}
})();
}
async function restoreBackupFile(file){
let state;
try{state=JSON.parse(await file.text());}catch(_){toast('Не удалось прочитать файл — это не JSON.','warn');return;}
const isSettingsFormat=state&&state.format==='sun-catering-backup'&&state.storage&&typeof state.storage==='object';
const isFullExport=state&&state.format==='caterium-full-export'&&state.localStorage&&typeof state.localStorage==='object';
if(!isSettingsFormat&&!isFullExport){toast('Неверный формат файла резервной копии.','warn');return;}
if(!confirm('Загрузить базу из файла? Текущие локальные данные будут заменены содержимым файла.'))return;
if(isSettingsFormat){
Object.entries(state.storage).forEach(([key,value])=>{if(key.startsWith('sun')){try{localStorage.setItem(key,String(value));}catch(_){}}});
}else{
Object.entries(state.localStorage).forEach(([key,value])=>{if(key.startsWith('sun')){try{localStorage.setItem(key,String(value));}catch(_){}}});
await restoreIdbFromExport(state.indexedDB);
}
toast('База восстановлена из файла. Перезагрузка…','success');
setTimeout(()=>location.reload(),600);
}
function ensureBackupFileCard(){
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;
let card=$('sunBackupFileCard');
if(!card){
card=document.createElement('section');card.id='sunBackupFileCard';card.className='enterprise-card wide';
card.innerHTML=`<h2>Резервная копия базы (файл)</h2><p class="hint">Скачайте базу в файл на компьютер или загрузите ранее сохранённый файл — например, для переноса на другой компьютер или работы без интернета.</p><input id="sunBackupFileInput" type="file" accept=".json,application/json" hidden><div class="actions"><button class="outline" id="sunBackupFileDownload" type="button">Скачать базу в файл</button><button class="outline" id="sunBackupFileUpload" type="button">Загрузить базу из файла</button></div>`;
const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');
window.SunSafe.insertBefore(grid,card,audit||null);
$('sunBackupFileDownload').onclick=downloadBackupFile;
$('sunBackupFileUpload').onclick=()=>$('sunBackupFileInput').click();
$('sunBackupFileInput').onchange=async()=>{const f=$('sunBackupFileInput').files?.[0];$('sunBackupFileInput').value='';if(f)await restoreBackupFile(f);};
}
const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');if(audit&&grid.lastElementChild!==audit)grid.appendChild(audit);
}
// ---------------- Custom logo + brand name for PDF documents ----------------
const PDF_BRAND_LOGO_KEY='sunPdfBrandLogoV1';
const PDF_BRAND_NAME_KEY='sunPdfBrandNameV1';
const pdfBrandLogo=()=>{try{return localStorage.getItem(PDF_BRAND_LOGO_KEY)||''}catch(_){return''}};
const pdfBrandName=()=>{try{return localStorage.getItem(PDF_BRAND_NAME_KEY)||''}catch(_){return''}};
function renderPdfBrandPreview(){const root=$('sunPdfBrandPreview');if(!root)return;const src=pdfBrandLogo();root.innerHTML=src?`<img src="${src}" alt="Логотип">`:'Логотип не загружен (используется стандартный)';}
function ensurePdfBrandCard(){
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;
let card=$('sunPdfBrandCard');
if(!card){
card=document.createElement('section');card.id='sunPdfBrandCard';card.className='enterprise-card wide';
card.innerHTML=`<h2>Логотип и название для PDF</h2><p class="hint">Показывается в шапке и подписи «Предложения клиенту» вместо стандартного логотипа и названия «Солнце Кейтеринг».</p><div class="sun-default-qr-setting"><div id="sunPdfBrandPreview" class="preview">Логотип не загружен (используется стандартный)</div><div><input id="sunPdfBrandLogoFile" type="file" accept="image/*" hidden><label style="display:block;margin:0 0 10px"><span class="hint" style="display:block;margin-bottom:4px">Название компании</span><input id="sunPdfBrandNameInput" type="text" placeholder="Солнце Кейтеринг" value="${esc(pdfBrandName())}"></label><div class="actions"><button class="outline" id="sunPdfBrandUpload" type="button">Загрузить / заменить логотип</button><button class="outline" id="sunPdfBrandReset" type="button">Убрать логотип</button></div></div></div>`;
const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');
window.SunSafe.insertBefore(grid,card,audit||null);
$('sunPdfBrandUpload').onclick=()=>$('sunPdfBrandLogoFile').click();
$('sunPdfBrandLogoFile').onchange=async()=>{const f=$('sunPdfBrandLogoFile').files?.[0];if(!f)return;try{localStorage.setItem(PDF_BRAND_LOGO_KEY,await imageData(f,600));renderPdfBrandPreview();toast('Логотип сохранён.')}catch(_){toast('Не удалось загрузить логотип.','warn')}};
$('sunPdfBrandNameInput').addEventListener('change',()=>{try{localStorage.setItem(PDF_BRAND_NAME_KEY,$('sunPdfBrandNameInput').value.trim())}catch(_){}});
$('sunPdfBrandReset').onclick=()=>{try{localStorage.removeItem(PDF_BRAND_LOGO_KEY)}catch(_){}renderPdfBrandPreview();toast('Логотип убран, используется стандартный.')};
}
renderPdfBrandPreview();const audit=qa(':scope > .enterprise-card',grid).find(x=>(x.querySelector('h2')?.textContent||'').trim()==='История изменений');if(audit&&grid.lastElementChild!==audit)grid.appendChild(audit);
}
// ---------------- Discounts + promo tracking ----------------
const PROMO_KEY='sunPromoCodesV1';
const localDateIso=()=>{const d=new Date(),p=n=>String(n).padStart(2,'0');return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`};
function promoCodes(){try{const list=JSON.parse(localStorage.getItem(PROMO_KEY)||'[]');return Array.isArray(list)?list:[]}catch(_){return[]}}
function promoActive(p,date=localDateIso()){if(!p||p.enabled===false)return false;const start=String(p.startDate||''),end=String(p.endDate||'');return (!start||start<=date)&&(!end||end>=date)}
function promoDiscountText(p){const value=Math.max(0,Number(p?.discountValue||0));return p?.discountType==='amount'?money(value):`${value.toLocaleString('ru-RU',{maximumFractionDigits:2})}%`}
function promoById(id){return promoCodes().find(p=>String(p.id)===String(id))||null}
function promoByCode(code){const key=String(code||'').trim().toLowerCase();return promoCodes().find(p=>String(p.code||'').trim().toLowerCase()===key)||null}
function ensureDiscountUI(){
const form=document.querySelector('#new .money-form');if(!form)return;
if(!$('deliveryCost')){
const label=document.createElement('label');label.id='deliveryCostLabel';label.innerHTML='<span>Доставка, ₽</span><input id="deliveryCost" type="number" min="0" step="100" inputmode="decimal" value="'+Math.max(0,Number(draft?.deliveryCost||0))+'">';
const prepay=$('prepayment')?.closest('label');if(prepay)prepay.insertAdjacentElement('beforebegin',label);else form.appendChild(label);
$('deliveryCost')?.addEventListener('input',()=>{captureDelivery();updateOrderSummary()});
}
let panel=$('sunDiscountPanel');
const desired=`<label><span>Скидка</span><div class="sun-discount-combo"><input id="manualDiscountValue" type="number" min="0" step="1" value="0"><select id="manualDiscountType"><option value="percent">%</option><option value="amount">₽</option></select></div></label><label class="sun-promo-select-wrap"><span>Промокод</span><select id="promoCodeSelect"><option value="">Без промокода</option></select><div id="promoCodeMeta" class="sun-promo-meta">Выберите активный промокод из раздела «Рассылки».</div></label>`;
if(!panel){panel=document.createElement('div');panel.id='sunDiscountPanel';panel.className='sun-discount-panel';panel.innerHTML=desired;form.insertAdjacentElement('afterend',panel)}
else if(!$('promoCodeSelect'))panel.innerHTML=desired;
if(panel.dataset.promoBound!=='1'){
panel.dataset.promoBound='1';
$('manualDiscountValue')?.addEventListener('input',()=>{captureDiscounts();updateOrderSummary()});
$('manualDiscountType')?.addEventListener('change',()=>{captureDiscounts();updateOrderSummary()});
$('promoCodeSelect')?.addEventListener('change',()=>{captureDiscounts();updateOrderSummary()});
}
const summary=document.querySelector('#new .order-summary');if(summary&&!$('summaryNetTotal'))summary.innerHTML=`<p><span>Стоимость позиций</span><b id="summaryTotal">0 ₽</b></p><p class="sun-order-summary-discount"><span>Скидка</span><b id="summaryManualDiscount"> 0 ₽</b></p><p class="sun-order-summary-discount sun-order-summary-promo"><span>Промокод <small id="summaryPromoCode"></small></span><b id="summaryPromoDiscount"> 0 ₽</b></p><p><span>Доставка</span><b id="summaryDelivery">0 ₽</b></p><p class="sun-order-summary-net"><span>Итого</span><b id="summaryNetTotal">0 ₽</b></p><p><span>Предоплата</span><b id="summaryPrepayment">0 ₽</b></p><p class="balance"><span>К оплате</span><b id="summaryBalance">0 ₽</b></p>`;
}
function renderPromoSelect(){
ensureDiscountUI();const select=$('promoCodeSelect');if(!select)return;
const active=promoCodes().filter(p=>promoActive(p)).sort((a,b)=>String(a.code||'').localeCompare(String(b.code||''),'ru'));
const currentId=String(draft?.promoId||''),currentCode=String(draft?.promoCode||'').trim();
let selected='';
const opts=['<option value="">Без промокода</option>'];
active.forEach(p=>{opts.push(`<option value="${esc(p.id)}">${esc(p.code||'Промокод')} · ${esc(promoDiscountText(p))} · до ${esc(p.endDate||'без срока')}</option>`);if(currentId&&String(p.id)===currentId)selected=String(p.id)});
if(currentCode&&!selected){
const saved=promoById(currentId)||promoByCode(currentCode),label=saved?(promoActive(saved)?'активен':'срок истёк / отключён'):'сохранён в заказе';
opts.push(`<option value="__snapshot__">${esc(currentCode)} · ${esc(draft?.promoDiscountType==='amount'?money(draft?.promoDiscountValue||0):`${Number(draft?.promoDiscountValue||0).toLocaleString('ru-RU',{maximumFractionDigits:2})}%`)} · ${esc(label)}</option>`);selected='__snapshot__';
}
select.innerHTML=opts.join('');select.value=selected;
updatePromoMeta();
}
function updatePromoMeta(){const meta=$('promoCodeMeta'),select=$('promoCodeSelect');if(!meta||!select)return;const id=select.value;if(!id){meta.textContent='Выберите активный промокод из раздела «Рассылки».';return}if(id==='__snapshot__'){meta.textContent=`Условия сохранены в заказе: ${draft?.promoCode||''} · ${draft?.promoDiscountType==='amount'?money(draft?.promoDiscountValue||0):`${Number(draft?.promoDiscountValue||0).toLocaleString('ru-RU',{maximumFractionDigits:2})}%`}.`;return}const p=promoById(id);meta.textContent=p?`${p.code} · скидка ${promoDiscountText(p)} · действует ${p.startDate||'без даты'}${p.endDate||'без даты'}`:'Промокод недоступен.';}
function captureDiscounts(){
if(!draft)return;draft.manualDiscountValue=Math.max(0,Number($('manualDiscountValue')?.value||0));draft.manualDiscountType=$('manualDiscountType')?.value==='amount'?'amount':'percent';
const select=$('promoCodeSelect');if(!select)return;const id=String(select.value||'');
if(!id){draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';return}
if(id==='__snapshot__'){updatePromoMeta();return}
const p=promoById(id);if(!p||!promoActive(p)){renderPromoSelect();return}
draft.promoId=String(p.id||'');draft.promoCode=String(p.code||'').trim();draft.promoDiscountValue=Math.max(0,Number(p.discountValue||0));draft.promoDiscountType=p.discountType==='amount'?'amount':'percent';draft.promoStartDate=String(p.startDate||'');draft.promoEndDate=String(p.endDate||'');updatePromoMeta();
}
function captureDelivery(){if(!draft)return;draft.deliveryCost=Math.max(0,Number($('deliveryCost')?.value||0));}
function syncDeliveryField(){ensureDiscountUI();if($('deliveryCost'))$('deliveryCost').value=Math.max(0,Number(draft?.deliveryCost||0));}
function discountAmount(value,type,base){const n=Math.max(0,Number(value||0));return Math.min(base,type==='amount'?n:base*Math.min(100,n)/100)}
function pricing(order=draft){const base=baseTotal(order),manual=discountAmount(order?.manualDiscountValue,order?.manualDiscountType,base),afterManual=Math.max(0,base-manual),promo=discountAmount(order?.promoDiscountValue,order?.promoDiscountType,afterManual),itemsTotal=Math.max(0,afterManual-promo),delivery=Math.max(0,Number(order?.deliveryCost||0)),total=itemsTotal+delivery,paid=Math.max(0,Number(order===draft?($('prepayment')?.value||order?.prepayment||0):(order?.prepayment||0))),balance=Math.max(0,total-paid);return {base,manual,promo,itemsTotal,delivery,total,paid,balance,discount:manual+promo}}
window.sunOrderPricing=pricing;
window.calcOrderTotal=()=>pricing(draft).total;
window.orderTotalValue=order=>pricing(order).total;
window.updateOrderSummary=()=>{ensureDiscountUI();captureDiscounts();const p=pricing(draft);if($('orderTotal'))$('orderTotal').value=Math.round(p.total);if($('balance'))$('balance').value=Math.round(p.balance);if($('summaryTotal'))$('summaryTotal').textContent=money(p.base);if($('summaryManualDiscount'))$('summaryManualDiscount').textContent=` ${money(p.manual)}`;if($('summaryPromoDiscount'))$('summaryPromoDiscount').textContent=` ${money(p.promo)}`;if($('summaryPromoCode'))$('summaryPromoCode').textContent=draft.promoCode?`(${draft.promoCode})`:'';if($('summaryDelivery'))$('summaryDelivery').textContent=money(p.delivery);if($('summaryNetTotal'))$('summaryNetTotal').textContent=money(p.total);if($('summaryPrepayment'))$('summaryPrepayment').textContent=money(p.paid);if($('summaryBalance'))$('summaryBalance').textContent=money(p.balance);updatePromoMeta();};
function syncDiscountFields(){ensureDiscountUI();$('manualDiscountValue').value=Number(draft?.manualDiscountValue||0);$('manualDiscountType').value=draft?.manualDiscountType==='amount'?'amount':'percent';syncDeliveryField();renderPromoSelect();updateOrderSummary();}
window.sunRefreshOrderPromos=()=>{renderPromoSelect();updateOrderSummary();};
// ---------------- Seller settings only ----------------
function simplifySellerSettings(){
const card=$('sunReceiptSettingsCard');if(!card||card.dataset.sunSellerOnly==='1')return;
const sections=qa('.sun-receipt-settings-section',card);sections.forEach(s=>{if((s.querySelector('h3')?.textContent||'').includes('Покупатель'))s.classList.add('sun-buyer-settings-hidden')});
const buyerCheck=$('receiptIncludeBuyer');buyerCheck?.closest('label')?.classList.add('sun-buyer-settings-hidden');
const h=card.querySelector('h2');if(h&&(h.textContent||'').trim()!=='Реквизиты компании')h.textContent='Реквизиты компании';
const hint=card.querySelector(':scope > .hint'),hintText='Реквизиты продавца для товарного чека. Реквизиты покупателя вводятся отдельно в каждом заказе во вкладке «Реквизиты».';if(hint&&hint.textContent!==hintText)hint.textContent=hintText;
card.dataset.sunSellerOnly='1';
}
function readSeller(){const d={includeSeller:true,sellerName:'Солнце Кейтеринг',sellerLegalName:'',sellerInn:'',sellerKpp:'',sellerOgrn:'',sellerLegalAddress:'',sellerPhone:'',sellerEmail:'',sellerBank:'',sellerBik:'',sellerAccount:'',sellerCorrAccount:''};try{return {...d,...JSON.parse(localStorage.getItem('sunReceiptSettingsV2')||'{}')}}catch(_){return d}}
function party(title,rows){const list=rows.filter(([,v])=>String(v||'').trim());if(!list.length)return'';return `<section class="sun-receipt-party"><h3>${esc(title)}</h3><div class="sun-receipt-party-grid">${list.map(([k,v])=>`<div><span>${esc(k)}</span><b>${esc(v)}</b></div>`).join('')}</div></section>`}
// ---------------- Order blank + compact receipt ----------------
function currentRows(){return (draft.lines||[]).map((line,index)=>{const item=boxes.find(x=>String(x.id)===String(line.id)),price=lineUnitPrice(line),qty=Math.max(0,Number(line.qty||0)),categoryId=Number(item?.category??line.category??0);return{index:index+1,name:item?.name||line.name||`Позиция ${line.id||''}`,category:catName(categoryId),categoryId,weight:[0,5].includes(categoryId)?String(item?.weight||''):'',pieces:[0,5].includes(categoryId)?inferBoxPieces(item):0,price,qty,sum:price*qty}}).filter(r=>r.qty>0)}
const docDate=raw=>{if(!raw)return'—';const d=new Date(`${raw}T00:00:00`);return Number.isNaN(d.getTime())?raw:d.toLocaleDateString('ru-RU')};
const docActions=()=>`<div class="sun-doc-actions"><button class="primary" type="button" onclick="window.print()">Печать / PDF</button><button class="outline" type="button" onclick="closeModal('preview')">Закрыть</button></div>`;
function showOrderBlank(){
captureBuyerFromFields();captureDiscounts();const rows=currentRows(),p=pricing(draft),qr=defaultQr(),b=draft.buyerRequisites||buyerDefaults(),dialog=document.querySelector('#preview .dialog');if(!dialog)return;const number=draft.id||'черновик',qrNote=String(draft.blankQrText||'').trim();
dialog.innerHTML=`<div class="sun-doc"><div class="sun-doc-head"><div class="sun-doc-brand"><img src="sun-logo.png" alt="Солнце Кейтеринг"><div><strong>Солнце Кейтеринг</strong><small>Санкт-Петербург · кейтеринг и выездные мероприятия</small></div></div><div class="sun-doc-title-with-qr"><div class="sun-doc-title"><h2>Заказ № ${esc(number)}</h2><p>${esc($('event')?.value||'Новое мероприятие')}</p>${draft.status?`<span class="sun-doc-badge">${esc(draft.status)}</span>`:''}</div>${qr?`<div class="sun-doc-qr-wrap"><img class="sun-doc-qr" src="${qr}" alt="QR-код"><div id="sunBlankQrText" class="sun-doc-qr-note" contenteditable="true" spellcheck="true" data-placeholder="Нажмите и добавьте текст рядом с QR">${esc(qrNote)}</div></div>`:''}</div></div><div class="sun-doc-grid"><div class="sun-doc-field"><span>Клиент</span><b>${esc($('contact')?.value||'—')}</b></div><div class="sun-doc-field"><span>Телефон</span><b>${esc($('phone')?.value||'—')}</b></div><div class="sun-doc-field"><span>Дата и время выдачи</span><b>${esc(docDate($('date')?.value))} · ${esc($('time')?.value||'—')}</b></div><div class="sun-doc-field"><span>Адрес доставки</span><b>${esc($('address')?.value||'—')}</b></div>${b.name?`<div class="sun-doc-field"><span>Компания покупателя</span><b>${esc(b.name)}</b></div>`:''}</div><table><thead><tr><th class="doc-num">№</th><th class="doc-name">Позиция</th><th>Цена</th><th>Кол.</th><th>Сумма</th></tr></thead><tbody>${rows.length?rows.map(r=>`<tr><td class="doc-num">${r.index}</td><td class="doc-name"><b>${esc(r.name)}</b><span class="doc-sub">${esc(r.category)}</span>${(r.weight||r.pieces)?`<span class="doc-sub sun-doc-weight">${r.weight?`Вес бокса: ${esc(r.weight)}`:''}${r.weight&&r.pieces?' · ':''}${r.pieces?`Количество: ${esc(r.pieces)} шт.`:''}</span>`:''}</td><td>${money(r.price)}</td><td>${r.qty}</td><td><b>${money(r.sum)}</b></td></tr>`).join(''):'<tr><td colspan="5">В заказе пока нет позиций.</td></tr>'}</tbody></table><div class="sun-doc-summary"><p><span>Стоимость позиций</span><b>${money(p.base)}</b></p>${p.manual?`<p><span>Скидка</span><b> ${money(p.manual)}</b></p>`:''}${p.promo?`<p><span>Промокод ${esc(draft.promoCode||'')}</span><b> ${money(p.promo)}</b></p>`:''}<p><span>Доставка</span><b>${money(p.delivery)}</b></p><p><span>Итого</span><b>${money(p.total)}</b></p><p><span>Предоплата</span><b>${money(p.paid)}</b></p><p class="grand"><span>Остаток к оплате</span><span>${money(p.balance)}</span></p></div>${$('note')?.value?`<div class="sun-doc-note"><b>Комментарий к заказу</b>${esc($('note').value)}</div>`:''}<div class="sun-doc-footer"><div><b>Солнце Кейтеринг</b><br>Спасибо, что выбрали нас.<div class="sun-doc-sign">Ответственный / подпись</div></div><div>Заказчик подтверждает состав заказа, дату, время и адрес выдачи.<div class="sun-doc-sign">Заказчик / подпись</div></div></div>${docActions()}</div>`;window.modal?.('preview');const noteEl=$('sunBlankQrText');if(noteEl){const save=()=>{const text=String(noteEl.innerText||noteEl.textContent||'').replace(/\u00a0/g,' ').trim();draft.blankQrText=text;if(draft.id){const saved=(orders||[]).find(o=>String(o.id)===String(draft.id));if(saved){saved.blankQrText=text;try{persist()}catch(_){}}}};noteEl.addEventListener('input',save);noteEl.addEventListener('blur',save);}
}
function showReceipt(){
captureBuyerFromFields();captureDiscounts();const rows=currentRows(),p=pricing(draft),s=readSeller(),b=draft.buyerRequisites||buyerDefaults(),dialog=document.querySelector('#preview .dialog');if(!dialog)return;const seller=s.includeSeller!==false?party('Продавец',[['Наименование',s.sellerLegalName||s.sellerName],['ИНН',s.sellerInn],['КПП',s.sellerKpp],['ОГРН / ОГРНИП',s.sellerOgrn],['Адрес',s.sellerLegalAddress],['Телефон',s.sellerPhone],['E-mail',s.sellerEmail],['Банк',s.sellerBank],['БИК',s.sellerBik],['Расч. счёт',s.sellerAccount],['Корр. счёт',s.sellerCorrAccount]]):'';const buyer=party('Покупатель',[['Компания / ИП',b.name],['ИНН',b.inn],['КПП',b.kpp],['ОГРН / ОГРНИП',b.ogrn],['Адрес',b.legalAddress],['Контакт',b.contact],['Телефон',b.phone],['E-mail',b.email],['Банк',b.bank],['БИК',b.bik],['Расч. счёт',b.account],['Корр. счёт',b.corrAccount]]);
dialog.innerHTML=`<div class="sun-doc sun-receipt"><div class="sun-doc-head"><div class="sun-doc-brand"><img src="sun-logo.png" alt="Солнце Кейтеринг"><div><strong>${esc(s.sellerName||'Солнце Кейтеринг')}</strong><small>${esc(s.sellerLegalName||'')}</small></div></div><div class="sun-doc-title"><h2>Товарный чек № ${esc(draft.id||'—')}</h2><p>${esc(docDate($('date')?.value||draft.date))} · ${esc($('time')?.value||draft.time||'')}</p></div></div>${seller}${buyer}<table><thead><tr><th class="doc-num">№</th><th class="doc-name">Наименование</th><th>Цена</th><th>Кол.</th><th>Сумма</th></tr></thead><tbody>${rows.length?rows.map(r=>`<tr><td class="doc-num">${r.index}</td><td class="doc-name">${esc(r.name)}${(r.weight||r.pieces)?`<span class="doc-sub sun-doc-weight">${r.weight?`Вес бокса: ${esc(r.weight)}`:''}${r.weight&&r.pieces?' · ':''}${r.pieces?`Количество: ${esc(r.pieces)} шт.`:''}</span>`:''}</td><td>${money(r.price)}</td><td>${r.qty}</td><td>${money(r.sum)}</td></tr>`).join(''):'<tr><td colspan="5">В заказе нет позиций.</td></tr>'}</tbody></table><div class="sun-doc-summary"><p><span>Стоимость позиций</span><b>${money(p.base)}</b></p>${p.manual?`<p><span>Скидка</span><b> ${money(p.manual)}</b></p>`:''}${p.promo?`<p><span>Промокод ${esc(draft.promoCode||'')}</span><b> ${money(p.promo)}</b></p>`:''}<p><span>Доставка</span><b>${money(p.delivery)}</b></p><p><span>Итого</span><b>${money(p.total)}</b></p><p><span>Оплачено</span><b>${money(p.paid)}</b></p><p class="grand"><span>К оплате</span><span>${money(p.balance)}</span></p></div>${$('note')?.value?`<div class="sun-doc-note"><b>Примечание</b>${esc($('note').value)}</div>`:''}<div class="sun-doc-footer"><div><div class="sun-doc-sign">Продавец / подпись</div></div><div><div class="sun-doc-sign">Покупатель / подпись</div></div></div>${docActions()}</div>`;window.modal?.('preview');
}
window.sunShowConfiguredReceipt=showReceipt;window.sunShowOrderBlankV5=showOrderBlank;
function patchDocButtons(){qa('#new .actions button').forEach(b=>{const text=(b.textContent||'').trim();if(text==='Товарный чек')b.onclick=showReceipt;if(text==='Бланк заказа')b.onclick=showOrderBlank;});}
// ---------------- Promo statistics ----------------
function renderPromoStats(){
const view=$('stats');if(!view)return;let root=$('sunPromoStats');if(!root){root=document.createElement('section');root.id='sunPromoStats';root.className='sun-promo-stats';view.appendChild(root)}
const groups=new Map();(orders||[]).filter(o=>o.status!=='Отменён'&&String(o.promoCode||'').trim()).forEach(o=>{const code=String(o.promoCode).trim(),p=pricing(o),key=code.toLowerCase(),client=(String(o.phone||'').replace(/\D/g,'')||String(o.contact||'').trim().toLowerCase()||`order:${o.id}`);if(!groups.has(key))groups.set(key,{code,orders:0,clients:new Set(),sales:0,discount:0});const g=groups.get(key);g.orders++;g.clients.add(client);g.sales+=p.total;g.discount+=p.promo;});const list=[...groups.values()].sort((a,b)=>b.sales-a.sales);
root.innerHTML=`<h2>Промокоды</h2><p class="hint" style="margin:0">Отслеживание заказов, клиентов, продаж и предоставленной скидки по промокодам.</p>${list.length?`<div style="overflow:auto"><table><thead><tr><th>Промокод</th><th class="num">Заказов</th><th class="num">Клиентов</th><th class="num">Продажи</th><th class="num">Скидка</th></tr></thead><tbody>${list.map(g=>`<tr><td><b>${esc(g.code)}</b></td><td class="num">${g.orders}</td><td class="num">${g.clients.size}</td><td class="num">${money(g.sales)}</td><td class="num">${money(g.discount)}</td></tr>`).join('')}</tbody></table></div>`:'<p class="empty">Заказов с промокодами пока нет.</p>'}`;
}
// ---------------- Lifecycle wrappers ----------------
const previousSave=window.saveOrder;
if(typeof previousSave==='function')window.saveOrder=function(...args){captureBuyerFromFields();captureDiscounts();captureDelivery();const result=previousSave.apply(this,args);renderPromoStats();return result};
const previousOpen=window.openOrder;
if(typeof previousOpen==='function')window.openOrder=function(...args){const r=previousOpen.apply(this,args);setTimeout(()=>{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');patchDocButtons();renderCatalogV5();},0);return r};
const previousReset=window.resetDraft;
if(typeof previousReset==='function')window.resetDraft=function(...args){const r=previousReset.apply(this,args);draft.buyerRequisites=buyerDefaults();draft.manualDiscountValue=0;draft.manualDiscountType='percent';draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';draft.deliveryCost=0;delete draft.qrImage;setTimeout(()=>{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');renderCatalogV5();},0);return r};
const previousStats=window.renderStats;
window.renderStats=function(...args){const r=typeof previousStats==='function'?previousStats.apply(this,args):undefined;setTimeout(renderPromoStats,0);return r};
function onSettings(){setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();ensureBackupFileCard();ensurePdfBrandCard();},80);setTimeout(()=>{simplifySellerSettings();ensureDefaultQrSetting();ensureBackupFileCard();ensurePdfBrandCard();},250)}
document.addEventListener('click',e=>{const nav=e.target.closest('header nav button');if(nav&&String(nav.dataset.navLabel||nav.textContent||'').trim()==='Настройки')onSettings();},true);
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;if($('sunReceiptSettingsCard'))simplifySellerSettings();patchDocButtons();},50);};[$('new'),$('enterprise-settings')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
// Initial install. Populate the new piece-count field without overwriting manually edited prices/weights.
try{let changed=false;(Array.isArray(boxes)?boxes:[]).forEach(item=>{if(![0,5].includes(Number(item?.category||0)))return;const before=JSON.stringify([item.pieces,item.ingredients,item.composition]);normalizeBoxItem(item,{cleanup:true});if(before!==JSON.stringify([item.pieces,item.ingredients,item.composition]))changed=true;});if(changed)persist();}catch(err){console.warn('v17.5 catalog normalization skipped',err)}
ensureCategoryModal();renderCategoryTabs();syncEditorCategoryOptions();ensureCatalogTools();ensureBoxWeightEditor();ensureBoxPiecesEditor();ensureRequisitesTab();ensureDiscountUI();syncBuyerFields();syncDiscountFields();syncDeliveryField();patchDocButtons();renderCatalogV5();renderPromoStats();if($('enterprise-settings')?.classList.contains('on'))onSettings();
})();
;
/* ===== MODULE: client-offer-settings.js ===== */
/* Sun Catering: editable client-offer content and metric settings */
(()=>{
'use strict';
if(window.__sunClientOfferSettingsV1)return;
window.__sunClientOfferSettingsV1=true;
const KEY='sunClientOfferSettingsV1';
const DEFAULTS={
version:3,
metricMode:'weight',
showAmountPerGuest:true,
showBoxCount:true,
showPriceBreakdown:true,
showDiscountBadge:true,
showGallery:true,
showControl:true,
showExtras:true,
showFinalPhoto:true,
showClientName:true,
showDate:true,
showGuests:true,
showFooterNote:true,
texts:{
proposalLabel:'Индивидуальное предложение',
menuTitle:'Меню',
summaryTitle:'Итого',
totalPriceTitle:'Итоговая стоимость',
heroNote:'Фирменные боксы с закусками — готово к подаче на стол.',
salesTitle:'Почему с нами удобно',
salesNote:'Меню составлено специально под ваше событие. Закуски приезжают в фирменных боксах, их удобно поставить на стол и подать без сложной сервировки. Мы готовим заказ в день доставки, используем свежие ингредиенты и контролируем детали до передачи заказа.',
finalGalleryTitle:'Как это выглядит на вашем столе',
weightMetricLabel:'Вес еды на гостя',
piecesMetricLabel:'Канапе на гостя',
amountPerGuestLabel:'Сумма на гостя',
boxCountLabel:'Количество боксов',
pricingTitle:'Расчёт стоимости',
priceItemsLabel:'Стоимость позиций',
discountLabel:'Скидка',
promoLabel:'Промокод',
menuAfterDiscountLabel:'Меню после скидок',
deliveryLabel:'Доставка',
totalLabel:'Итого',
controlTitle:'Всё под контролем команды Солнца',
extrasTitle:'Дополнительно к заказу рекомендуем',
footerNote:'Доставка считается отдельно от меню и не участвует в скидках.'
},
controlLines:[
'Фирменная упаковка',
'Уточняем детали доставки',
'Стоимость заказа не изменится',
'Готовим закуски в день мероприятия',
'Контролируем заказ до момента передачи',
'Привозим заказ вовремя и передаём вам лично',
'Всё организованно! Вы просто встречаете гостей!'
],
extraServices:['Напитки','Десерты','Фрукты','Одноразовая посуда']
};
const clone=v=>JSON.parse(JSON.stringify(v));
const esc=window.SunSafe.escapeHTML;
const bool=(v,d)=>typeof v==='boolean'?v:d;
const lines=(value,fallback)=>{
const arr=Array.isArray(value)?value:String(value??'').split(/\r?\n/);
const clean=arr.map(x=>String(x||'').trim()).filter(Boolean).slice(0,20);
return clean.length?clean:[...fallback];
};
function normalize(raw={}){
const d=clone(DEFAULTS),t=raw&&typeof raw==='object'?raw:{};
d.metricMode=t.metricMode==='pieces'?'pieces':'weight';
['showAmountPerGuest','showBoxCount','showPriceBreakdown','showDiscountBadge','showGallery','showControl','showExtras','showFinalPhoto','showClientName','showDate','showGuests','showFooterNote'].forEach(k=>d[k]=bool(t[k],d[k]));
if(t.texts&&typeof t.texts==='object')Object.keys(d.texts).forEach(k=>{if(t.texts[k]!=null)d.texts[k]=String(t.texts[k]).trim()||d.texts[k]});
// v3 separates the short first-screen note from the longer selling copy.
// Preserve any custom v2 hero text by moving it into the new selling block.
if(Number(t.version||0)<3&&t.texts&&typeof t.texts==='object'&&!String(t.texts.salesNote||'').trim()){
const legacyHero=String(t.texts.heroNote||'').trim();
if(legacyHero)d.texts.salesNote=legacyHero;
d.texts.heroNote=DEFAULTS.texts.heroNote;
}
d.controlLines=lines(t.controlLines,d.controlLines);
d.extraServices=lines(t.extraServices,d.extraServices);
// Since v17.5.17 table photos are automatic whitespace fillers, not an optional section.
d.showFinalPhoto=true;
return d;
}
function read(){try{return normalize(JSON.parse(localStorage.getItem(KEY)||'{}'))}catch(_){return clone(DEFAULTS)}}
let state=read();
function save(next){state=normalize(next);localStorage.setItem(KEY,JSON.stringify(state));window.dispatchEvent(new CustomEvent('sunclientoffersettingschange',{detail:clone(state)}));return clone(state)}
function reset(){return save(DEFAULTS)}
const style=document.createElement('style');
style.id='sun-client-offer-settings-style';
style.textContent=`
.sun-client-offer-settings-card{grid-column:1/-1!important}
.sun-client-offer-settings-head{display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap}
.sun-client-offer-settings-head>div:first-child{flex:1;min-width:250px}
.sun-client-offer-settings-card details{border:1px solid #e4e7ea;border-radius:14px;background:#fff;margin-top:10px;overflow:hidden}
.sun-client-offer-settings-card summary{cursor:pointer;padding:13px 15px;font-weight:800;color:#2a3138;list-style:none;display:flex;align-items:center;justify-content:space-between;gap:10px}
.sun-client-offer-settings-card summary::-webkit-details-marker{display:none}
.sun-client-offer-settings-card summary:after{content:'+';font-size:20px;font-weight:500;color:#7a828a}.sun-client-offer-settings-card details[open] summary:after{content:''}
.sun-client-offer-settings-body{border-top:1px solid #eceff1;padding:14px 15px 16px}
.sun-client-offer-settings-grid{display:grid;grid-template-columns:repeat(2,minmax(220px,1fr));gap:12px}
.sun-client-offer-settings-grid label,.sun-client-offer-settings-body>label{display:grid;gap:6px;font-size:12px;color:#59636d;font-weight:700}
.sun-client-offer-settings-grid input,.sun-client-offer-settings-grid select,.sun-client-offer-settings-body textarea{width:100%;border:1px solid #d9dee3;border-radius:10px;background:#fff;color:#20262c;padding:10px 11px;font:inherit}
.sun-client-offer-settings-body textarea{min-height:135px;resize:vertical;line-height:1.5}
.sun-client-offer-switches{display:grid;grid-template-columns:repeat(3,minmax(170px,1fr));gap:9px}
.sun-client-offer-switch{display:flex!important;grid-template-columns:none!important;align-items:center;gap:9px!important;border:1px solid #e2e6e9;border-radius:11px;padding:10px 11px;color:#303941!important;background:#fafbfb}
.sun-client-offer-switch input{width:17px!important;height:17px;margin:0;accent-color:var(--sun-brand-sun,#FFC107)}
.sun-client-offer-settings-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:14px;padding-top:14px;border-top:1px solid #eceff1}
.sun-client-offer-settings-actions .hint{margin:0 0 0 auto}
@media(max-width:760px){.sun-client-offer-settings-grid,.sun-client-offer-switches{grid-template-columns:1fr}.sun-client-offer-settings-actions .hint{width:100%;margin:0}}
`;
document.head.appendChild(style);
function html(){
const s=state,t=s.texts;
return `<div class="sun-client-offer-settings-head"><div><h2>Настройки предложения клиенту</h2><p class="hint">Тексты и состав блоков применяются ко всем шаблонам и к PDF. Внутренний номер заказа клиенту не показывается.</p></div></div>
<details open><summary>Показатели в блоке «Итого»</summary><div class="sun-client-offer-settings-body">
<div class="sun-client-offer-settings-grid"><label>Первый показатель<select data-offer-setting="metricMode"><option value="weight" ${s.metricMode==='weight'?'selected':''}>Вес еды на гостя</option><option value="pieces" ${s.metricMode==='pieces'?'selected':''}>Количество канапе на гостя</option></select></label><label>Подпись «Вес еды»<input data-offer-text="weightMetricLabel" value="${esc(t.weightMetricLabel)}"></label><label>Подпись «Канапе»<input data-offer-text="piecesMetricLabel" value="${esc(t.piecesMetricLabel)}"></label><label>Подпись «Сумма на гостя»<input data-offer-text="amountPerGuestLabel" value="${esc(t.amountPerGuestLabel)}"></label><label>Подпись «Количество боксов»<input data-offer-text="boxCountLabel" value="${esc(t.boxCountLabel)}"></label></div>
<p class="hint" style="margin-top:10px">Вес и количество боксов считаются только из раздела «Боксы». Напитки, посуда и дополнения в эти показатели не попадают. В составе предложения количество канапе/закусок каждого вида показывается итогом с учётом количества заказанных боксов.</p>
<div class="sun-client-offer-switches" style="margin-top:12px"><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showAmountPerGuest" ${s.showAmountPerGuest?'checked':''}>Сумма на гостя</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showBoxCount" ${s.showBoxCount?'checked':''}>Количество боксов</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showPriceBreakdown" ${s.showPriceBreakdown?'checked':''}>Расчёт стоимости</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showDiscountBadge" ${s.showDiscountBadge?'checked':''}>Значок скидки</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showGallery" ${s.showGallery?'checked':''}>Фотогалерея</label></div>
</div></details>
<details><summary>Основные тексты</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-settings-grid">
<label>Метка предложения<input data-offer-text="proposalLabel" value="${esc(t.proposalLabel)}"></label>
<label>Заголовок меню<input data-offer-text="menuTitle" value="${esc(t.menuTitle)}"></label>
<label>Заголовок итогов<input data-offer-text="summaryTitle" value="${esc(t.summaryTitle)}"></label>
<label>Подпись итоговой стоимости<input data-offer-text="totalPriceTitle" value="${esc(t.totalPriceTitle)}"></label>
</div><label style="margin-top:12px">Короткий текст первого экрана (23 строки)<textarea data-offer-text="heroNote">${esc(t.heroNote)}</textarea></label><label style="margin-top:12px">Нижнее примечание<textarea data-offer-text="footerNote">${esc(t.footerNote)}</textarea></label></div></details>
<details><summary>Продающий блок после первого экрана</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-settings-grid"><label>Заголовок блока<input data-offer-text="salesTitle" value="${esc(t.salesTitle)}"></label><label>Заголовок финальной фотогалереи<input data-offer-text="finalGalleryTitle" value="${esc(t.finalGalleryTitle)}"></label></div><label style="margin-top:12px">Продающий текст<textarea data-offer-text="salesNote">${esc(t.salesNote)}</textarea></label><p class="hint" style="margin-top:9px">Текст не обрезается: блок увеличивается и при необходимости переносится на следующую страницу PDF.</p></div></details>
<details><summary>Тексты расчёта стоимости</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-settings-grid">
<label>Заголовок блока<input data-offer-text="pricingTitle" value="${esc(t.pricingTitle)}"></label>
<label>Стоимость позиций<input data-offer-text="priceItemsLabel" value="${esc(t.priceItemsLabel)}"></label>
<label>Скидка<input data-offer-text="discountLabel" value="${esc(t.discountLabel)}"></label>
<label>Промокод<input data-offer-text="promoLabel" value="${esc(t.promoLabel)}"></label>
<label>Меню после скидок<input data-offer-text="menuAfterDiscountLabel" value="${esc(t.menuAfterDiscountLabel)}"></label>
<label>Доставка<input data-offer-text="deliveryLabel" value="${esc(t.deliveryLabel)}"></label>
<label>Итого<input data-offer-text="totalLabel" value="${esc(t.totalLabel)}"></label>
</div></div></details>
<details><summary>Блок «Всё под контролем»</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-settings-grid"><label>Заголовок<input data-offer-text="controlTitle" value="${esc(t.controlTitle)}"></label><label class="sun-client-offer-switch" style="align-self:end"><input type="checkbox" data-offer-setting="showControl" ${s.showControl?'checked':''}>Показывать блок</label></div><label style="margin-top:12px">Пункты — каждый с новой строки<textarea data-offer-list="controlLines">${esc(s.controlLines.join('\n'))}</textarea></label></div></details>
<details><summary>Дополнительно к заказу</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-settings-grid"><label>Заголовок<input data-offer-text="extrasTitle" value="${esc(t.extrasTitle)}"></label><label class="sun-client-offer-switch" style="align-self:end"><input type="checkbox" data-offer-setting="showExtras" ${s.showExtras?'checked':''}>Показывать блок</label></div><label style="margin-top:12px">Рекомендации — каждая с новой строки<textarea data-offer-list="extraServices">${esc(s.extraServices.join('\n'))}</textarea></label></div></details>
<details><summary>Что показывать клиенту</summary><div class="sun-client-offer-settings-body"><div class="sun-client-offer-switches"><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showClientName" ${s.showClientName?'checked':''}>Имя клиента</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showDate" ${s.showDate?'checked':''}>Дата мероприятия</label><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showGuests" ${s.showGuests?'checked':''}>Количество гостей</label><span class="sun-client-offer-switch" style="cursor:default">Фото на столе: авто, только в свободное место</span><label class="sun-client-offer-switch"><input type="checkbox" data-offer-setting="showFooterNote" ${s.showFooterNote?'checked':''}>Нижнее примечание</label></div></div></details>
<div class="sun-client-offer-settings-actions"><button type="button" class="primary" data-offer-settings-save>Сохранить настройки предложения</button><button type="button" class="outline" data-offer-settings-reset>Вернуть стандартные тексты</button><p class="hint">Изменения применятся к следующему открытию предложения; открытое предложение обновится автоматически.</p></div>`;
}
function collect(card){
const next=clone(state);
card.querySelectorAll('[data-offer-setting]').forEach(el=>{const k=el.dataset.offerSetting;if(el.type==='checkbox')next[k]=el.checked;else next[k]=el.value});
next.texts={...next.texts};
card.querySelectorAll('[data-offer-text]').forEach(el=>{next.texts[el.dataset.offerText]=String(el.value||'').trim()});
card.querySelectorAll('[data-offer-list]').forEach(el=>{next[el.dataset.offerList]=String(el.value||'').split(/\r?\n/).map(x=>x.trim()).filter(Boolean)});
return normalize(next);
}
function bind(card){
card.querySelector('[data-offer-settings-save]')?.addEventListener('click',()=>{save(collect(card));card.innerHTML=html();bind(card);try{window.SunEnterprise?.toast?.('Настройки предложения сохранены.')}catch(_){}});
card.querySelector('[data-offer-settings-reset]')?.addEventListener('click',()=>{reset();card.innerHTML=html();bind(card);try{window.SunEnterprise?.toast?.('Стандартные настройки предложения восстановлены.')}catch(_){}});
}
function ensureCard(){
const view=document.getElementById('enterprise-settings');if(!view)return;
const grid=view.querySelector('.enterprise-grid');if(!grid)return;
let card=document.getElementById('sunClientOfferSettingsCard');
if(card&&grid.contains(card))return;
card=document.createElement('section');card.id='sunClientOfferSettingsCard';card.className='enterprise-card wide sun-client-offer-settings-card';card.innerHTML=html();
const templates=document.getElementById('sunOfferTemplateSettingsCard');
if(templates&&templates.parentElement===grid)templates.insertAdjacentElement('afterend',card);else{
const brand=document.getElementById('sunBrandThemeSettingsCard');
if(brand&&brand.parentElement===grid)brand.insertAdjacentElement('afterend',card);else grid.appendChild(card);
}
bind(card);
}
function boot(){
ensureCard();
const settings=document.getElementById('enterprise-settings');if(settings)new MutationObserver(()=>queueMicrotask(ensureCard)).observe(settings,{childList:true,subtree:true});
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const label=(b.dataset.navLabel||b.textContent||'').trim();if(label==='Настройки')setTimeout(ensureCard,35)},true);
window.addEventListener('storage',e=>{if(e.key===KEY){state=read();setTimeout(()=>{const card=document.getElementById('sunClientOfferSettingsCard');if(card){card.innerHTML=html();bind(card)}},20);window.dispatchEvent(new CustomEvent('sunclientoffersettingschange',{detail:clone(state)}));}});
window.addEventListener('sun:cloud-state-applied',()=>{state=read();setTimeout(()=>{const card=document.getElementById('sunClientOfferSettingsCard');if(card){card.innerHTML=html();bind(card)}},30)});
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
window.SunClientOfferSettings={key:KEY,defaults:()=>clone(DEFAULTS),get:()=>clone(state),set:save,reset};
})();
;
/* ===== MODULE: client-offer.js ===== */
(function(){
'use strict';
if(window.__sunClientOfferV2)return; window.__sunClientOfferV2=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const esc=window.SunSafe.escapeHTML;
const money=v=>`${Math.round(Number(v||0)).toLocaleString('ru-RU')}`;
const num=v=>Math.max(0,Number(v||0));
const toast=(m,t='success')=>{try{return window.SunEnterprise?.toast?.(m,t)}catch(_){} if(t==='error'||t==='warn')console.warn(m);};
const EMBEDDED_LOGO='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAAIcCAYAAABLt/LkAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAACAAElEQVR42uz9d7RlV3kmej/vjCvtvU+spJyhEFEyIGNMyWBoCQQmnAITjAiWMMHd9vBt2923m8P4ct977b52u23RtsHZXWWTTWpwlW0hDJYMAkkIoRxKFU7aYcWZvj92oXbboCydKtX8jVFDY0ij9l5rnaN9njPnfN8XiKIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqIoiqLo+BKWl9kNe5aKPctLarOvJYqi6PHAN/sCoiiKHk8hgO6rzz5JSv36mcLNvfuVpwy3f/LWZj8QNvvaoiiKHi222RcQRVH0eNq/fxf35J/nnX8fOD6gdP+lz/+NS4rNvq4oiqLHQmz2BURRFD2eTr/zdNHO2vO9C0/jnE7mSq+dni7cAOBGxFW2KIqOU3GFLYqip5TVbrWo6+p8742QSvbTPH12v0ifvmfPTrnZ1xZFUfRoxcAWRdFTRgggMRBzROyZIEYgMEZ8m0z00y86+aK4LRpF0XErbolGUfTUsXeJzQ8GZ/sQtjvrEeBhnM1dG87rxmYBwNpmX2IURdGjEVfYoih6KuE+0DMD0OeCQysF8kGZtj2NrN22vBw/86IoOj7FD68oip4ybgdS58wz2qYW1hkQcWKMMUa0wLk+9e2nXR77skVRdFyKgS2KoqeEEEBqZm4LZ+I850B13aBuagQfAGBgvD3N56Xe7OuMoih6NGJgi6LoqWHvEiMuT2GCn0UEVJMSo+EInesIIWTW+pMP1XUsPIii6LgUA1sURU8JNwLcN+258GEgBAdjBAIQQoB1Tjljt1UlmwdAm32tURRFj1SsEo2i6CnBNUM5DvzZUglORCh6/em/txbOes65WJyd0QuY/qLqNvt6oyiKHom4whZF0VOCyYs+MXq694E440iSBFJKEBGEEIwYm0sl27JnOc5QjqLo+BMDWxRFx70QltmA9OmM8VOJCAEB3nt0XQvnHDHGKDjfM53fqtMXxcKDKIqOO3FLNIqi4951H/407y2ed47pukVjOiipQIxQ1zWAgDTJiHOZCqG2ljrJAIw3+5qPJ9f/4dvyRNj0wC33b1y8vN9u9vVE0YkorrBFUXTcO/n0nQrgT/fOZcZYOOdgrUXwHiEAAR6MkxYc20/KebbZ13s8ObDvioXeQvoeo8S7022Dc/csxS3lKNoMcYUtiqLjXtXYgpE4nzgxpRRUokGMQevpPzlnCAEqeLYwN5v0Nvt6jxfX7PmFtGnLNwL081KKtWIxW9nxr150D/Z+Ja5QRtGTLK6wRVF0XFteBqsNFrque3rwgE4SaJ0g0Qm0TiGEBMAQAhhjNJvoubk4ouqhheVltjWvnkGgdyL4k7gQJw/6vbPOem4/2exri6ITUfzQiqLouPYS7GLe2HPbplkwpgMRQEQw1qFpGtRljbpqyBlHXIhBIpP57QcuiNt6D+HOl2z0OYW3m647v20bhhASKeWc6tJYtBFFmyAGtiiKjmsnPz/ljPFne+fy4D0QPJq6wWhjiHJSwloLQoCUgqSSfR+woHb0YmB7EGHPEk8IF3GhLrPWqrZtEZzjwcuU1afGozRRtAliYIui6LhWr7MM8E8nTlwnCZTSCAhA8JBaIs1S6CSBkIKCD3lTVfPnPNfIzb7uY9kdc4MFa+xbuZQnCaEAAC4E8ghite1iYIuiTRADWxRFx60QQJOJ2ToZj85y1pPSClIppEmK/mCAXtGH1hqMTxfU2tbotmsW1Nog3+xrP1Zde+0FcnJk8tJyMvlJ23UiSTTSLANjDJ1tuXEm/tyIok0Qf1OKoui4tXcv2DZTn2QMP70oBuCcI/gAxjkkY0CYzhINwcN5QvCOk6cF7vI4BP4Ho/Jr89uMLt/UWTcPAEmSItEpnPOwjUXXNHEWaxRtghjYoig6bu28aScfnZQ9TXAxSLMMIQDWWQihEEKAsw4+eFhrELwHEeeMs3mW+hjYfoBrr7pAOJ29HKAfIyJGxBHC9L+FABjn0NQmbPZ1RtGJKAa2KIqOW+7Mk2UmBs/UieRSKoTgASIwRkBgaG0L03Uw1kzPtEnFEDDHfOgBIAAxfBwVALpWnnaKEPKnBecDxjmICNZZeO8AEDhjvkil3+xrjaITUTyLEEUPIgTQPZ9/19yBT18Wu+Mfg/RgbpDm6flKaWKcg3MBEMFYC2st2rZFXdfw3kMoRVxwcsH1AJrdt29XrBT9J279jUuUzNOXCymezwRj1jsMR0OMRkO0bQvbGRDBK5nFkBtFmyAGtih6MHuXWOfcSTWb/6k7/urybZt9OdH/FMIyC0GcjhBOsm4a0Iy1qJsWTdNOe7DVFdquhuAcWZZBSk2MidR5u3DkSBsrRY8KARR2bD8pSfRuIXhhjYU7Gni7ziCAQEQhOHgB5jb7eqPoRBQDWxQ9mKW93li2Gjye48Hec8/n33V2mG6lRZvsug9/mpu6PbcsJ1vqsoQxHcpygnIyhnMWIDo6lkqCiIHAIDiDFDwJgRaKgzMxsB21/6OX646Z13jvLwwBBBCSJMFgZhZplkNKCal04FJYzcu4JRpFmyAGtih6EEQIXN6/Gji7HsReZTz+9V2fv+K5ISzF7bRNtpruVFyK8xhjCeMMwTu0dY2ubRAQwAVHmqbQSQoQwTkL4ywZaxPGw8L8IImBDdPRXpJWz6pG5ZuqqiqsM5BKIkkzpFkGISWsdeiM82CucwvrcYUtijZBDGxR9BDOvvRzHQ90NRHdB9DrffC/ePtf9S7et+/yOFNxE507z3s6SZ9ZFD2W5z0wLsCFQJblYIzD2A7OBxBjAE0XRa01qMpKNU07J62LI5YAvAS7FNnwyrbrntnUDTnjoJMEMknAhQQxhrpuMByuu/F4ZJwaxhW2KNoEMbBF0UMgIDg6cJBAHwNBhuAvcSH8wmkte/X6vstnNvv6TkTLy2Au8AUh2HmMcwQA1lpILZH3ihCCD03TISBASAklp8HDGQfnrCDQoGrR2+z72GzLy2ByR+8sxuXrlVIJIwahFLK8h6LXR97vI9HpNOjWlW+apvW2i4EtijZBDGxR9DCcfennuuDlfsHFNYz4DBFezEm+v6PizXf+7RXbN/v6TjQveckuxiXO9gGLbddiY2Mdk/EIztrgnLcuhJJxZpWSUEqCCwFjpofohRAsL4r+4ta0v7x8Yn8Gvrx/kdZaXCqFfKbSmrI8h5IaBEJwHt5YOOdAxCClclyqNlUxsEXRZjihP6yi6OEiIJxRHr6fgf4YhFVGVADhecb6nxVOvuvw3/z8OeEE/+H/ZDq5Trl3/lnO+bxtGjRNDQJBSgWEUDLgnkTpSioFYtNeYl3bgoiQZjnpRPeSAR988IMnbgHJ8jKYnF98Goi9SUqRSCnBOUPXdhgNN7Cxvoa11RWUkwmkUkjSzIWAdmOtjoEtijZB/AETRQ8T7d5rrHdX++C+BFDw3mfGmKe3TfP2pmnee/eF737utVddEQ+yPwkmh3Rat93Ormu4cx5EDEJKJDoNQoqSEbuVcbYuuIDgAkIIKK2g0wQAmGnbnq3CANddcMJ+Br5kcSnzQb7eO+zsOkNN06CqaownY6ysrODI4cNYX1vDeDyCdy4QwVnbNqapYmCLok1wwn5YRdGjEFbuwgp8+FMi3Od9IGs7ZdrutM6a17fOfoDlaz++b3lXLEZ4AoUAssJvdW13lrOeQggQQkAnGowxcC5qlejbuBDfIsAxYiAieOfQtYaapqG27QpTywGu+dET8jMwLIP1c3MeEf1UgNfGdOg6A2vt0fNqE4xG63DOQHAGRgRG5AjUVEzFKtEo2gQn5IdVFD1aF175YWO9/Hvvwue9cz4EkOBMkMcOZ+2lpqs/kJ+WX3rtVUuDzb7Wp6q9e8E6Y06qm/p05y2ITXuGpVkOxllgjDVKJbdxos96HybGGlRVhaZtAUbQqYJKEy0UzX4Naydkpei3znxbCiFfyzk7h3FOIALn/OhIrwDBBZIkQV7kGMwMUBQ5lJJWkK5vuTGLgS2KNkEMbFH0CB0obtnwzu4J3t/BiIFzSYIzxonmkyTZ1esV75/bke++Y0+cjPBE2ImdvGvrc+qqnOnaDlIK6CQB5zx4ohBCqGzAfY6Jb3DJ7wKAQIBKEhT9PvJen5Iskw6YPyCrE241dM+eJa4H7DlKJ68XUirGGISYFmdMVyg5er0eFhYWkGcZGCMwBjCCtT40t984FwNbFG2CGNii6BG6+OL9tintdWD0aTBmiQggRlJJyvO8p1TyI6BwBTK8/a4vvfPMaef46PHimpOl0umzGReciMC5gPcB1hkg+OC8q2zXbcxk4i4l1J1JkiLPCiilEEIAgcAYV4HYXCjbdLPv58l2aqdy7/EGIeVZAYBzDpwzCMHBOYNSEnleIM9ycM7hrEXwAUTMccbrA2ocA1sUbYIY2KLoUbhZmhFjbC8RvhtCgHce3ntijCgg5CHgfCnl24UXV97ymbc+a9++XWKzr/kpo9ia9wczO2dn5ynPCxAxOOfgfQCBecZYrRiVrdZDj/BdAjnOOXwIqMoSTdsg+CAZ0ZzS/IQKbHv2LPFU07ODD68M8JKIIISAlBJCCmidQGkNIMB7D360GTERAuNktRL1y9avi0UHUbQJYmCLokdh9+69bpZ1NzDgLxF803Ud2rbFtJiOwIlrAjvLWrebOfb+k9Z2/Oi+j8TJCI9VWF5mPalOz9PslKIooLWG4BxKSnDGEYLzzvnam6469O3SGtPeUHd107QN2qZBU9fTP00j6rqcLaQrNvuenkznlKZHzi11nTm9qRswxpCmKYQUICIoNa2oNcaiaVt45xB8QFU1sMY2Sujmpr0Im30fUXQiioEtih6lua+fMyHgE11rvm2dAQHTM0BMEEDknZNw4WRO7DLvwweKcPgVV//ei0747vqPxXXbD3DHcB4RbZ1OmwpgbHpgvus6TMoy1HVdrwyr9oL7d7i6mnx3uLE+2VhbRVmWsM7BGktd1zJjTV9yzC4t4YSYCxv2LHHeyy4MRJdZa0RVVjCdgVQaSioQ0QNJLIQAa6bFGqPhEJNyHKx1TZIM2mXEwBZFmyEGtih6lGh52Scr99xS1fUeIlalWQYhBIgRGGPwIZCH5967ha7tfsL78N5+svX1t37sPVs2+9qPX/dLZ7tznfOpcw7GWvjgYa1F01RwznoCahZkg+XlsHb4yJHRxtp9VVUCCEi0hk40tNbIs7xIi/7cM7DzhAhsd2eyn2bFT6d5cbJQmrwPsNaCMTY93weg6wy89xBi+ki6tkPXNcE7GxjjjUxlixjYomhTxMAWRY/B79z+xdp14TOJ1l9PswJKa9DRFglcCHgfqG1b5r2fSZPkojwv3pOk7q13fPw9p8dihEduZlEUFOgZAYE572GtgzEOPnhonaAoipBnWaVnZEtAaD2GiU6/m+c9ZHmOrCiQZTm0SkgI2RPE5/snD57ygW3fvl1i0rEXhBBeLqQUSqvpyC7OgODRdR3qqobpOggpwBhD27Qw1kBKjSRJAwh1ZYbtZt9LFJ2o4kHoKHoMlpfh376vd6d2xZ9LrS4IQfVCCAjeA2G6tUSMU1pICC4KIdSzfKBcpn5u7Svv+7MQDt9MtDdW3T0MIYC++6l8lnPxNC4EQteBaLqayRhDkiaQOnEwoZYjbwDgvC1z1UaQt4AAIQWU0pBCAAA1tc2DM/NPG8xIAPVm398TSd+m+xtsuFupbruQEj6E6WSILAMxhrZp4JxDmmXIsxxVVaFuanAhkGgFwYXjjKrWjGJgi6JNElfYougxOn3XR1tO+JK19u+stSDGQZzDew8fAuS0TQJlWQbGkQD+3BDCm7rWv+/w3y6+INywrDb7Ho4Le5dY27ozrPHbOJcQSoFxBiCACwGd5RBKWwFRFavCAcCh9dutD+72EHzFOIfgfDoYNniQ4DrReuH0s+bzzb61J9K+5V2iM/4lXWdfXlUVHw2HaOsaQkporaerwZxPW3nkPTAuEAAIKaGUgpQSjPNAoFqtixjYomiTxMAWRY8REcKoxb1VPfnT9Y3Vw3XTgKQChID1Ds47MCHAdUKMC/LBy7ZrT2vq9jWTYfn+m77zvZfu27N0QlUrPho3Arxr2mc1TZ17APlgBmmawVqHpm7QlHVoqqqr7aRy25wHgNu/dKY3bXe7d341eI8QArquQ9t1EJzxfNBf6M8NnsqFILT1mScvqqz30wBts9YAAJSahjUheDDe2eCD40IgBI+2bRCCR5plYILDuQBjrW+apvJUNpt9Q1F0ooqBLYoeB+dc+ptdNan/uppMvtS0VTBdC+ccAALjHMSmw8lJCDSdoaZtedvUW9u2ezl58775zr36a//lpfObfR/Hso1xqxFwftPUsqlrMC6Q9/vQSQJrO4yHG6gno86hLtuTv+EAYPfevR6puFtqeYSI0JkObdvBGAPvA2eMLzJJ/c2+tyfKvn27OOPyRVmSXiyV4konyPs9JHkOpVUIgZmmrNcm5bitqgpVVaKq6mkPNsbgrYP3DsF772xX3d2WcYUtijZJDGxR9PgI1cp3VrRK9miVHvDOwR/tIK+UBjGGAIALCWIM1jiyzjHO+Czn8sWews8hET/9zT941UmbfSPHogBQT+oFYjjDe0/1ZIJ2MgEnjjRNobUGYwTvvLFNWzUbR77f3DXMAkOl9O2McTRNjXI8Rtu25Jxj1tr5SW1m8RT9LDy92rkQGHsTEZtLswyD2VlkWQ4QwTkfjOs22ra5rWmbuq4qlGUF03VwzoMYQSoJ4gTrnLMh1O3qxGz2PUXRieop+SEVRZvhwiuvM3O5viZJk89qrZxS6mhYI1hrYTpz9EybgkwUpFSktCShRE8q+by86L8z68+98/bPvfO8sGfpKV+5+EjsXQLrmNvhfDhDCgXGCV1ToxyPUJUTAAFKy0CElkIoz05vfaCQgy3mrdL6JkYM1jg0TQ3TdgjeE6PQAwuLv/GBS+Rm3+Pj7YY9S2rSVT8ZnH0JKDDBOYgIdVWiLiehqWvXVM29zvkbOfH7rHXoOgNrLYL3UEojyzJ471HXpTFtW7bS282+ryg6UcXAFkWPo23pHeucxB4p1b1JmkFICWsdrDHo2gZtVQHeQyt9dAQQEQhIdJr2BzM7817xVgrsPd8GnnPtVRc85ULEo3Xmyy5gIbDTASwKwaG1RggBVTXBeDyGtRaCSzAujOeiwt/ggfFJq7coZ5y73TvnGGOQSsN7D9N1sNZlBLvtec+RT7XCD7rvnju3rhy+76fLsprjXCCEgKasUE4mqMsabdvV1tnvMtDXmOC3CTGdGKETDZ2mSLMcUiZw1gdjTeu9nxRiJo6liqJNEgNbFD2O6OL9NtX0j0TsY0KKjojgrIV307NA1hpY78AYg+BiunpR1dS2LUmtVFL0zgDjr/fGv5/kjhdf82sXnVCzLn+YrjxTdE3z7OCD5EJACgWpFIQQ015i368UVUknmS5p+X82d719dt2bur6z7cyqUgozMwOoRGFSTmh9fT0djyc7RmSeUmPD9uxZkiLtvWxSTl64Mdxg48kEbduCOEOaZmDEQtdWh5xpv8k0u3EwM9PNLi6i1+9BCA5GBGMtmraGNQYAtVyn5RZ9OAa2KNokMbBF0eOsf++9QxvCXufMrYwxcD7d3Qx+OlDbu2ln/ta0sM7COw/TGRjriTgXSuvtOtGXQrL364XFS67dszTY7HvabCOMUuvtM30ACSmhtEKa5SgGs8jyAt57tF0biKHjpGr8k278S0t7fdV1h7uuOWCtgZRqeo4rAOVkklTj0VYahqdMa48QQM+APnV2dvEtadIb1HWF4XAdQECRFxj0eyHNU+Mcbu+s/WaeyruLwcz87OwslFIwnUFTN6gmEzR1DTAKic7aVCeT8f29OOUgijZJDGxR9Dij3XudGLY3dl33ScZ5mxU9JGkGYgTvPazpUFUVJuNx6LouJEnqi14f8B7NpCQhBO/1B/NZnu+SWr+vr7LXfW/P0uJm39dmWV4GO/X0LTt6xeB0rRU4YyAQiHPoLENe9MFIoGkadK1t4c3/0nqCCKGs/EpdlvcNhxsYj8fgXCAvCnDOuPNuPs+6OeCpMXni1s9domSSXpJmxQtmBrOsyHtIlEZRFEgSDR8CGONllqc3sIBbhnXtmqrKu6ZBCOHolA4OYgAXAmmaQSWqsW1THtm5GAPbEyg8Rb4HoydGDGxR9AT48uRgaYP5mA/+Rs45dJ4j6/Ux7TI/HbLtAwBiJsmzjd7MTCmUCE1Voq4rIsFZmuaDJE2f75y7sjT01hv+YOnUE/ED/SUv2cWk0GdlSbJFSQkcHUzelCW6qgECQWkdBHFnbFeVbvwvKhmHpikD6I7gfajrCm3bgjFCCIGH4OcFpYv7lncd94UeIYCY23qq1PINWqm8KAps2bIVg/4MnLEYj4YYDUe+nkwOBuuvX1lwh+vS98brG/l4OIRzFmmWIUkT8O/PxeUcwbmuLOvJTTftjYHtcRYC6Gt/8Nr5ff/tkpOvu+qCOH0o+qHiN0cUPQF2797rDn7hbd+xgn+sQ9ipg0y4UtNebIygmATjDEKIQJxvcM7GjOlTrHOzTdOSM4Y452BM5CbUzwrkU5+I2e997N1/FuzwFtp94oyzOrlOueu6c5uu6ZvOgPIcUgW0TYOuaafNia0FgXlnfZlw+he9ws6YTWxrw22wurPe6bZpAAQyXcesd7Nc0tb6+SkHcFxXQe7/6C49l3WX5Dx9ntIJcS4QvEPDG6ytraKqy+Cs6wLhVsnyG+skbefQzBhLBTqCJo0sS8C5QNM2qKsa1pgAoiZIPVlejoPfH0+H9723+O7n2meTrM4V1vzjBfdddgC4brMvKzpGxRW2KHqCbP3qH9Uw7jPw9C3TdTBtO93zIACMIKWEEEI4a9KqrA5Y578ihbpbMHZ0FmmgEDyMsQlATxOSvZlJd+Xd2eA51151xQlTQTo5tDXtnDunaWpeNyW8tw+MU7LOoqkqNE2DgOCVViUTg3+xwvYMwEnGv6u0rqWQMMbAWQcxLVQoEp1sm9xTHNeFB8vLYKrJzx6Nhm/ZWF/NTduAcQEmBECAcw5106Bpm3FnuuuZpLuXAPTzwYwQMm/qGuWkRNd18N7BOQfnbCBiIc/zCiJtgBjYHg9hzxK/7dNXnjNpcbmAuiJNdb+t5b20vByLOqIfKga2KHqC0DK8oOpWwP15cHbMGDvaYT4BCCBixDlnIYRZY82Wpm2+7az/v0H0985aV9c12rajABAXJDmXp0Kw1wVJ750/tXvRNXuWnvIVpAEgmbu+9/YZ3nlijAPEYK2FtRbOHW2Z0nQwxjp4VIx4989f50M37Q1g4l7G+BAEMEZIshSD2VmamZtLsyzZkR6pj+vxYK/aflmi8uQ13vlnDjfWaThch7Mdgg9o2hYEhH6v73r93gGdsn9cnL1thMUjJLWeYUR50zSYlGOMRyOMxyNUVQljDaQSNs/zSZ4UsWnu4+DGPe+auyUdvMI4+2/H4/E7jWl5momrr175+PpmX1t0bIuBLYqeQFtf/kdV1zafdc5eR5xBFzmyooDgAkIIKK3BiCtGdIY39uxxObzJmuY/VlX15clkYqy1EIKBiCjACy3Vdp1klwbI980y8bKbP/nqp/IcTGAZVNfjbVVZn+GcgxQKRAxt26KalGibBiEAjHGEEJxp21K24V8Ei+VlhNDV6yH424kxZFmOLMtR9Hooir5OsmTHYCE9bqtxl5fB9Pzs2UmSvi7v5UlR9CDEtG2MswamaeC8RZH32vnB/Hd4Utxy/u6bDHrnEhDmOeeZ1hppkkBIAcY5hJj2bqvqypV1OwkMMbA9BjcsL6nr/uCnn9XZ5krr3K9471/Vto0fjyefzzr2neVlxNW16EHFwBZFTyAiBDke3825+Et4Pw7WQnCBNEmhdQIhFDHBqO26QVmXF7Z1+/yubb/bdtUHGeFTnDFLxOB9gLWWuBBc6mQRwE901r2nmqhXfuszb57d7Pt8oly3/QIOj/N8sH0pJYqigNb6aPf9GtZYSCWR5RmIcW9tNynHR35QsAim5OOyru4IHpBKwQePqq5QVWMJG7aetmX2uK0UfdUFVyRSijekebZzMJiludl55FkOhIAQAqTW4EwGBGxwob5pvT8IIOz/9i08OLclILA0S1H0+1BKgTEOnaSQSqCsxnY4XB0533aP+UJPTPS9z75jEc9MXy1S9stM0M9SwIVCcp+lyZetCvt2XPbharMvMjr2xcAWRU+wk5f2NkLhc8aaa8rRMJi2mVbgEcF5B2KcXHAsWL9DKPmvwNXzrZc3Si3/P4zzPQBVRAQCg/eBGCPGGJ9hnL0oTdMrCpa84fYvvXvrZt/nE2FwyhYmtHq61mlS9HrI8hyCc1hjYew0l0kpp9W3zrq6a6v72egHrgTN33+kpYBbBeMeAKrJBBtrazQaDqntmlmrw9arrjj+qvTC8jKbk+78NM9eV/T6WikNLjgYY7DOwjoLpTWSLPHO+7urpvuWvrcdAcD2uTOlNXZ7VVWw1oKIYDqDuq4RQoBSGoIL27TNpPNlDGyP0DW/tpTe9ldXXiBF/gEuxC8xTq8Ukp8SELwPuE6r7BMXAQc3+zqj40MMbFH0BCNCGA1xr2nbP2vqZqNrW1hrUTc1mroGESFNUuRFprM0fTpn9LpiIM9mN+Hbktj/k0L4I8FlpZWGYAIERirRpLXuCSF+BAHvco396Vs/8Y5TnmptP44c8LnpunOl4Gy6suZQ19MB5ZwxSKWm5wEDAgDHOKtw1w+u9FzdPnZJlt+lUl0SAGc9iBikUBQIs8a2J5/+Y/PH3Yiqr539tWJjWL4ueDqHiNH3ywIYY9NCg7pG19TBOVcba76zNipvvXh5vwWAPhOaKbWdiAFh2ty5buqj7T+G8CGg6A3cTL83KngTt0QfprC8zK75g6WT2ny8+8j66q92prs8BDzHGNs30/OXt3Wm/ViOcP2JVPEdPTYxsEXRk+DsS3+zc+1wPyNcDQDWmKPni6bjqjgXlOU5pBT9gPBjpnavOTizNnuObW6xgf86cfoTxlnNOAMTHCpJKEkTCvD5uCyfNRmNLjeufds9X77izOXlp8b/1wEgY/28MeaUABBjDF3Xoa4qOG8x3Sp2sM4hICBNki7v9epd2P8DzwLdPnumL+aLe7MiH2qdoOj10O8PoFNN1tpe0zQ7FhOebfZ9PxJ7lsDLjfY5TdX8VF1N1GQ8QmdaEDEQ4yBi6NoWw41hGA3X1yaj8fX33bV66OhfJ5Xlujfobe31ekizbDqVIxCMsaiqCsH4kCSZTXV/IkpxXLc8ebJc/4Vfym97/sGLtOL/JgT6N/Vk8pNVOdnRtq12xgdyWA0u/I9u0vx13AqNHomnxAd7FB3rCAiO1g8yzv+Sc7HCBUeSpgAnbGysYbixDmsdEWMMwFbv/SuLueTiW4uJWLvp3tuMtb/mvf9d48yG6aaVf0KnJKRE8Dbxzj+NS/UWRvqdu5/1tp179uC4bwK7d2mJ5f1ia5plJ0spEcJ06UgqBZ1oMDYNI6btEEKAUKrJVVbjgz+49cTS7r0+SYq7tE5WlNboz8yg6PWAADR1o9u6PakzfGaz7/uRWPzRXb00zXYnSp9hOoOyLOGsQwgBwTtwxsAZCyEExwK/kzt8+57/9JUSAEIAPENKYIucC0gpobVGr9dDmqbIsnQ6oYNYN67qyR98wsaVoAexZ2mJf+fjl5+eo3wLMfarWuu3ZGn+jCRNCiLGQQiM81ooeW1ayE+OTz1yYLOvOTq+xMAWRU+Ssy/9XKcS8ddCqb9NstzLJIG1Fmvra9gYrqMzBkSCuBBSa31uluSvd2bhmbuW97tnevs9Y/yvOWv+a12VG3VVwloHxjnpJKW0l6skTc8Gsd3GdO86efSyZ1171QXHda+2M192OyvS3ulJkiwQTVfXACBJE6RpBp0kRxsRA53pQtd1jfWmJvrBgY2AkFTjjbZu7mmPbkszzqG0Rl4UIi+KkzQrFnGcfC7u2bPEZ7dseW6WFa/MikwqPQ2xgnMgBDRtA+cspFIYDGbLhS2LN84U6W3LOFqN+KFlWl9bnx1tbGRlWaJpmgee72BmBjOzs8jyPCD4emVtVF1zOA5+/2E++xuX9LdfUl08qZpfqsblBzrT7VI63Zr3cpn3+tSbnQt5f8ZJpW7nxD7OmLn+4ov3xxXL6BE5Lj6YouipgICwfujIIZUkf67y4jDTGmAMSiVQOkUIeOA8W1EUeV70L9JCv/47f/nWbbR7r/vv13/07s61v2Ot+W1nuo2uKtG1LRjjUEITF0K2XXtaXbWvNZauYMmpF96wvHTcncn6vvlze5yBziMiyRiBGEMIAW3boq6m892LIodSOrRdFybjcb0xHjYP9prl+no3Ho2/OxxuYH11BW3bQClNg8EM9QczW5O8t/2qqy44LlYnnynSGa7Um5ngJwuhKEtzSCnggoO1FuWkRFVVkFKFvJevZFn2jR2glQdeYOeNtLaxujAeDlVVTlCVFapqOrYLBHDOgrE2WOtLwXl13XXXxaa5/8yeJfDP/9aus9OE3sGZ/BVGtNR23XlN02SBwISQ0CpBVhSYmZldS7Pky75qvnT2pX8y3uxrj44/MbBF0ZPo/N17DXn6SmD0Nx7waZZj67YdGMzMwjmLuprAO09plrGiKLZwIS4lRq+4Zs9SurwMf9ZXP3Kfb6rfCT78VwS/7q0DYXrA3HQd2a4RxMJJSZK9Ki2KK/oXzb0w3LB8XIY2W5+kSPDz2rYhYwykEGCMoZqUmExG8N6DcwHOOQSxwInV6EL9YK/51ZtudE1d3drWja+rCsZMz8IRiIjQV4k47dn50475hsT79u0SaZa/kAv+iqZuRF3XMMbAGIu6qlFWJdq2hfcucM4MEd3Wdf7bH77/ggcC7XXrtzMPbPXeJc55OOfgvYcxBuW4xHBjiPFoFDrTllKLCnHKwf/iW3/y5tmtl77mFYzkL3Mu3qt08qNCiAUiks5Ysl0LzjnSLAuMqOEsXJck6hOHdhy4j+KzjB6F466EPYqOc2Eh2Thy76S3x5r2xymE7b2ZGZi2QzncQNPWUEpBKklAEKBwVpLopZPy/HthGV+lZfiw/Ef33vf8d/92g+Cds+8N4HOMGLz3QAClacaDD9tA9K8Apg6vjHW45heuph/99fqxX/6T9JAAOsAHg4mpz2jqGtZNty8F52CcQYZpdWjTNCAG6CQBMdYIsg96iHtp5zPc9V24IzA+9mADRtMRYSF4sp3JibEz+gt5DmC02c/gwZx85IzZVre7nfM7vPdomwbeewTv0XUGXddNx24lKYjYODh/ffCTu5b/yeijZPZMslW3wJlUgIUHpsPeGYcPNTaGY4BYIGKlLV2JGDIAANdedYFMt59/rpD6smIcLuXEdgrBB0TEnXPEGIMUAkpp5HkepNbOO3d768wnmgbfuPjSuBUaPTpxhS2KnmR04YdtWF3/+3K4vq8pSxfCNHCkeYY0zcAYR9d2aJoazoeUCXkh5/wNtz7/LTuA6cir3/367x6oJ+2Hq7r5naZu1q2zCMGDgaCkIikFD8EvWu9e5gOuONjQxQc+fcXxUwG5vExt126H9/P8aN+1uizhnEeaZkizFAhA13WwnQXj3OtEN0GoB90S/dBNe4MQ/JBS6n4ioCwnMMaAGIN1Lumq6hRUw3kcw+1Rwr5dgqfqJQH0k1wIPjM7h36/D84ZiKZrN8YYeOfAGPmAcNCR/+aR8sjGP30dfXBC3oc5qQTvDXrI8gyMMTBGQPBo6hLGtB7BVUy0cYUNwI2fX5pz/XNe1dTmV4PzP5sX/efPz8/PpmkuBBfTmiEPEDFolUBJCQKtBee/bMfjL5996W/GrdDoUYuBLYqefGHUjlessX8ZnLu/rSuYtoXg8oHAVjcN2rYjokCMMEeEn+RILz3w6csyAFhehv/L7/75fU01vooIvwVgFZiegeNcQAhFQkgWnJ9vmuripppc2VB42fVfeFu+2Tf/cOx/yX5WN/Vp1rlZxjm4kAARhBBIEg0QwXsPrTWU1iAQENA41j3oKuLyMnwX6iPGtPfWdYXJaIjReIiubRCCF977bdLzk/Yt7zpWz7HRN0ZnbvEMP62TZEt/ZhZzW7aiPzuHNM2PbhELSKngnAtt0xnn8T3nxHcufsf+9p++UDXjhE7TecY5cSFQFAWKoo8kyaC0htIKaZL6XlGUC3PF9NDgCeqGPUvqa3/+xmeHOn9fmiS/LIR4pbHuNCGlljph3/8+5JKDjkZ9xigY55qua75hu+6Tp9yDu+NWaPRYxMAWRZvg/N17OxH8NZzzvzFN46ZVn+aBoebTFhYEIkZCSq6S5HSZJK9f6XoXfr/P2vIy/Jfv/9K9pPxVztrfCs6vKKWg9HS70FpL1hpmunbWAS+2IVzJhubl+35r1zE/5PzkOuXW+9MQQk8qjf6gj16vN12xYAwheITgIaRAkiRgFHxTN/XKWvOQ3fiHa3pUVdVd3nkQ42ibFl3XkeCSSaUXWkenHum3x2SF7bVXXSAm9x/6idXVlZdMypIHH8BA8M5PV9eIQJwjTTMolQTvMfTWf+vI4cm9+GdhQRXbkizN570HlZMS7dEqXCUl8izH3NwCBv0ZlxVpOXtS3T6a630KoL/9/aXF1cr8VFvV/76sqncbY57rgx9Y53jbNGiaGp3pUNfToo0AD6VEIEY+OHdn2zafbFer6+jKD8fGw9FjEgNbFG2SZn12lRM+5q075I2BsxZVVaIz7XTckuBw1sE5B85FKpV8npLy9S85adeO77/G8jL8x7/9sQPC2w/7gN/mXK5ppcCPVlR674lxIqXkQEj+o97hil5v9pIb9iwd06HNGamUUKcJJXmapOgVPWTZdEfXWQfOpqOXvPdw3qOz3k4mZXXnbQcfsldY7SYmgN0muDDB0wN93HSiSSo5CCycjkYcc88nAMSK03aQYG8pq2p2PBxjNBxiPByirRs4N20i3LYtQgghyXKfJNkB4fj1t+CW4T9/LdWbyVWSzHEhEHxA1zRo6hJd10IIgSLvo9cfdFoV47sPVifcuatrr71AXvMHb3qOFP5fB2N/OYTwCmfdSU3dqqZpCSHAWYu6rDDaGGI0HsE7C6UkOGcI3m0Ewl9zxr545u4PH9NnIqPjQwxsUbRJLrzyw7Zt7Ve9d/tB5EOYhg8pNYpe/+jWaI3JeEJ1VRInmtFa/eRMMX/Jnj0XPVDJuLwM/2c37b3fEP9vAP0uMT4UUkApBc4FnPMUgiNBvJ+myUV5r3eF5Okrr/69V/c2+xn8MLady4Xip3I2nbn6/WHv4/EYVVUdPVhvMJlMUJYlTNearmuqOc0echUjXatdmqS3qCStpZSQUkJJPd1eVTrVaXZG2puZ3+xn8M/d+tkPqN5g8WVpmr6QiLEQApqqxWg8xqQq0XUGRAwBQFXXaNu2ZYJ/18B+98orr/tfn0sAmG8y78Is4wxCy6PfKw5lWaIsS4RAQei0Y0IPr7uJn0irQ/SVP3ztFnfLWW/oTPkfq7q6nBF/Jue815mWd10LISV6/QHyogcXAspqguAsdKJBLIRJOW7WNzb+sanrT5wyaONWaPS4iIEtijZPqFayFQL+gjE6RKBp41M5bVXh3LSflvcOXdOSD4HrJDtN6/Q1p9XbnvNP54YuL8Of/83T7rMwVznvPgJgyBgDCKirChvrG9S2DXEhekmSvoBJcUWWJq/6+z++pL/ZD+FfPJQAItiiqbsz266FsR2qusLGxgYmkwnquprOx+watHWDtmkQAmyapSXLew+5wvY32O8d2J2C87I/GKA36IMYoa5r1E2tbdeesq3g25aPoc/HEEDMjU+11r4FoD7nHFIqBARMxhOsra1iPBmBS4Gi1wMRhbKabNSm+YaR4V921P8QaDyc5G1bz3rnQNPGJjDGTkPwpIR1BtZ27Xg8Hg8P44RYYbthz5K69s/f+tyi1/83xNgvW2teFgK2K62kc47WN9bQNBWyNEPe74MLCecsiAhJmkErGYyxvqubO8cbw0+N7q+vpQvjVmj0+DhmPpCi6ER04ZUftil1X2OM7/Pe+6apUU7GqOsSjBGyLEeSJCA2PZ8kpEqIsecx2Nd84/eXFv7pa9Hysj91beUu6/1/MdZ81Ds3ssaibVs0TYPOGAICMcYKzvmPSC5+NhOzl33vsx84tkLbh5YJjOaddYtt08BaB2ssnHMghqOFB9NZoGmeQQkZEq1NnhXVSTPuIQPbB5cRlHQrWsl70jSFlBJ1XWF9bY0m4xFru3aLDf40XH76MdO/bv9HL9fDsr2sadoLjTFMcoEkURCCIQSHpi6xsbGOjfV1+OBCkqaOC35vU9bf3Ji5c/LPX2/vziWyjSmsMfN0tLWJTjSElOBCIEkTSCHQ1XVXjyfj4ZH2qT6Wiq7/2Nu2TCztdq5bphAuhws7Ged5nudMJRree3jvHqjGbZsGVVmCgaHX66PX70MICcH4uhByf0r8C598x0fjVmj0uImBLYo2Vxi1bsW78LG2be+vyhLlZIK6qsCIIc8LJEkCAKirmow1xKVYEEq/XGRs17VXXfG/HI6n3Xvd368evLO1+C/Ou48EhA3OOZRUUFIBYGStgTFdwRi/MMuzn+2nuOzI1f/2mNke3f+S/QyMnwpGWQgB1hh4H6CkglbTrcs8z5ClObROphWNSjshRNV33UMGCwJCaouJ1Po2zjk4l7DWwhgDISUlOpnvFfmZl1z43GOionZ5GeyUHcnZSurdAb4QRwtLGCcoLVH0CvR6fRAC1tdXMBmOIAVvsiS9Aa297eKL9/+LZ7J40xEiJWe01kqpBGmWYzA3j/5gBv1+H3mRgxEL3nQteBgNnqGfsqtE1157gfzGnsufbVz4xa5r/7eua3+ia81WMMii6FGaZbDOwlqPophBUfTQdS3K8QjWtBCSQ0gJa00oy6rtjLmeBD4h72J3LX9/DFgUPQ5iYIseN2HPEl+79pcH1157xTFZYXesOn/33q4uN75SVeXfMqIguIDpOjRNg+AdAAbnHKpyjPFoSAwQWZadLSR/nZofn/XPX2/37r3uxnZ0Bwf9JpP8I1rrDSDAOgvnHSaTCVVVBcZZLiS/wIO9G/CXfe/vj42VtsUji4zDny4YacYYuq5F29bgkk+b3PoA6zw6Y2E7C+cDAmACQmXH6cM6KzRokhre3zqdlsDAaLpqQgAIyISUZ/Z39I6Jc2zvevkv6DwtXiu0fHYASCkJpRWIAMY4sjRDr99Dr9dHohRC8AEUVjjn/7iy4Q/jB5yf6m0/l3SiF9M0Y0orCCnBBIcH4Lw/uh3fwXvXMkMTYP9TMnhc87uvmBt/Y8vrxuXaclVWb3fGPb1tm6yqK8aFQJql8N6jKitYa6CVRpJm4JxPzwEyBsY5QnBhNBr6tdUjd69tbHy6Gm5ce/7y3oesWI6iRyIGtujxc+YsM4ztPI3PvejIzcfOis3xoFm/fZUR/1SaZmtJksD7gLZrYYyFtQadMfDOoxyNUTcViCjzzl/krX+gN9s/tXv3XvcNU90pBH4rBPfRrutGVVlO24YYA+sccUEUQsg7011oWvPunlGvDDcsb3p15ADgYDidCyanAQ3w3oMYgQuBEABnHIIPCDSt1Gvb1lpj67P1KQ8rWBwATF1Xt5VlWZXlZLrdrBSatsXGcCibtj2DOE7asweb2o8tLIM1G/XTJlX1uhCcFkKAMwYQwTqP1nRougbOeyRZipm5QZBaOhf8nYbCDX987ad+4OSHw+09zFm71XnHnPcwpkNZTjAej44WdtTojAnGuiq4slpefmodmt+zZ4nf8Nkrd8re4AMB+BVn3EsphC0ApDGGvPcQXMAZh6oqgzVdEFxCiGmRSpIm4JxPewEqiSzNILlYd87sM83k8xe96wvrm32P0VNPDGzR4+eCHU4KMeFJ8jrp8933ff0XT9mzZ+lYbUB6TLngyuusknRNkuZfVToJUqlpOCHAWAPnPbiQCCHAdJass8xau9U7d+k4LD4vhH/ZmX/37r3uG5PqTtP530KgPxRCjjljUFpDawWAqOs6VJMyn5STC9u2fffB9fKSA9du7kSEFZlr7/x2IRQppZDlGfqDGWil4bybPg9nMd1tCrDOhqZtrLFltf/+Wx5WsLh9dt23XXfvaLg+XF1dQUBAXhSQSpM1jjnvtnMlzrzo5KVNPcf2lVNenQ/HG2+cTMZP74wjqTVICNDRghJjLcqqQts2YJwhSRMoLavA/A3DjdGde/fiB24Rz2zbYGVTbRmNhzSejDGpSzR1PZ1n2zQYjkdhNBz7qmomtQ9Pqaa5N1/9zt6Ltpx8aSb5Bznxdwsmn6G0yqWSjI6umCmlgeBDU9e+bZuKwEZF0bNZlsE5D9MZOGsQgocUPCSpbnWafktx+YnADt1J9NR5XtGxIwa26HFDtOzJ8XsEY2MEfwVn/IqfOP2s54V7fuGYH6a92QgIupgckoL2cs430jRFohN4P+2r5UwHIiDLUqRpCiUVGOPKO3t+sN2rD/7dBxZ+0Ovu3r3XPU/jjjThv5UV2V6pVJUkCZIkhTEWw40hjScj1GWV15PyR8Yba+9eubN6+T17ljblaxYACtVo4Jxb/P5h+F6vh8FgAMEluraDO1pX4L2HNdNKWtsZM5lU7a4PPrytu6Xdez1LcKfgcpXAYDsDYzooIZAXBSmpBwRx3saRwaZtE+/Zs8QXtu94Vl70f0oqqbz3aJoGTdMiAJBSI8tSSCngQ4AxHaxzIcnUoaxIrs/L1SM/7LXzQY9ZZ7eOJmPqXAcuBLiU0xYVdYWNjXVU5cQR4xM1N2gewWUfs0JYZnfse8/popRXtrb539u2vcR7v0MqKZVOKEk08jxHlhYI3oXJuAxd160JLm9Ks/yWmZlZVxS96Ui0tgMQICUP3nlvjLmHCfqMbsXXL/35W0/UJsPREywGtuhxNfOcnWPJ8EUi9CSXb2VSvP/+A+EV1376pxce+6s/tZ19yec6Zs3feee+TsC0+a1zaJsadVXDGgOtH+gXRjqRxMDmvPMva8b1j4YfsppJu/e6Z25fvTXR6X/mUvwFF7L2PqCpa7RtjbbtyDhDpuvypqpeGLy/wqT9l33vex/QT/Yz+NAySGXJrBRywVoL6yyUUmBE6NoWACHLsqNzLzlCCAgAQgjt6tC29DAngBIQFl221u/378uyDE1dY31lFdZYFHkBBp5Xo8k5fjhexCbNFX1BttAv8uLNc3PzZ87MzJGUElU5QVmOYYwBKIAxBqUlpOKo2ypMJmNDnN0ue9mNF/3CV39o0PIHMklEi0JK9GdnMbNlC0SSojUWxlg4F+AD81mWTU7fflL9SK77WLTvI5cnd+5bfTEF8b93Xff+8XD8nLqpC6Eky3sFlJbgfHoeMC9yABRC8EdUqq9Okuyv8jz/XloULElzaK3BGJtWL1sL5+zI2e5vvDVfuOgX4lZo9MSJgS16XBHtdonw14PzLzPOTw4ULiXH3j+b9t9y+1//3Hn/vKox+p+IEHjTHPTOfco5O5xOKghwxj0w1Dt4j+Cn57ZCAEklBef8LO/8ZbcnyUk/9LUv3m8PzXY3c85/zQf/ybZrW2MNhFQgENq2JRssgaiQkr9QJskV2V3sx2/Y8+RuCe7cuUTB+hkX/ELT1KjKEp01qOoKw9EGjOnA+DSXeu/hbBe87XxAaIj4dNnjYToyKdquMzd3bQvnLII/GoCkBOBlWzenduTPvOqqC8ST+QyAaQFPSeZC69wrAUgiglIKKkkAEEzXYTQaYzQagTEBpTW6rkZZjibVuP728ND4rgfZlqOVbtJvu67PhYBSKQBC2zbwIaAoehjMzEJq5YAw4Xx0PK+w0Rd+++VbNC/fMhmNPjgejV433BievLa2qtq6JSEktNJgjKNpWjRtDUbkpVKHsiL/fK8o/kxK/Q0fwrlNXYnJZHw0pDlMJuNQVVVrbPcta8wnFXBr3AqNnkgxsEWPv9NvHLvG7HHO3NY1zVwAXpDm+buTJPm5rU/Pf/SY6/t1DDl5aW/jPb7knfumsQbGdiBGSLMMUsoHuv63XQvnPJRW4ELm4PRjTKqLHywQX3jhh83GysHvMMd/HcS/wDnvhJAPNOl11hExEAIG1toXtW17JU/zH30yQ/biTUdoPNxYHA9Hg6Zp4JyHtRZt26BtG5iuhWk7eB/A+XQ0Vdu0AR5NlolHtBV1+/1X29HG2ve6rglpmiHLc3gfYK0lIRVJrbcqKc47E7NP+pm+/baZHW2M3jocbZy0traG0XgIj4A8z9Hr9yCkPFqIwZBlBXq9QUjSnncI94/K9X8cj+79oSs9y8sgQA18QG6tw3gywtrqCtq2wWAwwMmnnYZTTz8dC1u2OCKMx3ceOS5X2Pbt2yW++Fu7ns0C/rfONL9UlfVFZVnNNG3Lm6ZBWY4wGY9RNzVMZ2CtCZPx2LVte0Aq+Wmt1R96n3zV2+453rlnjIZDOnL4IDbW11GVZehM50MI97Zd+5nRePS183fHqtDoiRUDW/S4I9rrQr1xvanqT7TlxIbg8iTPz017+RvSIv3A7EL26vIfl3eE5fj9988RIQzk+r1M8s9yzmoiBqkkpBRH2wdMV5YYY8iyHHlvQCrRjIifEsBfOb+jO+vBXv/83Xs7x+X1Ibj/g4jtB4IFEYQQIBC6zlDbNtQ0zcB5+xIO+tlse3fBk1UteQSHWQhsu/dOKKWQJAm88wiBoKQG4xL86MD3JEkghIALNnjXNb7jBo/kcPzOZzjP6A6VZpPB3DzyvEDXtRiPxwCB8rzo6TQ9b3ZL8aRu51971QWSd/wlddP85HBjQ2ysr6Guqum80zRDf2YG/dk5LGzZii1bt6M3M0A+mMG2Hae0iws7vsctu2nX5ft/aHjduXOJ+v3+zMzMTJ7lGbq2RTkZwXmHot/DzPwcBnMz6Pd6hhif3HRTc9z1YPvk772op++ff5XOZpZB+BlrurM70yXEiNI0gVJq2kttMkZd1QBCEEJ4AAe8t59KhPgDNequPXDXzaeMy/HutmuTpm0eGPBurYXgcsw5v5o7fP43P/e5tc2+5+ipL/7AjJ4Qcy+Yn1jXfiwE/z1vLcE5ybnYJqV4qUzS9/qEv6t88//rWWETzkkd67Zf9+kmSdMvZkXvO1mWQx7dthRCPLAdKIREVhTIej3oPAeXMgFwoQF/xTUPUTBwzqW/2Q5KfW0I7v/suu7rAd4VvQJpmsJ2Bk3TkQ+eGGGWwH7CteZnz2vf+OzlJyFgX7TzGbzXG2zP8h7pJAURg+kMGDikSqYd+XWCLC+QFj0keYZEZT4ArUf9iILFTTftDf2t2w4MFhYPJFkBEFBVFcaTEYwx8CEkprNnMyNPXn7yPiupS87cQmBv9d5tscZMxx4lCYrBAL2ZAYr+AIOZGczMzSHv9aY95MBCrzc72rL95G9kuTzwYFtzizcdIUF8IIXIhRDwzqFtGtRVibIcoxwPUY5GwZimFYJGfNUeN1MOQgB953PvOf2shXN/bmZm/t/PzS+8lCu9UDelaJoanAkolUy3052DUgmKIg9aJ07r5J40ST8uGP+DAV/55m2Hbxck6c0++HOCD8QYg04SZEUWsqIwOtE3BeY/ueDWb/1h1bhR9HiKgS16QhAt+0zJm7mUnwgBpp2U5NqOBef6tm2fa1vzM75z7xvVg5fe+6VfPSYalB4raBley+I2LvgXlFaGMw5iDFIpqCSD+H57DzPtz0ZSUdrvU5IX25JUv2LH/JanP9R7nPGOjzZU2a+0bfP/lpxf1+v3Q5pl8N4hOAcpJHHBWN1Ui01T/ytKxDsvf9F7z/tB7UMeT3eNW8GI7+BcIASCD9NzZYwxOOumFaJhOkVVcIFEJ0iyLEidtJzLR7QltbyMIIhWgw8HmrrGZDLBaDzEpCpR1zXKyUQMNzZOHm3U515w1QXJE/11B4DP/sYlSmjxcp3qH+/1+3xmbhZFrwedJOBiujPtjEXbNJiMRlhfWcXG+gZG6xthMpnc64O7/vZbbnvQcUi3bB9TVdez49GkGA2HaOsa3nt0XYfRaANVOYE1LQJ8zRUb33P+4eOiae4Ne5bU6tW/9PxBv/eraZa9Vwn1LCF5TiDGmJhWVysFayx8CMiyHvozg5CkmWVEdwP+Y0kW/nBhx5FvfbcYd4OFHS8qisFrEp1IpRSyLMPs7FxYWNjie/3+/VqrzwZnvnrGO/Yfz2f8ouNIDGzRE+bLdx8sMyE/ST7cYkwHayxZY1nXVLoaD0+vRxuXda39QDYnlu76u39zZtjkJqXHkoUDd1S2tZ+11t0zHZ8kMK2H9BBKgkuJtmsx3FhHOR4DRNA60Vqnz5LEX3bwC297yLFKF1756YqC/Zs0zX5dSfldzqbd/hkjcMYRApE1hllntyilX5mk+c9sXPPvT30i7zufYzwgbAUxcC6gVYIkycA4gzEdurZB2x7dyirHCCFAKeWV0m1/tvdIt+7CcHV1YzIc3l1Oxuja6V8PIcA7T0xwUlrN60Q/fXu2MHgi73v6vqAtW7acXPR6b55f2DKzddsO9Hq9owfcJxitrWG0tobxcIjRcIjVlVUcOngQhw8dDGtrq+3G2urNk9HhW5Y+eNODPocXzZ5JWqlZ56yajEcwXQfJBULwMG0Dzhn6/V7oz/SbLC8m99/fO+YP0v/9H1/SJ6l/alSVy3VZvcF25uSmLtV4Y0SMERYXt2J+fhGMcXTGQOsUcwsLIc0K54y729j2Lzwr/zg9f3LD6bv2t/JgfwuX4nLO5XYiRj54EAFccCglJ1zyryCIz9398U+tbPa9RyeOGNiiJ8zu3XtdCPpm5/0nvffGGQuAwLggZzpejspF29kXEaMrcqWuuGvuyudf/zCCxomAdu916215U4D/H1IIJ5WC8wHj8QhN20AoBS44mqbG+soKVg8foqYqCcQWSfCf9PnCQ66yAcDF79s/yYX6PAvhPxHhriRNwYWYjrGyDsQYcca4s+akuhy/9sjqobdee9Xrtj9R9z2fn5xl/cFiXhTQWoPz6TgqpRSE4DDWoqoqlOMJynI60zzP85DneSuq6hFvS20tTRecv8M7Z5VWmF9YxMLCIvJ+D/1eD71eP0ukflpL+cl4gj8vP/qhXdp0k1cTYy8oBgOWFQWsdVhbW8XKkSMYD4domhpt26CqxqirCm3bYjIpw3g0XptMxtfftnL/wYeqVBzdC04UthA4fb8tihAcnKbVp21bgRhBZ1nNEj3+4MPsbbcZAkCf+j9+/IyuZe83xv7KeDh68frqyuxkPOHluERdldBKYX5hAYPZWeRFDzOzs5idnwtZ0bdC8NuNM3uMq/4kl9ffdM45n2tv/c1LlEZ2iQ9hV9d1/PvVyuPxKAw31sz62urNTV1/0ih58+64FRo9iWJgi55QCy/KSm/NJxHw3a5t4IwBZwIERs51ZLu2YIzt5Cp5k1DJz2cseeXX91y+LWxS76tjyU59yoiM+6wP4TAX084Sk8kE49EY1hxdDfIOTVlitL6OsiwpeC8Y0TNs3b7i6t979cMaD3bGaz86dCF8ikD/OcuyA2mWT1eZgoNWGkopMqYVw43109fWVt7YivYNN+5Zmnu873d5GSwIOaukKqRSR7d+pyteaZpjZnYWSZJN+185B+ICSZog7xchzdIW9MgHlN/7zNoFH252ztdSKswtbMPc4nYkSQaAyDkvrHFnaOCcO/btesJanCwvgz1z50nngPE31VVVjDY2MBmN0TYNrLEwXYu6qWGNBRCmYZoCkkQHrZUL3t09Ho+/bfL944d6L7ZlIp0NW6SU0CoBEMAYIU00GBEmoxGGG8MwGW40G+O1MR2jUw6uuuoC+aX/+rIX5L3kVxkT72kac/64nGTWOuKCQ2mFvCiQZtOefUJIFIM+ZhfmQ5pmBt7fhhD+O9PNn2j/nZvPufTWNgTQcGHmDKXUzyQqmRGCAyCE6eJ2sMYdbrrqC8Ny5eoLL/tw9VjvIYoeiRjYoicU0bJ3PrtZpcmnhJSt7Qy6poYxBsZYstYSAMU4PwnEXs4Zf9+2HTNvXf3qv33ak90D7FhDFy9bT/U/mBD+IRCDsxa26+CsQde26JoG3k4XP4Tg4JzD2pbappmbTMYv49w8Cw8v+IaTXvpba5LaP+NK/G6WphtSCBCxaTf9dBrgRqMNVdflOYzxNzsvL7n+D1/+uK6G7ty5RNXGxkI1GWdlOZkOHw8OXdchBKA/M4f5hUXoJEEAQUoFxgU6Y33btp1oKvtI33PX3+z3BHOHD6H0BATGAGKw1qKqazRNQ977heDEs47cuTjzRH2t37DzvZli4g1K6Wc562j9yApWDh1CW7VIdAqtUpiuQ1WWQACKrEBRFFBKQgpZcy5uMrW8dffuh17xmR3MqLxIF2ZmZzG3sIgkScAIyPOjoThLQ12XYbS+XpdrqxMcg73F9v3WruJpYdtlgqsPap2/njFxUtN1EsQozXPkWYGZmVksLiwiTRLUVYXh+jpG6+thtLFumqq+tamqP68m5Z/XC7fccv7umzoA+NYfvTxz3r/ee3dBQGBKawxmZ7Bl65awsGWxnpufu7aXF597Ef/Uoc1+BtGJJwa26Al39iXzkyxNP5YVvRtUkkw70x/9M+3/ZSjAcQAzQsoL8zR7B1PifWl/5sev3bP0hJ8dOpYZv7qm0uQzXMmxcw4PTEDwDt57cMGh0wRZVkAqDecc6qqWdd08LUC8/No9L3tYPe+IEH776t89Qk79vuTyjxlR5Z0FYwSpFECgruvgg9NgdH7dlD8zKnHx937jksetynfxpiPku3amaet0PBxiMh7De390XmYJax2U0lAPdJq3mIwnGK6t+bosu7Q/88i3p5YRINiKEuKA6SxGaxsYrW+grmp0rQEjBiFFQZw9Y9y5HXgCPjP3LIEfGd/9nLKqlxCCPtrIGG3THD2jp5FmOYSQ0190OgPOOdI0hdY6aK1W8yT9Jgvy8MP5UiPbpmcXt2yZXZjHzNwcer0ekiRFr9fHwuIi5ubmoBPtAJqMJ+6YOlAfALrpL9+6vTez+K7A8e+Ekj9eDHqzMk0YMYLSCowYrJ12eGGcw/uApmkwHg3DyqGD5vDBg7eMRqM/aSbtn33l0Le+d+GF1xkACMtgjRg8y3n75tFomK2vryHAY2FhISxu3eoGg9nb86L4lBb6RnoYwTiKHm8xsEVPOKJl3+nku5Byr86SJu33UBxtT6C0BogAEEkliIDU+3CO9f51xOjn52cHr7vv6794SjhBh8j/ydc/Z7xSfxdCuAHAtLUH4yBiSNIUvcEA/X4fgnOYtoFpDXlvSXA+OzOYfclMcdr5D/e9lpfhry0P38tU+C/Wmo87Y41pO3jnkSYZBoMZytIc8MiapvkRz3D5cCH5kRAen69Nb/u5JJJkwIkl1hi0bQshpm08jDUYj0Zo2wZSTM+12aPVks46T8J3M6ff+YhXgggIfdIj791tTVWhbSo4M23MKwSH1BLESHZde2bTjJ62fPnpj/uq79yrXjrT1t2bNzbWz1pdXaHxcAQEIMsL9Pp9FL0+il4PRa8HwQWqssRkMgYxCjPz83Zxy5Y75xeKb7XXfuoht+hCAAaCKy75wnR6hofWCkVRQEoJ13Xo2hbee0cMI+HDMdMMds+eJX7TX7x9Jxj/t0mSfSDPimcpqdIQAjlj4JxH13ZouxbGGjRti6os0dQNmroK1piWM/YdWPzxZFLuqe44dPuVV173wDb6ty988yDR+q3B+3OqqqbpVnyGvChCkiarQoovGY/9573m9yeb/SyiE1MMbNGTYvEvUHnX/pX34dtMCAidIJsZIJ+bBRMCIIALQcY6qquJ8NZukUnyEi71e5pR+e7bssFz9u27/ElprXAsWV6GH40P32Mm4782bWO9n26BciGQFT0MZufQ608XIctygqZtQADSNJf9wczTkrS4+OZPvvNhnWUDpoUiO+g7txEPv84Y++vgXfDeIU0yzM8tYmZ2jtI8Ja31IEnzFyuVv+22L/af/ni0+0hm1ynNe/NJlqdKKzjvYa0D5xKMcVhj4bwDF2LaSJgRpJRBaW2lZibMrD+qrbvxeFI1xtzqEZDlGYpBH1JOvyetdVRXNSuratF29fmn7cxmHs+v777lXaKnFl+Y6OSypq3lcDSEJ2B2fg5zCwvI+wPk/T7yfh9cCNR1idFoA+vrqxiNRwhEZVrk39Zp/87de/GQxQEf+hDoyMZGb7IxzKrJGAQgy3PoJEFTV1g5chgrhw9htLFhxuPJqB42j3ib+Ynw2d84W28v6xdb+P/AuHxrmqanDwYDqbSmjfV1bGysIPjpoldwHgFA13WYlBMMN9bC2tpqNxoPbwLhj3PF/+Lvj7R3Xry8/4F727e8S0ivX8SFejUxJrngmPYmzELwvgXCNwNjnzlvvrv3WD3TFz31xcAWPSloedk3Rt1uvfvLtuvauqrgECASDXa0ISxxjuA9qrKkEAJTUvassc/uquat4OEDT5eDl4fvnHg92747bJrxqPqSabv7ffj+z4qA4D3gPRDCdAj60T/eBwrkyVk3G3zYlQl53iN5P7p4vw3s8A2M+P8lpfyGVgpKaWRFgcFgFjODWZpfWKAsy+aCdy9vy/anb//02055rPeZL+ZMcD4rpGJCKrRNh7W1NVRVCSUTZHkOIoam6VCWFaqqgeksCOSkUMa71Uf5g3S7SZPs9pn+TJMXPSidgBiDNdPmqowxJGlaFEX/GdsH23Y8nl/b039k50KWpG9LsnS7UoqkUsiPTi4IAaiqEnU1QVOWGA83sLGxhnE5QlVX2FjfCEcOHTyytrb6jftHk1U8jCCxc+cSEbr5pql0VY5huxbwDuV4jI31dZjOgMDQNY0tx6OhkHbTV9g++Xsv6il+2hvg8R8FF5fm/cF8UvR4kmbQWk//PwiEwWAGg8EMwDjqukFT1Sgn4zAcbTSTcvSt8WT0h623e8d3bty1/E/CGgDqPfPkReP92zvTbWdE6A/6SJIEbdOEsqzuaZv2r9rOfoMu/PBxN/UheuqIgS160mz/DBrb1J81prs+0HR7xjkHUAAJDq5UYIIHH3wAiIg4tU2t6ro6NXi8kpj4wKTmbx597VfPPZGGyO/evdd5Xt5ExP4uz3NIKWHaDnVZHt3ymRZxMMah1fRI2WQ0ptHGujBd83TH8dIDn77sEc3DPOfSz7X93F0jhPy/iHB3CB5SaaS9HvJeH1onRES8bdqTykn56mHZ/tR3PvbaxxSmyyOlWFtbmZ1MxuiaDsZ2GA03sL6+DhAhy3I457C2uoKVlRWMhusYj8eo68bZzhhv+KMKbBfMrvuZhbl7i35/iBCmxRzegyuOoj/A1pNOwsmnnqa2n3TKWYN8/pwPfODsx+XcXrhhWWWzvZfrJH0pZ5xnWYE8L0A+oCknqMoxuqZGXZYYj4bwPiA5OlNWax3SJLG2ab83WV274TOXffhhnTU7c/12xhnbzhklpuswmYwwGY0wGm6gKksIzjHTnw1Fv2+VEqPOTzYtoASA7vzbf7P93O1Pv2Jmbv5X8qJ/UZZnhdKacLR3iRASSZIiy6bVoErr6epoALx3wdiutqa93jn7h8Z2H7/vEx+79+L/Nazhe79xiTJ1/arJuLy4KitOjEFJDWttMKYbtnX9dxujjS+d+mP/343NehZRBMTAFj2JaHnZI1+5PYA+JtK0ElrBWwvnPYhzkBQQWtc6TTdkolpiDMY4ctZya82c6doXGu+vYL3iPefuOv2FB6//pROmZxu3YkMr+aUsTYdSChhj0DYtrDFwzqHrWhjTgTEGYoSu62BMS4zzeZUku7pkyyNaZQOAba/4o5LC5HN1XX94PBmPq7KE7QysMaibGk1dkzGdMNac5a3bbX3y0nseYizWgzl465q+//575w4fOoimaSD4dKJDXU97jznv4JyH6bpp0AfBOou2rd2o3DCuY48qsH3opr2BMX6P927dHK3CJQCccyR5jnwwQ3mvx5I83cKlOv/F2895WIUcDyYE0L1HmlMDFz8jhJwLIUBrjTzJAOvQ1Q0E4+gVPSgp0XUtuBBY3LINCwtbMTs7h0F/ME7z7FvcyLuX8dDboQCQzJ5Jk3KypW1r7cO0RYgP07/KiE0nakiJXtEzs/MLoy16y6ZsiYYAWvnar57T7w1+cW5+8f1btu142uz8fEJM0Gg0wmi0ga5rwRhD0e+hPzMDIoLzDmmaYjAzCFmvV/f6vW8Wg94fJDr7BK677r5/3jft+208KIi3mbabnZ7fCwhAEFwalaY3cyE/Q+aO2x+qv10UPdFiYIueVL/z8T+qHdiniNM/ECOEEOCMgTHTIBAAy6S8B4zdZKw5DMAKKQkM1DZt7qw7F4QlHvDz3OqfuvuL7zwhhsg/Y2mvkVJcTUQ3cyFBjKHrWjRNA2sNgvfwzsF5Czo6zJ1LAQaSnMudDHrXtVc9slU2ADj1lX+6UY+qPy3H48+sr624I4cOYX1tFcYaCCmRaIVE6YRx/szgw5smUr/g0a5+tt1IlmXVq6oJrLWQUiLLcmilYUyHspzAW4dEZ5ifW8CWrdvQLwYA4HznbbNx76Nq8Lq8DN81G4fHw9H9VVWjbae7gCEEVFWJ9bVVHDlyCBtraz04//TtW/s7lh/jZ+e3vvhL2aQZvWG0PnxB27aMaBqyrXNHh90HCCGQ9/oo+j0QAdZ00Epjfn4B8wtb/Mz8/IH+zPz1922sDB/u+959cELGNHNdZyQXAlon4IxDSYksyyCFhPMOQOhcoMln7k+e9MC2b3mX+Nafv+n548nof/fBvU0pfUrWK0RW9GC9xcbaKtZWV9F2LYSU6PUGyPIc1jnUdQ0QQt7rV7MLC9ctLG776MJgy6e+XX/twA9qcvvF//PlmTVmiTFcyDk/+jUN4MSD4OIQJ/YFrvzfn3Pp59on+zlE0T/3lP9BFx1blpfheTB3CqL/Tghj6zqMJ0OU4yG6agLvbSo504HwnbqtP+5d+OssL0qlE/IAdaaRbVluN23zUs74eyGzd9z4rLc+83u/8dQeIk+EkCt3gIj9tZLSCyHgg0ddVhiPRnDOgR9t8skYg1QS3oOMMRS8XWBgu+a2bT370bzv2ilH7ml8e1XXtDdMxiNUZQXCdCC5ShKSSgEBRWfsj7ade5PaWj7tUQyKJ7V9oJSSc845WDfd4s2KAlmRw7uArjUIgaCTFEXRQ6/XR5bnEFJ7ktLhnEf/fO+9Y1KXVXlbVZWo6goBgJIa8B6T8QhrK6uYjCfSGnOO1P6cl3zk0VeLhrDMUmnOR8Bb2qbKre1AhKMTDCboTPdAtaMPHlqnmJ2dxczsAEVRYGZuLvRnZtus6N1iDP/upT//8MPEFi24kMk8F4wYEUKYFnYQMRS9AoOZWSitQ9t11fpaVe7ff+RJnXJwzZ6L0t7OxVc4juVqPHnNcGVtcTIactN26EyHpmnQmRbWWXTWoO1atG2DznTwwaNr27C+vlG2XfMPXCYfYZJ/5rnffdrB5eV/uQIZ9izx2W1zz+acv5EBidYaeZYhTXQQDJW39uttaz57+pEjR57MZxBFP0wMbNGT7uSLfr2RKfsfxHBt17UYDTcwmYzgESA5F1on2wUTO0LA7aDwezpJfkcn6f1CCHStocl4yMrJeOBt91zG+NulUu/rTi9/4lufefPsZt/bE+k76p5WS/m3QqnD3x+IHuDRti26rjva6FZBKgUpFYQQcN7Btp021pzfWnfRowm2F1+83/aT8XWc04eVkitcTHtbBR+ODg03RBzEGJv3PryMXHjda5//lkd0OD8AyHQhtU4GRARG06JT7z0oEIKb/jNJE2R5hkBAVVVwPkArHdIkcW37GLassiOWge7knAXGCD54BHgwIgTv4awjCmCCy21apuf39EXFo32rb//VLYNmOHlr8O5cgJGzDsZYCCEgpARnDN5btM10VuxoNEKSZFjcsgNFv48ACl1rRrapvxXa4b2P5L1V0dNaq3nOBbVti6qu0XXT7x/vA5JUB6FksNaWXdtW+/fvf9K2Aa+9ammgcdob0yT/D1onL2m6pre6tsqGw/WjZxnXUJUlGONIs3R6nnF9FatrK6jrCiH4YJ0tq3rytXK09vvtZPzZ0/565hAtL//A0HkTMBCcfoYCziVilCQaWZYGzrljTNzJBH32SLPxHdq9N/Zci44JMbBFTzoihD7HvQ7YQ0QjIeW0a72QRIzgvS8I4fyiKC4oejMdEX1EcP7BJEu/IZT01lhqqoqqcpx4Z05P0/TVvSL/eeHVT1/75288a9++XWKz7/GJsOvi/W4m098WQl4fQgBAEFJNz60RTeduKgmtc0gxXQBqm5aarmad7bY6Z3+827b+qKo5n7F7fxly8Yk0TT4upTLOuaNNjx3atoH3gdI05Wmanqxl8pqkFZd+4yM/NfOw3yAAqZA6TdLZPJ8evvfeo6lq2KPn1bwPQMD0e4UITdOgqSsQY0Er9dh+qJ4Oq5P0Zq2SljOOyXiMw4cPYW1tFXVVw/tpqwipZK83O/vMLactbns0rUzCVVdINOol1pjXmK5TRID1Dp0xkFIhz/Jp0HYebVNj5fBh3H/vvRgNR2ibBlU5CZPRyG+srd63vnbkm+eecfXokby/SJqEgD47uvs37YDIYIzBZDLCaGMdTVl6IcQk76cVnpwWFnTwK+/ZorflV3DOf4kL8Vyd5ykXkowxqOsa6+trOHLoIJq2QZKmGAxmkBcFiAuAGHSSBpkkYw9cY739PT9c//wfXv1/H/lhYW3f8i6xuj758XE5fmXd1pJLiSQvAC5gOrvhgt8nOdt/8e695ZNw/1H0sMTAFm2O05dbEZrPS6X/dnZ+IfRnZiGVBIiRdYZM284qqV40Mzfz0mxmJvWaPs6F+ndpnn8mGwwqqRIynaWu7YQQYjHN0hclifrZNNVXnsuf84IjN///HnbvseMFAeFQu77ade0+H1zng4e1HayzEIJDq2l4+35jWe/+51gnyUTCJJ7DEvkjj+aMGQHhua07JAj/rTPtdW3XgTgD5wLWOnRtC84Y0jRRSZKey5R8A8vUi695mEUIH/oQSCepTtNM9Xuz0Ek6Pd/o7HSqQ/AwpkNdVWiqGgAgpQAxCs65YB7jGsiuv9nvgzf3WdsdaZoak8kIa2srWFtfg7MWjLPpljNjynp3VlPZcz/3m5c8om3RANA3t3QnMcHfKaXYwRiDkApJkkJKCYDgnYe1Fs45tG2HyWSM4WgDa+trWF1ZOTr9wTUg+51xNf4unX/Tw67iDAHUBiqc9/PWWnA+XdEDAVJrCJVM25hw5rO8KAe93hM+KzME0MF/+JUzmJr5xaLfex8T/Lym65TzDjpJkaQJuBAw1qCuK4AArTWUTtHrz2B2bh5FUYQkzUZ5f3B1mue/x1rzxee+43MrP2gb9Chii2p7a8w7JpPx9kk1gQseYAic89Zad8N4PP6r0X3X34PYcy06hsTAFm0KIoSVO8r7GdEeLtQ6HV0l0nkOLiTZrmO2abcKzl6ecPWqDOnAsuRveZH8xyTPr8pnBwfTvAAA8sERE6LI+zM7Z+fn35j08n+dBnvZkauXdzyKs1THtH84UpqqHH3FO3/YuenIpq5rQXR0i/RoY12lFYSUIACMOOkkZalOdyRCvnjbuXrro3lv2r3Xrd+/euOkGv+e6boV7wIYY9BKg3MO70EhBFhrU+/CBUyoN+bOPyvswUNOQti5c4ngw6wQUkg9zZPTeaaEqqowmYxR1RU6Y2CsgbEWAQTnA7quQ1fXj+3BfhABjV/xAQd8CGA0DaNCCGR5jpmZWfR6fUIIbGN1ddvqwcPPnD+5e0Tbotd9+rI0GP86Y8yLOec8y4ujTXGnh+YZn1ZpplmK3tFGudZadLZD29Rw1oBzHqQU60rrbwp55BAeQaD40IdA1qCwxs37AIAxdF2HqqwABvQGM+jPzCJJ8xCASVmW9SN5/UcqLC+z+77+y8/iUv27JCsuLwaDk5VORVWWDxQVaJ2g3x+g1x8gSVIED3TfD+5tC+9dsM4OTdf+rfDi93sz7Msvff+X1x6sovOGPUtSFsWlQqqXgIj74FGWE0yG4+CsPcQY+8Jq3V134T+ZghBFx4Kn1A+z6Phy9qW/2Qly+1zXfbmtangznZGotQYB1NYVb6r6NO+61xDzl/aTw/3bxvfepHXya0Kr/0ea5dcrnfiqrGm0sU5EkGmen6SUfhkB7yXdvWPpue969sNd5Tke7N691zkTblNaXaeTFIlOkWe9aW82a6ezJs30PJtW05mbnTEwpoULoQCjC+D8cx7tOKkLr/x03ZTDzwbgY23TWGsctE6RpjlAQNd11HUteWsHBPrxzmL3lw9fehYeYgj94k1HqLFtDgKTUkJphd6gh8HMAEpJhKM/f4XgUImGlArOA2VVo2oqeAqPadICEcLcfL4ytzB/58LCAuYXF7Fl2w7MLW5FmmfIs2n/u65t0dZVj4jOH/CTTnq4r79nzxIXZvF53oe3t03bN9aBcQHn/dFtXUKe5+gN+phfWER/ZmY6M1Xw6XlEqaC0CpwLzwK7Swh9/XNm8IhGJO28EdTapnDOzSudQAo57XfXNbBtB9s2cM5iMhn5g/cdKFfvOvSEbYmGa6+Qt7907Ufh3X/wzr7eOLsIJnjW60OnGSbjMdZXV+G9h1IJkiR9YNXVGIemabB25HBYOXxoYzTc2D9ZHf++Hw3/+mkv/cO1B5tEsLwM1nTqvKwoLh/Mz/WL/gBSalhjQlWXVVvXX4fzX6xnP73+RNx3FD0WMbBFm4aAcP937zgMMn/GEA6yQIDzCM6DgQAEdG2t6rI8tynrpdFQvmyxnhT/6U9++YBX9k89Y78Czj5d1WV15NAhjIdDAsAQwsC07fOcNW/PE/n+xWzmJ6//2Nu2BDz28UnHgnROrBeDma/Mzi+0i1u2YjAYgBhD207bfDRNA2sMOGdgnMOaDmVVkTEtd86d0Vrz4hs+JRYe5duHa1blYWfMR5um+ba1DsQYAELwAdZY1HVLzjtGoG3OhUs5D5d96T+/esuDvegt28c0GY/mmqYRQgikSYo0zTC/sIhtO07GwuIi+oM+lNbgjEMIBak0GHFCYMyDc63Pfmxf3xk0eVbc2ev33dzcPBa2bEPRH8BZh/FkjNFwiLZrEYhpIdTZTcfOvuqKCx7W9vLccG2hHI/eYax5GhhnxKcD3tdWj+DI4UPougZ5UWBmdhZCK1hrwZXE/JatmFvcgjTLwKQAMV4T5zcyw2+li/c/opYbS0tL0Czpcc5TIoJ1HsQ4it4MpFSoyglGwyHW19bsysrq+Os3rD0hUw727bs8ufmQeUUzLJer8eQV4/X1waEDB9jq4cNwziHLC0ipYI1FCGH6/VuW8M5D62T6y4kxYTIer7Z1++Wucb83quy+U1/12xsP1SvtZae8Orfc7WaSP6foz1DeK8A5CwAcBbrD1OavUFU3747D3aNjUAxs0aY6f/fejqv0Gq3F/5BaeYQA2013IpRKSHCJrm3TyWTy7HpcvVHRtouueNUVyewZyxtzfey3LnwwBPxXztj9wXuEAHLOUVNVSVNVZ7jgLlOa/3y/13/zXR9799Nu2LP0uA/vfrKdfqQ04OEfBLHDSunpVtrR3mshALbr0HUtrHXwYfpHCI400RCCDYL3zxeeP/3R9q9bXt5vg2u+LRn/faXkBhHgg4fzDm3bom1bOOeJMRJCiNND8K/zYfIT+5Z3/dAtxNn7ayrLcW802uCT8fiBAd7GGBCjowFuusJSTko0TQ2EgDRJkWW5UJyr5EjzmD7PVm9RrpwMbynLsmmaBl1dwXbTPneTyRhN00CphPIsowDaVttu5+zTznzIs5L7PrIryfTcqzgXr5RCqF6/j95gBgEB5WiMuiphO4vprzBAU1Vo2gZKa/Rn5pCkCRhjEFwFmagVcH79iK0+4lYT163PsmLQX0zSlAc//e1ldn4ei9u2Q+sE1joADILLjnExGR5ee9x7sO3bs1Rsa9PX+UD/wXTtiybjUb62skZrKytYPXwIaysraJsGWicYzMwizwoQ4zCmgzHd9FyjtaFtu1Xv/ZcFl7/PWPd3P3nl3tFDzfjcs2eJZ7PzzwPC7rpqEmsNiAgeAW3XDsu6/JuyCX934ZWffsLP7kXRoxEDW7TptpyvVoMPf4rgDyAEcMEhdAKdZdB5QTLRMLYr6rp5gfX+zYnY/rzvffYDms5Ybu6ib99Iwf1nrfUHheT/CE4exMl7T9Z23Bgz31rzAgr2Xb7g78vnFn/ins+/a+54Xm2j3XudOTy6ZTIZ31JX5dHCgoAkSZBlGYSUCN7DOosQPIL34JwhSTMIIUUI/hzr/Y9+9eRXzDzaa9j13v1lliafFFJ81jobpqsh04pDnSTgUsH7AOesNqY7vzH1G5uee8Ge5Z0/MDAXcx1ppWeCd2I8HmI0GmE8HuPg/Qdw8MB96EwHxqdnrsrJGE1VIXgHwQW4YIoxpFxtfUxf09tn131Zld9r6qqqygkm4yGc6aCVgpIaSZKi1yswMztLs/Nzg7mZ2Wds26K24UG+l/YsgWsxeJ6S4meTJFmUSoIxwHYGk3KMsprAew8fgM60qOoKbdsCCEAIaKrpdAmtVWBEtq6qOyZm5YaPf/WPHvGhvQtetIPmFrYsLiws0tzcPObm5zGYmYNSCaz16BoLRiLkWb/Ns5nxYfb4Ns390h+8dj737GfaevIrnNhztE4T70GdMVBKQacJurrBeGMIRmxaUNAfgHMOzgQ4F1BKesZoxTvzxWDN7wv4q3/yyi+N8DC2bhfLcrFuq8vbpjmjnEwwGY3AGAWtdWdse+NwuPa52/jCPY/nPUfR4ykGtmjTES1bacy1tjGfhnOWcwEmOYgRuOSQOiGlFUkp55TWu4ixNw3S5On79u0SF1+83z57994Dqe32SCV/OQCfcN5VTAhwpch5S+PRKC+b+lwh1evyovfzIinedPNf/cw5Pyw8HA8EExtd1/59cM4TCM5MxzYppaG0PrpNGSCkgFTyaKII09qO4OeIcJHKZ895tEUZRAjt6HsH26b5SNt0t4YASCGRZhl6vR6yLINzjiaTMeq6KhjjL1RJ+sbtp5z59B/0nmnvZJqZXZjLspwF7+GMPToP0sNZgxAcgADGCDKZ9phjBIB7Ig/NQ9JPbj/zMc2XXVra65lkB/OiONyfGSBJExABUkrkRQYhp82KhZJI01TqRJ8zVyRnLS/v/GHvS8mLXnbScNz+3Lgsn9N2LTPWYjQaYeXIYdSTEoILaJ1CCAGi6UppwLQSMkszIHiQD1BagYhK15lvb6yP73yQCsgf6t7RkDOtd0idsqzXR5r30DUG62vrmIxLNE2LqmzhPdo0zUbbF7LHZVswAHTTH711e5/4z9nG/Bvb2qcJoVRe9NAbzGB+cRFbt5+EuYUt4Eqibmt0dtoIt+06TMoKxpgQQF4IeSRNks9rjY9ImGt+7F2fGuNhhLWrrrpA+oCL26q6tDOdtLZF01QgUEiT5JAU/H9kyeo/XHllHO4eHbtiYIuOCdeNr9uwwf05QrgdIcB1LWzXTifEI0AqTcWgz3SSbIVz/8rw7rUnuR2nhmUwAsJZu/cOOzu+muA+2LXtf4F39zPGEUDkQyDnnSSGLUKKHyPBrxBMvudZzz3/Rfs+vmtms+/90dhe3NIJQdcyIcZaHW19YKZboc47TPu0AVpNV904Z9MRVsGDGNda65392d4LX7Xzikfd/uTCK68zfhL+QSn1x71+v+n3B+j1ehBCwDsHHwDnHDnniIjNa528NEtmXvv657zjXxzW7y2cy5K8NyukIi4ktFZI0um2WH/Qn86JdBZ5L8eWLVugUz1dQZy2/Eg8FztWi8FjauVChCBkNkqy9PYs7yEtCjAieO/AGIPpOqxP22vQxuoamwxHO4DwjBfufMYP3Oq9Zs/SbNHPL7dde+n6xpqumxpKKRBn05mXeY65uTn0+32kWQqlNKx1qMoS1tppE13O0TQNNlY3QlmODxN338yLe1cfzf3JnLNqOFwcD0fU1A3qssba2ho2VtdhOwdjHFbXVsLa2nrnPU0KPveYA1sIoO/85btODSn/RTD2/2fvPcMtvcvz3vvf37LKblM00qggiSIJMCCDaUbjJgMGbMyMwTYYXCQDwTEucZKTE+/kJCfHSc7xOcE4Ro5NkW2cGTs2HQR4hAGJMkJCSEKgPtLUXVZ7678958O7peC4qaHG+l2XPsCl2bP2Wmtr3+t57ue+Lw1ETwohqrpp4JxDmvfQHyx0fkQuwKWANgYEYDKdYDQaYTqbYVYU1NTVmnfu49Lg3Wfv0l/Y89Yr79fRBRHY80995u5+b/ENiUlWev1OKKZpSkTURsRrmQhXFDtv3Xyo3++cOd9O5oJtzmOCPXuu9DLiq2D8z4mCddbCOwdvu4Jzzjm0NgwgEZ3bzTh7ZS/Z/sq7vu+S+yIqztrznmbxG7d8Xbry/wvR/Wsi+kqa9WLe64Nzzqy13DvbA2dPM8bslab/y9uwa9+1H3rjuXTD6uNr2nbRlUGBfVMqdUeaZcj7PSgp4b2DbTv/VwwRnAlok0EIhaZuUFY1C9GzEOOOYO2LlajOfijRJx8+dmA2yJYPDAcLn9UmheAC1rYoZlPEEJDnfSRJxqRUQnB9mpTyFZKbl9314Tf/jVaK5V6mGWODGMGUUl3+lhCIsWsBaOoadV1DKY3BoDPJW2vvNaYn3rmnzUr3lIcamryjnZR13dw+K6Zw1t0XfdE0zX1BwU1ZYTqdsPF4MiiK6hkrXG7/X0N07zi4urBt2+7XDfoLPyOVXuCCQxmNJMswGAywvLIdi8sryPIepFJd0X2MqKoS0+kE49EIk/EIxXSGopxSUUxtU1e3BdgbP3zNNc2D+d7uPHJMTEbj7bPZFOONdYw2N+BbC8EllNSIMWK0OcL62lpTTqdTnImHtBJdXQX/8vt//LzG1//cU3i9kHJXb9AXJktRVhU2NzcwGm3ixMkTWDt5HLZtu4nb9h1QRqOuajRNTc7Z6L07Gb37WF1N3tML9Ze27zlwvy9k77zyjSZNej+aGPUCBvAsSbG4sITEZMQ5O6yk/ng7jV+fHxrMeawzF2xzHjMsPiedKRX/DMCNggswMLi2RXAOgnF451BNp8y7Vmltnpxm2Wuk6F184/69S/d+DbbvQFh50e8ca+rmgBTiN3rDwV/2B8NSSoWuIaFm0XtldHJKluUXGZNcOlTmFzfK5kWja3954cGk1z8aMAYapHSUMX5zFyaaIklShBBhrUMIAYStlWKI8CHC+YDgAqx1sK1PYozPlCy+6Ede+AMPejK1uorIEnE7Ab9flsWx2WyGtmnhtla0xhgMBkMkJmVEpIL3T+HEXsOS/kuOHlq9r4y+mBbGtW1OFCG4QCRCVZYYb45QlZ1Pr64b1FUN5y0YZ5BaQ0oFIZWMITzV1fXLzZ3L5zwUf+JJwMbgvumds5wBaZqBCYG67jRSknRCEkRwbWOm0/GTp+Pp2QcO/M+16G2HfmNINN5HLr45zdLTt+/YzneduhsmSVHVFRgTSPMcDID3vsvK26oa44yBMQbbWsymE9RVCRCQpOks0clXJdWHH8w6FACLheiHEIfeecymU5RFCW0MFhYWkOYpGBgYZyDBG+ttAVz5oHtEaf9e8Zrn/NyFSuf/MjLa1zq7rW4bDsYgpATjDGVV4ug9h3HP4TswHm0iBr91OBPhnAfnnDjnUSl9Ikuzj6gef3cl7Jd3Xnz5/W4foFXwI3fPLphNRz9VFkW/qsquMSNGappq1tbtVRLqylf9xucfUETKnDmPBnPBNucxA2OrMVta/IaQ6v1JktTSaAgpoaSEEAKubTAdj1EWM4Ao5VI+Qxnz2uVdu190/BO/lt/3dQA6+wcvm+zaGT6byfR/l0r+TprlR9M8R4wRbdOyGCOnSH3n3PlNU/1EW1e/7Fuxb+3qt5x96F33L6rh0Wa9PlG3VfPVuqltCB4hRlRlibZpIaWC0Qk4F7BNi+l0CmstuJDgXDEAXHB1ChPm++So95QHezEKAOe+7B1tUa1fOdrc+GBRFCFE6hL0CfA+QHAFzhW892jqNq2b5tlNU/2EnWx+F9FeQQCrJpOsKmf9e1P+m6pBUZSomxpdxisHEdA0DcqyBJcSi0srWFjZzvJ+zsD5ivf+RwILb/rce3702fv/nweXvbd201rkQt6ZDXqT3sIClDFbF7cO2hgMh0MMFxeRZDnjnHPb1LvKSfGM9Kbtg/2r5+m/vvzVp2zccc/PTyaTf+pc9eTEJHLnzlOx69TTYIyBsxbeO1RlgdHGBuq6gpASYIDzFmCA4AKMcQgmkaYZhsPF2O8PjutMXXu3umn8YL4vIsBEGkghc60VGOOgSOCCwyQptNbIexmdcsqp8bTdZ1Q7t2+vVlcfXAbboXddor4mey8SQvwrk5hXGJMsRsF56xxmxQxNW90XEtxuZb8JKdG0DY7dcxhH7r4LtrUYDhZpOFhcGwyHH1tcXHnPTi2vufgNVzygqqiPmBcNq9ns9aONjQvKomBKKqRJQowz7324ta6rj2/M7rwT80aDOY8DnpCdi3Mex/xeWbtX8o8wci+PIbxEbtUtRd9dBBIRirJiKk1J9nq5Mvy7A/BamfHxLbe87YvnnvuO9t4vxc59R0v7936j2XXWf2EmuVUac4mrq2fXRSmccywEj6atdQzYmWb5S7gUpzUz91S9/JSP3v3xJ33ltIsPjP6xXKdHk3u+VIeV8+sbuBQVImnrLNq2E2uCS0ilwQDUTYViVoAIEEJDKQWPiNa6hDH+TCn6F938ojfcBrzvQXmjAOCcoti8pvR/RFq/kLF4wdYrgC0rHRiA1joWJYHXYmht82I+YUfr/56uP3UVt4a6MR4xISIIrTvVHQOACCKC4AL9YbdyBQDOGFRiEAOBEJiQQnIpzwTCaxOtzrngKYPP3XHwn1xbTcf3xDLMaLBg7ZH1OBqNcOZpTyO+I2PlScYnmGBy+DAAINml+XBh0fSG/UGS9RxXCrPpMUzGIzDqulqTJAOXXeAtl4JJqQbO++foleSO5cEzcufbC2eT6UuVVKf2+wOZmhxJlgGCwzsPJQS8D5iMNlEWBYTq2hyICE1dwfmAGCKkENCJhtE98s66EOkbtg03v+xVtz64bLR/s8rw1FuGIYRccgljNBzrqsumcQLBObZt3w6pkyikKuLEN3gQIuaOd78xOdkrf0Do9J9qkzxfSJERGFM2R5L1AAZIzrsgZM7BBINzHkIJVHWF0cYGqmpGw8XtpI052RsOP8a4e+8ay6595qv/8AGJtYOrF8ls18oLbfCv9tFroSWGCwvIej1wzkcx0JXrG0e/8JKfufJBrZjnzHmkmQu2OY8p2OpqpJ9Zvf2ew5PLfeufkWbZoklScCGQ9HqAECi7NREDACHFMFr2EgJNVko1O3jwoq/t+ZZAUbbvQCDg2OYXVvdzhVsZpb8Ioh+hSDkYoFXCSEbOGPVt05xfVvV2RjiXp9s/Xtzwr/7qllsmt3+rCHws8RlcGX+K/cwdkfFjhLhA1AWLyq3uRR88lFQIgQDqooh9iAACrHUshsC1MjtVmvyAdnTNwYMXfWbPAwxj/dbn+YZ37r2uVnS5s+43nXeZMQYmSUCRYK0Da1swxpkUgnvntrfBvdQrceyr5+19fz8kPQ/Xi9R1hMYQIaSEMRmSxCDJUvQGC+CcoSxnKIoCrK7BGAOIEIkYQDKEcKrU+VLe7z07uHCstXQsCr7BqnJcC9aqpcVoUyLdRka6kGG8gSgsIIGeGLDF3jAxae/pjLHFuixRzKZwtoXgCnVVQSkFqRTauoZSCoPFpSTG8N1Jku5ITLJcNXzXeDzpI1rut9bSPgYEa2HbFt571FWNsijQtg24F6BI3ZQNgElTSCHhrYWzDoILimCTGONX14/YI3iwk6DzbmScJ0sAes66rUtUDSIgeA/Ty7G0vAxwFqrZtIAfP2ARc+hDl2SzYH80Fck/0Wn6XdKoBN3RD7iUGC4tIskycM7grIVtLaL3mIw3MZ2OAWJIs4x8CORte7IqZx9bGGTvZSld88yL//MDLWFn6TlLOxnjb+QQp0jOoKQC44zatrFJlt00WFj4RLnzmmMP9udvzpxHmrlgm/PY46zVdvyXr70ienqp9+7H41ZchUwSZL0eVJKCMwKPxBiBS8J2ztkPUUg2Ti/OLFZXcdu3+nwYQPie1ektH33b1Tu2Lx9nJr3VufaNMYZTsyxD8IHFQGAsKMZoJ5f8JVKYMzjkebvq5Y9MD/7ql/p7/u+NfyyY85FmdRXxDR9Q60zimwB7mlYaiUnhQ8BsOkUkglYaIQRIpbu+SilQFyWatu2aEAzXRidPB6eLt63tvgXA4Qf7eM5/y4Hy0B/u/UBE/CHn7PfHGCCV6iZMYHDada+jUkwpIYXlZzVN9WNN49od25Omly7mkWirXsuhPxhCyK7PUysFLrquVO88iskUkSKE0tBJAm0MdAbGveVMyLypmrSu613eRWdM4hC5Z9xGRkTWWYQIFLMCk8kYja2QqwxcSAYuWXA+m84myWg0wnQ8BucCWhs4Z1HMZl3rgODITc600TwG2sUYtimdKGWMCJEYhYAkScAFx2S0iclojKauEYJD2zRomhaMAWbrPZ2lGZI0g0kSeO8x2lhHVRbU2Doak9yd9rKvruTp7MG+NteMFvni2WYxtL43K6aIEcjSBMYYEDEoo8A4gw8uVE0xC/L2ByTYNg/9xrCs/N7Wtm/xPpzvvFV1WzMCobUOtmmQ9XpY4qyrmUozpHkOCmGrDmsCDoF+b0BS6I22aa5o29l7T87o0DNfcP89a/fy7ndfZGaz6SukNN+vlBYAQ+sswiSQc349AH81UOm1F75o3hc65/HDXLDNeczBADp04pbjYnj65W3Nn8cIp3ltYbyHVLrztGkNxjnIe8YBgRBPY0SvWlhaHv3ci9/+p6v47aP4XwTWuS97R7t//95bXnbemb9T1eFWCvHNSqhnO2tl09ZIk4z1Bn2OSD0O9hTXNtso0rkN3BUbH/mFTx29h31z16WXPaZS0BN+YlbLU7+OEF/JuGBKKcRI8CF0V42BQDEiy3voDRZAFFAX3bfAmGCMMzDOl7ng36e0ueHgO/f+jz1vvf8XeH/jdWOgg+9euyvF0nu11s+wzm5zziHnAmmagjOOGAi2baG1Qt7LDBf09KLwumraxiTZIkNXuq6TDNokiDGiqUo0TQNqajAAwd8bs+HR2BmkNhgsLEJpBdu2bDYZ4+g9R0XdlDwzqZJSb10hMDAw1E2DEKrOP8Yl+v0htNHwPmC0uQkWCWUxYdPpGK1zyNLO4xVjRIyxy4IT3VFxCJE1TSVsa0Xe7zOlDJIkAQMgGMd4tInxaBO2bbtQYyFRBQ/GgCTNMFxcwtLSMoToolBs06C1nZjTSQIw1ugk/brT4pbnXPIuj0sve3Dvk8URi/GUhRCjCZ7QVVM5xDp2RxQgeNfAOhvbpikq+Ps9Vf7K/jdtW9uY/bTW+hdijOfMZhM1nmyCS4nBwhKIIuq6K2u31qLfG2Bhaan7AKE4hsMFUAiIMRCD2OTAJ2uN9+a+PPTMi//iAYu11VXwc6J5SuXtG5nCsNfvA5yjaUqyrbWtba5jBf/U9X5t3hc653HFXLDNeUxy4aXXuK+/9/TP8Z76oFTyEu+sDN5DSQ2TphCcddMW6xCdYzE4xYU4Wwj1ml4uRtMv/osPDJ73H/6WJ2vfvgMBwIlbPvq2/5Ek/A6i8Obg/ctt0/bSLEfWGzDftmjbWrVts52AFwTvdwsjzm93+Y/f+pevversV4WjjB14TEQAnHLKKe6eMr0VMVautbkLEQRAaQ2yDmmWQSndTTWMAShisDCEqBSC8/AusqaupFTyXAj2o4un9G8/ePCiLzzY1eieN13ZHrr81Z9Suv/pNIZ9AOfeOWRZjizvoSoKjEYjOG+ZUD3KsjxnjJ0PBrKtS7iU0Nog7fUBIVCMxygm0/vM+UmaQkiBNEsRIqFsKtRVBW8tjDIQEGgbi+lkAmctY5GDswoAg20dhNQg51FVNThn2LZtB7RRqMoSVVGiLMqt7LWuKkpKCc5Fd1kIBu892qYBFwLBB8QYUdUlc87B+4AkybrGBw7YpsVkOkZRdFO5LMu3Lky7TxJZL0eaZgghoK7r7iAheAQfkBiD4fIiySwbCUHXbTQnjjPGHvSE96Z7IHaHzW0InsfQ+UHHoxHKstyqgVroGhi88877GdUr96dJgX3uD37wlLoe/QJj7mes0burspKbozU0tsbSjp3oDxcA1nkOy6KEcxatbVFMp3BNA623JsBSEWd6ExQ/jcjfq5dGX7pgz5UPWKwBwPef+/Ihd+r1Wa6fkeY9lqQpTJpiQEOalbOj0/Hkk5PJ4a/ve9OVj4mf4Tlz7i9zwTbnMctTUzk+Ohj8iZb6oqaszqvrujNkSwEKBgBDcB6ubRC8A2M80QnO16n8CaHTzZM3vPNT2y946981LaJzX/aOKd2wevVdx06eZIx9wyTmZ4xJzpDGwNsWdV0xAIiRMiHE2ZrrbZ7cuS7wC45dufSJ2w79xtfOvvC3Jo/2c4TbR1GdsXQHY2zCuMjL6QQRQN7rw3iPrNdHmmVbESmdZ0hwAaMV2hi7jsZZAyFkL+tlz+v3s1f3w/knia685UEeXBCqlfXBLvknTOD5VVWdUdc1dGKQJyn0ViBuU1eoy5r1+jl6vUFCDOBCMiUVpBCwTYPGtmirCiEEWOcQ2gbEgMFgACkVVOsghYZtW2ysrcO2rhNQRYUYqBP3OoOQCq7tDjI0gCzrIU0TaK3R6/URooe1Laqq7poHtIKSGkmSdNcSRGBgiBTRNDWss0jSDILzrUqwFP3BAIxxcMbBOOCsB0WCEgpaaTACnHMAAVJIYKv/NXgH5ywYF8h6PRAY2rqCVpKM0VFqtU6xPorxGKur4A8y0gPGtbIu2hUhRHe8A3T5dk2zFS3CuhJ0zpxSqriFT//BVSER2Nf+/CfPchTfHIm/Vhp9SghBzKZTxEDYvvM0bDtlF5Q2ABHU0jYkSQpwhcgIm5vrCNZCa4MQPQkmJv188NdaivdJ+C88bc/9C8X9X1ldvUiWG9MXKJO9ZmFh0SRZhta2gGDUGy5WC2lySPj4mT/76p9PH94fxDlzvv3MBducxyxs34GwccvbviZn6ftjjP/CWptR7NY5EAJMSjApEVsgBGJgnmJd5WC4kAnxkyqemNzy0bddde7L/u6jAXbBqt2/f+83nz1M3pki+WaapW/lDN9NBNmtEwPjXCBJEml0suisfXZs7WnWhfMXA12x/plf/swsju84a897Hr0rs70HIn3hl2+PUZ/ggu9inENLhTTLYdumy2CzDiBCXXeRHzFS1zEKAhHBOcuIaoDFbULyH1ZaHf/mX/3iHwO/97fWyveHCy+9zB376Nu+ELLkE03T/GzdVJJPOWKIXaZYohFDQIgBTCrWGy5CKoFAhLZuMJ1NUJddATpnHIeodBcAAIAASURBVIJ1GWV1UyHOItI0Qy9PYNIEyZZ5va4qpFkGoxMI0dU8Ka2RZTmSNEXNBMqqgvcOJjHo9/sIIaAsZ6ibLpSXKELKrsqLcw6TaHDBt8RXAIsBaZpDpSkGwwUIzlAVM3DGkGVZFxnTtt1Vq+SQQiFJDbJejhAjGBjatoWzFs634FJheWUFg+EihOmek6osEGOA94RiNuNxMlkO3v1wjL3dP/rsnzv+U5/OTmitN4qynsTWznqx18odR9rTng/7D019W7WmRJUtp0mGLM26y+ugsbi4BM4EiCJiCDA6adJeUjx99uQAXPl3fi1aXeV3X7F5Xjbwb2WM/SgR205c8Mlks+t8lQJZ1gNnHLPJGFIK9PvD7vu3LZqmy1Ysu6MOokizQb//eZ733gcWP3/uy/74wXr12A+dtW1H00zfJLU5Le8NGAGYjTZpMptET7i71+t/Ei679cEK3zlzHk3mgm3OY5qlc5aL4iv+z5VU35ek+Z4YPBjv1lGCcwitYHwCwTicd2xLaA1B8cXk/EgnbHroXZdcf+Hf0xG4tSI9efMHfvaDS0bdZa29tG7qV9ZNM0AE0kyCb3nD8ryXRB9Pi8EvOufOhsQzsjj45PpVb//S8j33HGf7Hvk1KWOgOw5iLLm9Q3D1LKkUuBDgnAG05f9CCQJDDB4hdDEZUklIISGlQCwc2taypqml1uosJeVreGTl597/2r940ev+9OiDeVw70+XRnc3mXzhrfxgRpzvrUKPqek05QIjggkNq08V4IMLWDcajTczGI7RNDesc1FaeHGMMSnU+srZu0Mt7SNMUTduiqkr42qJtuv8vy1OYqe5aCdoaOtHoDXI4b1EUBZy3UIpjVkxQzGYgInAhMBgMYLSBEBwhBHgfYYSAVAIgDqVz5P0B0jyHVBK2ruFtg7ZtUZYlCHTfJSgBSIxBfzDEcHkFznlMR5to6hrWWlRNBSEEhouLEELAW4u1E8exubEGioThYIH1h3241u4sy/JHwHGR8q6M3pa1a2e+tiMpxAZfxHpmd61/82PlxnX/402biyvD0dLKtolvXXH4jtuqO2+5vd6slTtjZdvQhnAKFwJad/mGUusukDoE1GUJzjX1B73amHy2cey6v1Oo0/794q4dX3kGAn6VB/FSzvlC3ba8bguURQHGO6dgMZ2gaWoE76HTBNF3LRF1WYILgYX+ArIkpbqpSyJ8KdfZ+7Ii/PVp+/7gfhW5/10cfPdFRkq8cmFx5ft6/b7oDwYoyhLKaHAhiujdF5tZ8dnz9/3XB7VqnTPn0WYu2OY8pmFsNd5xcPWOfla/N83TpxOxFQoBbVNDBAepE+gsg0oSSGcRfGBKSx4jrXjvfjA1cvTk7+rNaBW3sn/gU/VTX/WHM7ph9Uv3TGYniun01mI6/Vlj0tMzloOIQABTJkGSOR6C79umeSpjfKdOkvOF1leunXL6J2/75CVfO/sHL3vE16SmqKzrZ7eyGEAxdNERMcBbCyCirhtY20JrA6UUwDiyLEWW9WDbFjF6cMGhtIRUygguzmMUfnqgNPvsu37kg58+9uF7HshEgmi/uO2qLy2hCakQYpZmKVEkFoKH5hpCCHDG4EJAWZYIziN637UZVCWcdYiRtsJyCa1toLVBnuUIkRBiRN3USNMMeZahbTvRVNdl14AgBQb9AUbjTYxG64gxYNuOncj7Oeq6RFXO4F2Luq4A1uXDpKnBYDiEFLw7dKgb1E0NICBJuz7WfDBANlwAYwyzzU0UkzGcc/Bb0yLvHZTspnIhBFCMSLIMKeegGLZWn12bAAggIpSzojtm8AHj0SamkzGIGKSQyHoZIkVlrVuMCAvOWpoFT0VVRAbm8yy32hgrhbQhohaKzyiyqfdhGjibMp1upIPe5lNPGxaDhaUzCfRkENuaIpqudqtqUGyuo6oqJFkK70LNeDNbO2/b3xJNBw9eJK+fXPF8Vem3K4E9WulhCJ7NZhNUW9NLvjXdjCEihK7aq20atE0D8gFlMetW0XlGC73FdojF64j4H+W1P7jzpe8YPdhL7NVV8CTdcZ4xyRsHvcEw6+VgnAMMGAyG0fR7hznip+64ZXzXUx9j195z5txf5oJtzmOes/astmuH/uUVUrAPE9gbmsJx6yzgLHjbIu8PoI0GlIQEwADmm1qEEE/ljL2ibuPm9CU/9SdEf3zkH/JlsQtWLa3itsM7fui/OnK3JGl+KefsexjnGoKDgaH7bctARIpiXPHOPlcIcbpK1DOX8u2f3vzar32muXN6665XPHLXpKecAnfEqhsRyXIhtPMWfiujLElTWG9hiwZSCgCyKzMXDKaXQKcaXDDYtgUARohUN1UmmHyG5Fz1er3lV5z5kx9+yTvdN/6h69FD73pFVoNWTL9/1g1/+clnGC3P44QLtDG7OWeoy27lyDkDZxxJmoI5h7ao0FAJit3kjyhCCAnOOdD5qhBjhN7KdPM+oG1bzKYTgAFZ3scpWWfcb5saxWy2tYpMkLYpmrbBrJggSROYJEWvlyMED86B/qAHznknNoJD29ZQvRxGaVjXoLUlvG+QpF0OnNYaoIiqrLC+dgLlrOj8bkpBa92l9vtmKyojoq5r+JMnMStm9607e70e+sMVJEmCSARjDJqyQvQBvawHwTlmxQxt22I6mTHaypjrjh+6ZgDXeb80YyzrKrsqElJQDDGORhvRORuyfs9zRm2S99o0zTxjLK+belGw7iI0EiFGQlPV8M7f66ejpq7qUNriptsO/I2fk4/+l5cafnfvhaWc/rq08oWJMT0hJONKIctzeIqwM4dAAXmWYdAfQCcGkQhFMUNTdUcVdV2hrGYkpHDLK9tu7OeD9+civeKdX9Ibqy978ELq+y/8yWHmzc/0+71naJPyuqm7AxHbEmN8BqKreaq+sOdNj6J9Yc6ch8hcsM15PEArM702XozvoxCeb4N/SogR1rZd3ANjGKhFOOtArLuQZFIxHrxy1j2pbeofZzIbHbvmdX8BvH/9H/qLuincFScPvvOiDwou7gDh5330r/beL3QdiJ2Q4F0kBgvOmzqUu5M0W1ZCPhmteo4YZJ+85aM/84W2qO6+YN+BB5dM/wA4cPsoPn9nfpeQeiPJ8lNCCLBVC6V1d3DAgBBsZ5yPDiES2raBbRokSYrewqC7eOzM/aypKvLepwCdT5wtBO6enGTxyi/90auv6zXJyYabqpZjZmfVwCi5XaWDMxhnTxZNc0FdlGd4a3eENBumaZoIxlTjA6uqElobMAAxBmitodMM5axEMZsh+G5jzXnn/QIjxBi7oFopIXVX38QCoLWCkF2TFmOEXtYDlxLlbIa6arrSdgBKKuRZDh8cqqpEkmgMh320zoKxbgUqlMJ0OsXGxhpmxRicEdI0Q4wBjAGtbVCUBaTWaJoaShsE7+GdA0BQWqHX66PfH8AYg/FoDOdaaJ0gMxrOOkxG486zFzzyrIcszSG4hPcenHPY1sKHgDTPYVKz9R7rQnSJaKthQUNrhTRLkPd7SNOc6SRB9B4hRpaoBMxwsbmxgbZtyHqLEHy/l+UkhCRrLbNNyxEJMx9QN9XW3y+Qpz0sLi2SkoqE5DXqOPvWWqqD735jsn27vNi58MuRwvNAMfHes6apoYyBMho55YjorkEXFhbQ6w8hlUKMEVJqTPkEtsv+I+esd87fNh1PDjjUHznjy08+sbq6+qA9ZQcPXiSTk/R90PHVMUZTljOMJ2N47ylJEq+Uub2azT55eJbe8+3+WZwz59vJXLDNeVzA9qz6zdt+4ys0Fn8qtfz1QDFzdcCsLKCSBGmvB+ss2qZClveR5BmE4kBVGx/C+WBsX2izzc997meveNGL/vAfNTXveeuVxaF3XXKIyWotMHF7w6qf1SZ5ElHoVj+cwxiD1gYWvOPeuR4onkOEHVyJ87Kk91zJ1V8dvuKffHn3D5448e2MAdm790D85ocvOZEN4vE0zU/JsgygCLAtcWQ0lhaX4JyDtRbRdtOkYjJC8BYmySC0hpIplA/gjLNiMqZAwfjgzqibZhlE36VZcntt3FFCKAQJk2a9lRDCLophB5dqQXDe8wGKBRKcoasxZwxNXaEop1hUy1BSw8eA1lpIzcA564SitWAMkFJDCAkiwPkAKRW00WCcUFVd0sRgOETay2G9R1WXqJoGDPy+KQ4DQwzdNC9NE1jXCVVnWwCEuq5gTAJtFHRi4HwCMWawwXdrUAZIKTFcXEKzFbcx3tiA1ApJkt5X0K6V6bpuTdJ5AYkQQ0RRFDDGIO/30NQtwmQCFwhSangfMZsUW69NRNuWmM2m8N5tCVnTXaomCfq9XlcEby2SpLtazawFgWCSDC4EVEWBEDzSrAeTJrDOYzqZsOlkCmMSDBYWGeMC3jkoY9BUFaqNDYxHm6jrrrmBLQG9fo683yetZG31pMTW2pAOribH0ublzvpfBehZRJQ0dYWyKDCdTgHOQOjqxBa3bUev10OWphBSgSLB2e6iVmoDLgRF72Jb10fqqvgLVNO//OCX1488e/VPHnzJPIHd8umnnd7o6ufqpjmlrComhECIHmBAiGEKa69qrT30stf9t8dkY8mcOfeXuWCb87hh8UnpbPNa7Ncpf6HQ5gecd1ClQoidiIreI7QWQTswzsGlYYZAQoiUiD2bOL1ud8UmV+3f+/kX7Dvwj+ZMXXjpZY5Wcfutz917WQxLtzrbvNlZ9wLrrOqM6l0cKxhjYAw+eOmsX2CcPSOEsJvAnsU1++zRT2+/8vYPvO76s175/nXG8LALN8ZAX3xvsxbhjighn5WkKQRjsM7Cu65snAHI8hxKa/jJBHXdwIeA1nvkzsMkCYTSQIhwtoF1jnWTRCYiuaGSpielONMTtTE4B3CpjdEKpJqmla6y3PvAOOfQxkAqBakkYiQopTAcDDFYWEDW63cm/apGXU8B6p6+ezPOItkuEJmo6z6VCsqYzg/FAG0UesM+hFIoNsuuQaBp4JyH3+qb7eV9GKPhA4cvLbxt4UPn7+OMw7oWaZZCKg6jDZqmRdw6cOg23oTeoI8k66Gua0xHm1022lb4b1mWoNgNoIrZDDY4SKEQQwQTElxIWOuAsoKzDowL9AYZ0qzLaKvrpjsKAcN0Ousy5gRHURQAK5AmSTd5sy0SkyDPMmS9HrRWqIoSdV1hNp2gaVtIpZD3h9BpBh+654AxDiEVsryH4bZtYELC1TWiteBgCN4DjGE6GyP4CC5F1yKBGBpvi5ObtgGAkzes9kaOvUp5/cuNq57e2NaAIqy1oK2SWGe7uBQmJdJ8ACkkgg+wdTf9ruoKIUT0egPkeY8oxDXJxceYdH9G4dY7VldvelB5f/dxzSVpL+v/BAt40XQ2EVVdY3F5GcuLO4kr5Zu6uq0sZ5+6W4QHdTwzZ85jiblgm/O4gbHVSLf8l9sKN/4DH+n8NMtPGS54KKXg2gZtVSN6Dw4C0E07vPeMSwFhkiF596LQuskgTafvetdzrr300n+8lqZbkR5Yu2H/3o/0+v3DzoVfEJz/GBgteO/AmOgCfIODUppxSYwo6hjidu/9Alp7JgO+uyb+uev+7DV//ZWPljfeWnxsc9++h1e4hbSuojd3OlsTYmSdwZ1DSom6ruCcA7GugogrhVA1XZG6kLDOoW1qRAIYAdZahBCglYZODMtjD1wIKYQUrrUphUjgYAwMTAhG1KBtajDOoU0KLruMMSElWAjIej0sLC7DmAQ+RFjnAca7YvgYobQGGENV1WjaBt4HGGOgtYFOEiR5D0xwmF4P2miYzGCysYHJRpe95p3r1uERINaVwwshwDi2vHAEa1t4Z2GMAWMMdVmhre+BMQmMSaCkANcKEYDSEnmvB7N15CCVglIaWZZ1WWogRIogigghICKCMQEQ6/xzWYamrjEejzv/nTboDwZYWFqGbR1Gfg3edxMgIcV9RwBcsC2B7bvMuKZFmqTI+30kWYoYApxzmM1mmE7GiAB27joNaZ6jrhusr53ExvoJpGmGxaUl5P0BIhFC28LaFggRJs2QZBmWtm9HvXWxmpgUaZYgOO/LophZt8G+8Cev23Hy9rt/PMnSX+SMP2VjfU2PNjfQ2m4tvLJ9JxaXVuBsg9FWAO94bR22quCdRdt0IllpjTTvoSoLqqtyxoDPCvD9WRK+fta+mx6SXWD//r3ixiPN8/oL9vVKy55QAjoopEmCNM+Q9npT7/Kr1Vhfs2/PO77t1oQ5c77dzAXbnMcV7Nxfame3/J+fdqPpXyDGSwaDBcmFQF3M0FRVt5oKAb5pEWOEdw5SK6al5D6E5dY2308ubDxHnzml/dfcwu6ncLpg34GCDl1y6Kbb7VrST76plPxZIeS5gks0TY26qiH6AmmeAwBrZctCMTG2bXaBsWWKOFdy9Txulz//XfnPfO6eT/VuPPVz7xyxhykPagAEBf7Ntm5tGUoTY0De6yNJM2htUFUVRpubyAY9SKnQHw6RDYfQ2sBWJdqmgfcOznk455BtNSQwxiCEhHceIUTGhQAnMO8Dqrrq/GVSQvb6kEpCcA4f/H1+L84YOOdgjMM6i6qqURUlIkVo3X19zvh9JviyKuG9R5pmSNIUUiswIWHyFAkyME7wtkVVlnDWgjMBKRREosEY70TUlsdMGYXFpRVk/T4mmxto2wZpkkJKSWVZwDobhZRIGIuccQgpSWiJNM0RQozjzU3MplPUZbm1mmx427asOz4BwBgbLgx5muVw1vK6abpVuTYgEOqm2hJ1Dm3boK5LBN+93CGG7igkySCEgtryp7VtDde0WxNKhSTLYbIMFCMmkwk2NzdRFDMUZQEuOjHu1wKm4zHKYgofHJIkhbUWk/EIYWOjWzcD6Pd6SLMeGAMEUzB5DkTaEoItnG0DYkRmht+jpHrOdDZ95dr6ibM4k9I7C2s74ce2VsJSKYACTJKgrhuUsymauoT3Fs55JGmGhcUlJFlGk/G4mW6OrhOE/b3e8CtP++k/fqjmfyaOHz5ltLj9F3SanpNmfTZcXOlqvTijsigDF+L2JDV/Nem3J759/0WaM+eRYy7Y5jzu6F177eZ45873gtiFSZ4/l4GhoS6eAQRE79GWJXzwEEpDKHXvr1gBwi6u5I/kK73R7cnr/xi4/G7czzN/dmG3Ij3yorf+PufsFqXNW4SUL45EqrVTWGuR9vqdyFASYGCz6UTYtkkj0W4hxDbJ5dM4Ey8E4uePf++bP3/8E/T1HZONjYea4XY+EL5mm9tCW1nGmDFJsmXW764ru+DZBjQrkeU5siyDFhLRWTAimCSBQYq6qREpIk1z9HuDrSBUCSFDlzEGgBPAtkrYASDLs/u6Nuu6QlPXEKKbGuW9ATgBTdt0URl1jboqwYWEUp2R3oeA4Lp+ULXlBZNSotfvQRmNupzBtRWEYGjaBm1dwbtOmNRVixA8kiSHUgree0gpSGrjs0G/VCapvWsqwXjT1lUtpCwZYdP7WDHON41MAgebhhBKqSQlJiOtjbdNM56VhW9mBWtdA+8jD84OnG2TJO9DCQltTJrm2bJJUu69H3JgELwbONsMGtskaZYlXIos+pgVs0k2m00VZxyCSzAGEBjSPMMwScClAkOAUBKs14fY8kiCAZOt/LbpZIK6rgHGIJVGCB4nTxyDcw4xdAJ9+/IykiRFjARbdZPVpmmgpIJWCs552KaBEAImTcGI0DrbTVhDlGmaPnVxafFJIHZ+YHH7bDYTbTOFVgZLK9u69gd0/r2yKtDUVbciZQTOGfJeDs77cN5DSIlAjtbWjobJaHxrUzV/Vpb2c29a/fCDLrG/lzvefZE5nm1/teD8h02SqiTLwBPTRcNMxyiL6QTBfT4u9q698LmXzQve5zwhmAu2OY872L4D4eihS26A679bCH52DFgWrKvcYZyBgYMi0DYtuPcwebaV7g+WJKnkjJ3FwF4tpBzd8tdv+/Nzv/cda/f7715FBN65cfwTr/+4lyt3Cxl/Vim1V0q5ZFsL23SDA4oRXAgopeGdY4DnnPNManU6F3y7i/E8LvSLZWa+eDJJr1r/9C/eeOet8eTfF/D7j/FvbjpArzp379HIMdJS9pM0h8l7UGmCCMLCUoDSBaxzsK1FjNRlY4HAwSC5hE4M+jqBMQb9fIC014NoWkS2VZpeFKi3RICUEpR0WWlSSoC6y9OiKOCchdEGtm3RHwrkaYpyNsVkPMZ02nnO0jQDAIQYMR1PYG3bTf76A4TgIQSHkF0ERVPX4OgaLqbTMeqmwqDXJ6NTX8zK2lpbp0k6UkoeNcYc51KeSPJ8zeTZhhRyhOjGWospgc0MpCUw63s66pm1iBKMxWAZgm1byNQhAaOa1542xmQAbEJgVHrGy1I4tHxBKgDADpFyxSERAgtNK20gCRelpVrZ1g6Gg+WBStNFb9vF0Ua11NTVCmdsR5L1dvby3qkmSbYNVlZM2hsktq3VZLM7YFbGgBEhhIDpZIRiVqBtGsQYkaQp0ixHrz9AXRYYT0ZomhrJVoNBlvW6tTTvIlGapr7P/D8ZT0AhdGJKCMjZFJxxSKVAFGCtS5LEPCPv9aRQKoPgvG26ztU0SbGyfSeGS4sIPnRHB7MJyrpE2zbgjGFxeRkLi8vw3mEyGaMsCmxu1FTVsxPeNh+TZD+e33jTGh5iDhrRKj9xfXjmzknxRmfrBSEkbNugbhuE4Mi1rWvq+tambg5eNxnNp2tznjDMBducxyWnXHhZvfmFf/Zh17rvoRB+migKxng32UlSiESjairMZlMIrZEDsFUNhABtEuNb97QY4r5hajY3vvC2jy9/zzseULfgzosvLw+965Jrz35G9n9BmFu1VpfWdX1OOZt1l4pCdIn3MUIIAecsi8EjTVMupcy887tDjNslo/OlkS+OPDnU3zW++tr3v/Y6JcLRBxoH8pu/CbrriuGkquwR5+zpIRCEMuBSQacMAyZgkhTT6RTFbIqmbpCkCZSQ3VTC1lBtgzzvQ+sE0nQNBKlSIMaASJBiq3TdB9i2W5ERASE4BB+2VmWdfy3GgKqqYGYTLC6tIE1zmLQAm0zv89YxBjjrUNcVWttiMBhiOBjAbUV8eNciWkLwnsB5BFHLuVpXMjnMmLqHgR8Bwx0U/WHv4ppkNCOIMs9R6uB8Er13WofTV/r+yiuviRf95pXhQfajPlDY/v17+WlwYrhzh7hrbSIT24iZnSkX00zlTc5Vv59k2Y6sl59qtDizLd3p3ranRu9Oq+tyJ/louk8enSjWSqNpG9RlCcE5sryHRGtIJdG0FlIKMMYxm86QZAG9fg5jMsQQEYNHVXWHEl3GHUPTVCAQlhaXsbi0BG8tRqNNQcQGAFj0gdm2AYihP1jA0vIyFpa3AZyhnE4xK6awzkJIhVxr5HkPK9u2QUqFjfU1FMUUVVkQGEpj0i8yxA9v5tfddemBh+7dLG9Q2zjRJYzj/La1rK7X4b2H69ahcNZNnXdfkMB1+37mw3Pv2pwnDHPBNudxCQOI6uz4Mbn532JkzwTj38W7TFswrSDTFMoYxGKCpq6gtEbb1KBALM0zijGkPvhnE4uv8y1N7t6/969334/L0W/lwksvc0S468iX3vpuRriTK/FPmrZ+vvdeq8QghgAwBqVUV7LtLKz1TCoNZQynENK2rnYprVe4kE+VUr0oUnHdpKivvupdP3RIwxy+8NIP3a8AXsZA93zKTKVmd9Z1/fy6KtFWVZf/5T34VrWT1hpSKjAG9PI+pFSg2RQ+dB4y2zZQUcG2DYTo6otCCPBbkxqpumkaUUCM3fdHRAADlJJIkgSCd92fIEJZlDA6QZrlyNIMg8EQvZgjSdIuCNd5CCERQtX5o8QAuTEUQiQGmlCk40S4KwbcHEE3c87v1lwfg2Pr0cjGet6ul/fYa0rpf/PkhyJWQX9vWv7qI/b2pK3Ks79LnLDVVbDfXNrLrlyr1Y5zcuWrcUK1NRTssnV+Z101p8Xongqi8wTnZ+V5b7vSermqS4xGG2jaBkPnkfd6WF7eBpkYOOtQzKaIRNCme43vjUvpSufpvkw7H0JXiM4AnWjkvR6KWcGc8xiPR51FjzHMptPu+IQbhBBRFjNMZhNsrJ0AGGGwuISFpRWYtDP5a2NQbVV/MQ5SSnpi7DYpxYcyOb1+76V4yKtJumM1ObZe/Gh0/hXFdKbLskBrLby1UEqSUiqESHcw4DPH0tuPP2Kv+Jw5jwBzwTbncQvbs+qPf+L115JZ+n2p5L8HJQshBgTnQM4hTVL0+wOIrWtDKSWC81BSMU4EVNSPLj7fuWZSJ3pyw/7zvnLBA7xc6yY279w4/onXf4y4uYeIfsF59+pYxwViDGmeQ0kF7x1mzqGuS2ijkec9ZusaVVuLQDHJ8v5OqfQygT05eP9Ck6bXp3n/8zf+2Ru+lGzUtz/p0gPTf6y2h9euVom4yyQJnLUYra935nUhYJIERIS6qtC2LZTSABhMkkIlKfrBw7UtfNOAg8FZi+inYKILdq2qGRgApTtx0NZ15zUTXRQG57yrt9o6PFC6+3sBfl8MhHMWSskua011niqtDAYLA/jYEkOsKPp1IZNbOKOvRYSbwXAzs/5Iw/WM3LTecYqz1xY9v2/fn/4vYujOR1CPPSRodRW0igNAJ+gaADMA2L9/77HeuLq5ELXqhZBGr/vQYgcie6oP/jwX3PlKy6cpqbZ553PvfDdpyzNMRiMorbsgW21gnUNRzFDNCgghsbi83F1TO9+J6CRFr9fDcLjYtRwEj9a1qMqiiz1Jc0SKUImG8w73HL0bIXh47yEkv2/9meY5pFZQqQFi9wPRHw6RJIbGk9HmZLR50NvpZ7907JqH7Fvbv3+vuPnGu54dhbykbepl5xy0SaFMCtuUYGBgjM+0Yl+OwX5l30O8Qp0z57HGXLDNeVyz4+LLq6Of+ZUPCYHnccN+0jon27IAYtwSErLz6UgFmSQIrvuQLzhnFCKstcug+H0A3zS950337z//G/sexAHAzosvLw8duuSatKJ1LthtMfifI8aexMFgkgQIg65TsW0RQ+jCV7f8dt575pxlOjE6y7IlZ+thmiZnmDS50NXNTcVQfvGbH37jF+5J8pv+ocvSzXrkh9m2I3mWt7MQTTkroLREkqagGOG8R1WUsFtdnUVRwKQZskG/i6dwAXFrXUlA98vbtrDWoW0aCCmgpIV1DowApRRCiBBMdFlq1MVocHAo1ZW2K6XhvYd3FrQVhVEUFQhEvjeIeZbXaWIOD/v96523XyPyX5EKh1nAuueiLk7K5sJL3/sdYRr/lqlcC6AAsHZw9aLDlvW/JlJKQ2DLWZrv6uX5BT7ScxhnFxBnZ1VVsTAabbDgA8C7i1trW0zHY9Rl2UWkUHfcAcYwXFyA1gaDwQC94fC+nxWtNYAehosLyPIenHdgjDArZhiPuyvbfn+IheXt6PW71XW1sQ5iBJ0k0EqBxQCdKDKJDmD0jejsJ79y8s4H1EX798B2hXbH5pTeDIoX1E3NpdLQaVcZpqUEQBEs3h2C++ujopxP1+Y84ZgLtjmPaxhAFAfHTrLxZUywp3PPnmW96/xVQCc0vENbpmBp0v0ZzsAEB1GXMcq42BmdexnTZvTdu3dcvrqKww/mF8yFF94XtPv7kfduMyp5c5qYF0gpFdIMaZpja3sIMAadpADjaKou7V7rlCVpyjhbYiZJh0mS9SrGTmPWPksJ/X2N84e+dv5PXPXp/7p2/caJ42v7Vv/mBOH8bWvxHtp1TCkqtDbGeQ9spf43oYXzHoxzGKPhnEdRlOByE9Y7UIhoqxIUIvhWkG1RlCiLGaRS3XpU6u4vIgIXEkLK+3ooGWNwzgNE0FqDA/DedUXvgkFzjayXk5KS6roYl1V9mDN2jRTyy5zHbxrNbvca0xPrRfXsbx5wD1fcyeOdPatXenTirVhdxcZ5OO92fsr51/AEf665PM279pzZZPrc8Xj83CRJzh4ouSK1YT54uK2fgy7xP3Zr8MEQCysrSNOsOzxQAoxkV1HW70MIgcXlJWhj0LRdNM6976MkybB9504sLC4hxIDpdAPFrETdlOCcI896UJojSRMM+4uTxKgvOsW/9ku/dOtDbhg49K5XpGXLX0fUvAyca844KEaUs1kXUyIE5VlWJfngmkT7a77rBX88bzWY84RjLtjmPO7ZWo1eh2z59wj4d1ypbSpNt7oSGbxzKKcTBO9gjIFMDMA60caiZJxbyQU/Uwj5Y8NEb77lZb/+56ur/+lBfUL/1qDdhVOHh3VifoFJ+Rpqbd9vBZ82bYvUeSS9HEIpUIiYFScxnUzR7/WRJhlL0hzaaMEZ63ntMh/jTte0T69r+xJn41dOPf2Mz924/9nXTdAeua+14aKLor6qPcoYpjmx5UhApC7UVQoBxgQYF+BcIMSItq0xm0xRzmYwWkIKCYqEsgqIsVufNk2DntToDYaQQqBuGsSmBRA7X573yJWElN20jajr14xb/rWimEFpRYPBQpFnvcOcya+Mx6Ory1lxbVWVh4E4W+ib9tk/9T/m66t/hO5DxE22+weTgwcvOgG7+4am8p+qnd8xyPkzdKKfA8L3ENFTjDELwXeeyf6g30V7GAMfAjY21hGjx9LCEvr9QdfwAEKIAa61W4cOClxKVHWJ/mAIkxqsrGxH2stR1iVkI6C1QIxd6K/SomtwiBSDsycscE19pL7fF9h/H3RwVd4W11+iy/LnrLMLIQY0dY0QAkLoOl8BkA/hKAl89rTd5xx5tF+rOXO+HcwF25wnBDsuvrxa/+tf/lCU4llKqp+XxkghJRAjnO2yutq6huAcYisQVuc9CO8QKbAQgg7OPWUy2tzHgNFtn/yNj5z9g781ebCP596g3VYtrLVEdzR1/bOz2eTMqqzAGEeS1VBJAs4YTJ4jb/uoq5MoimKrWkgCIMaFRJal3NZtWpVVYtt2OQb/FIr8hT7G61TNrv7i7/3INdQb3nHgwI3lRWefv6aIJl2vk0DculKVSsFai6apIZMUSmtU5Qzj9XU0VQVEDd1TIM7gnOsqi4SEMSmk6tLjkywHVxUYAGc9mqbqKsGCh5Ldf0psawHGwTmRkMI1rTvZzpprpVZXDxYGV/eS5A4mk1E6Q7Vn9ZMPrZboO5w9e670ADyAcnUVaz/We/1tTMor2qo9NVj3rDTJXsS5+G6l9Tkr27YnIXhMJ2NMjh/DdDqCkhqMCAwMs9kU09kU9xbac8mheAKGLqPvlFN3waQpdJogRI8YPCJFCCkwXFxCbzhEnmWQglH0PlZVdfLY0bW7X/ZLH3tIQnx1FfywmjyFO/lPB8OFc7gUvGkarK+dQGxbZFmGJDEkpKy5lNcL0RzC7rc3wK882i/PnDkPO3PBNucJAQOIjh85eWzXaX/AOC5oyvJFMQYoqSClQF2ViLGbCsVISJIEKsugYBC977xaTZNEqp6ZJulrs6V8RHe//Uq2+7cf0OXo33hMF17miFZvH18/e1fdVreD4a1Sy+eGGER0DtG5+7onBwtLADFUVdmZ9GOEVF34bE8tMmkMtNFMK2OUTla8dwtFMXsSGHsel+Imw/2XntVf/opykTEeY/ABUnZryxgCIlFXVK41uhpM1iX6D/5ntVMI8b52gyioC3Fl3WSGqLt46PX7MErBNg3KSqIsCljrUFYlwEAhElEIMy7lrcaknwsUP9c2xQ1UuRMn7r6tPH/vAfc9L3xEojW+o1hdRVzF5SWAcv/+vRvbgFsW0+VP+BDPybL8OZzxPWvra886euSuU5xzXCkNIQSqqkaIJ1HOpnDeIU3Tzocou/YFLti9ogiBCFVVoKm7yWnbdCG+QjkwxZAkBv3+AhgxeM7Dwsr0Ia+1X3ne25bLSf0WxujFSZooneTQaYpAEZPJCIQIBkYmSU4yKT7bj7ffycDm7685T0jmgm3OEwa270C4+6q33+hb9zttU50eI53Oe31ooyCkhHMObV3BNQ0YEYTWEEbDZCnapmHeO5BHP8bw/NC048N3YHrD/r1ffqCZaH/jMbHVCODkzR945Qe5zA7n0rxFcPZyxlkf1GVkORvAwTAcLiBJMjR11WVuTQtIbSG1QW84RH+wgOgjUyZhdTVTtm0XhZB9rtQZjInnaCFuY6DCtnZnWczgQgDjbGvqxZB3tVmo6hKNYOj3hxguLMI7h2Iyhm3dffVOMRKAzoiutUYMEXVRQBsDzjmStIvl0Magrkqq6zqEGNc4F9cJwT+VMfEFy9ltO0WY/sGxK9qHwXQ+536ydbxQACgOHbrkZD4K1wVLHxhvbDyzqss9S0sr37fzlNOeJIRMyqLE5sYGvLfQWsOkKVwIcN6ibwaQUsCHgKKYYDIZoa6qztfmuraEJMlQ1xU2j5eADyDnwIXkztOuJBk87aN/9NKbXvbTH3tAGYf3cnD/3h7D5CebGq8hUNa6Fq23SPMcJs3QiwGuqYlAbaRwg47+qpVPf7B8tJ//OXO+XcwF25wnFLtf8Nv11//HWz9l+vx9SslfkVJmQmkY03VMOu/BxFaWmHOA4BBKIc0yeNcySy1AWAzOXgTORtRPZ7QfN97fztG/j6e+6oOzG/bv/aLs909KsFu1SX6WiHb54FCXJXwIyLIcaa8HkyYI6+soZiUIDDEAXGokfQlEQJsEpdFsOp2CIkmlVF9KmQF8lwvetY1Lm6ZB0zSw1qGqCnAhgG3bYUyC4B28I1jTItd99AdDcBCCD2iatqucYuiiOKxFv9/fioRwmE5rOB8gpcBwOKTh0pLTxhybjSefb5vqU9bhi8ZPj47t9vLCS3//O+K687HMhRde5gBMAEz3/z/PP4rIr15YWTywY+cpL4mEH4whPqupq55SZivg2aFtW/hgYZIEWd4JsqKYoSpLhBC6FbnKkWU5hguLaNsWVV0j+ogjdx9mBMa1yXYD7JUG5sj7/tMPff4Nv37FAxJSH/0vLzWJly+r0f4iA60AgG88qqqAmI47sWhSLCwskRB8k5i9ypf21vmxypwnMnPBNucJx1Ovf+foyPf/yuVKi/O1Nj8qlGIsRjQV78JftYDgHMF7UEPd+seYrridwDjnXGuzkxheqq0Zf654eQF85A48xEqdC/YdsESrt6xdefKdzKi7hJBvYd49A4CwbQvGOEySAgAidVVMXIj7wmm5EF1sQ/CQUiHv9RB8YIwJCCFEDCF1rU2JIoxJIIVCxStUZQnbtmia5r6LTwLBW4diOoFz3TVnmqRomhYNavjg4a2Dtw6CCwwGAwghMJ1OsDneIClkQzEeXt62/aBJ9SerwlzXy+zJP7vpQDWfpj0moX2/cnUNoD506DnrCHRjos2HlEmev7C8/HKl1HfHELZ730W4TCdTMAYMhwtb5fEMSyvb78szZETQSqM36IM4R101OHbkMKaTCbQ2kNzkQojnD/rLxbaVM+PB/ed8Yc++3y3uzwN917ueoxKlL7K+/TUpxTlccs45B/kA7z1mGxM477EwHJKW0prB8GsR8TOn/8DvPOSstzlzHsvMBducJxxsFZF+pLhjwrb/DhfibIr0DMa72qoQI7z3iEQI1iJUDlIZ6DTpQkCFAsXIdJrIEMIZZVG8Cpw2Dr77u/df9MYvn3io1UaMrUYCTowO/cYBLsRdTPC3ZL34QzHGjAu+Zfj3XVYb5yBEeNfCNQ2EkvDOommbrSooDs67rxtCZM55cO67laXJEHUEGFAWBWIbEEK8TwjGELsy9yrAtjW0kshWcizlPVR1g6qYYuo8gndo2wZ1o5BmCQkpmxjinXVo/qquqo+W0/Kr2XK6+fQf/8PmEap9mvMQ2Zq6bdD+veNJb+ft2cLw00LK7+ZMvIILcVFbVztOnDiK8WgTVVFAao0sy7G0bQd6C0NwzhGdQ7Sui6khBsEFGAkIYcCYZmAcadpfWVxa/gGpjeRsmh76k1dcfbv80GjfPzCtPnjwIjmcnP6S8eboXzWtfeZyb5vsD/qIoXsfllUJ5xy886jrlibj8Wbrw1+tN/brp7H5B4U5T2zmgm3OExJ24WWOblj90mZr3xlD+HcxxG3o8poQiUAMXXRFXUNY12WFZV2dFYUILhUDg+KMP1kJ9RrFtm9+7SM/+RHgT0YP+bEBhAt/a3LHwdXPLgzDSS7kYW3M60II27xzoBihlYLTCra1W9VaBkp1peNpmiGEAOu62iEGDqU4YoxobX1fnycXXaCtkBLCKwB0X01VjAExRjDGkKYphOCIIUIohb5S4AzdMUEZ0LYNKS1tmqV3pmn2V0mSf2TUll+tw8b697ziz5tH+7We8+BgnddtSkSzE9f/+pGEZVczZZ6nlfzRYjZ5yWhjbcdkPMJgYRELSyvIej3orfdgiITW1ShmU4AIIRCC88iSvLsShmRGJ1xytc3V7Q+01WQY1HDXMwe/+pmr9se7XrDvbx/z3PLRt5nBoH/RJM7+xXQy+W7yQWd5jsVt2xGDQzGdwoeAvNeHlJyMSR0YvlHY41d95qsffFA+uTlzHk+wR/sBzJnz7YIAVn7hX24ft8W/sNb9IlE0fCvJP+0PAIqoZl3lUtbrQ2oF11qEEKGTBKBA080NbG5szkDhc0nS/z07DX/1zDdc/rAZm+ngqjwmN3e1dftjbWMv5UI8lYGxEDyc86irGkpJ5P0BlJTgXECbBEJ2F5qz6QSMMSRJihginOtqoBhjSLMMQkhMZ5Oujkrr7nKUS0itADAwCrjvBJQBXAhIpcEZR1MXNNoc+6ou70my9K9Wllf+Qiu67tbDN2/sedOVLR7iinjOYwsisBPX/6eMucnOI0ePPPf4sSOvAmcvWdm+Y+f2nbuQJCmIAhC7JoXxaARnW0ip0DYtirKAMQnyfAiKBO9DZxVtLTnblkKJW7ft2HbV9pXtn209/+r68btPbKzV9ZlnAmpp+0qPL1wMzn6+LmffdfTI3UlZFNi561Ss7NoFgKEqZp1AjBFKSoqR1uqqfNfG9Pb/sucnr1x/tJ+/OXO+3cwnbHOesDCA6Hl6beNg+P2IeK6Q4qV51mNSKTDOERlH1usBBGijwYSEtx7BWzRlCSk400pTmqQ9ivF5SaImJpHjG/bv/eJDuRz9G49xz6onwt03Hfi5yy01d0kpf0lp/QLGhVGKIZquwLuuCjipIISCkKqr5DGdUdw7B84ZGBdo2wDvugoiIS3SVCBLM2RZDp1mALopo05TEGMoRyMU0zGM1lBSoa5rxLKgLM2jTpKTvX7vcz7EP2dcXMVNvbZ7z3vmE7UnKN1K+9dLItx+3U2vPy5SfGFxuO0FS8sr+2zbfu9kNFoARQjGUFYFRhubMIlBf7CAtm3gnYOSBloptI3F2smTKGYzppWCSUzPsOR85+0uB3+h9/br4PbrO0/PTvKkN6AaF05o8gIt5W4Aut8fdpPg4NGUBZwLcM4iSVJoLck558tZeeu0ip/9zDcxfrSfuzlzHgnmgm3OExrGVuMN+/fe0t+16/9Nk3S3SbOnUwhoqwrWOUjW9V4SutWhzrqGhLosEISE1ooNhgOAsMA4e0nbtptIzfTgQdy4Zw8eluDX7hflH2x+4n0/9MlTk1PWTJpdIrV5VXDNMFJApAgC4LyH9wFSSIQYEENECAF1U3cl71LCu27VCQbUdQUfPECA0hoJ59BpAi4kGOewTY2qLNDWTReMm6YUQqC6aWats9dypQ4MdO/T5dLJuy98xYdqzCdq3xF078fLSwLuvP4Tv3ZScLpmOp7uaarqtUqK75ZKpM5bWNfCeQsfHACBrJeh3+tDa42yrLtjF9vCaM2EEJCSKQph22wyWiiL4sl1XVzUV6oWQqQhuKXJeJw724pEp+CcdY0ZMWA2GaOpu88JymiURYC17dRa/4XJmvva6uqVD+mCe86cxwtzwTbnCc8F+w7Ykze85epcrbwDQv3b4MNO1raIdY3Gh87XJQSEUt1KUCuIussjAzEYkzKpJY8R2yHExZ750enuJyva/ye3P9S4j2/l4jdcUd6wf++X9fKOdS75Xd7ijSGE06RSLMsyBB9R1w2qukQsZgDnXZk9AU3dWYK8D123J7rL0rau0bQtpBAI3sNkGYixzgsRA7ztKhc55ySUbE2W3uJj/CCj8EFn481nfGm5OHX1Q3Mz93cgDCBc/J9LWsU3Tzz3p46yyL+YptnLZCr3MUlPq5tGTiebKDenyHtDbDtlB1aWtgPEMZvN0Ov3kOUpjDFgjMBALEbLppNKV1WlGeODshgThcC1USx6xxgAIXhXobXVhSoZkOY5YgxompLKsgiM89sZ2JX3fPX2Dcw/SMz5DmHuYZvzHcPs1v+0nZH/lRjiP4kh5G1RoilmIAKSJIVJEzDG4K2Fdx6C8864DwBSQChNMQbXOvsNxvl7rOX/ffcL/v1RPMy/MPbv3yv27Dx1+2ZZ/lg5nb5dG3POysp2RAJGmxuwTdvVAgmxFXRLaKoaTVND6wRaGwAEk2gQEYqixL3NB8QIMUSYxKCX5wghUFO3Mc2ztYWFhYNc8T9GaL+0M3xtxLrqozlzAAC0f6+4eWn7gkrEM+qq/ImNzY1XzGbTXc61SLMcu049HQvDJbSNRTEtEAIgOAdjBGtrNFUJJghcMKRJCiEkZsUMHBy9fACAoJRCrz+ASVPYtsVktAkmOHrDRWitUJUFlVUxEQx/FGP8j0+/+N13P9rPy5w5jxTzCduc7xh6Z5fr69/w7+YtO5sL8WohJedSgaP7VO9bi7ap4Z2DNgYm6UFICW8t2qoChGVcSRVjOIcTftyIsHH9Z9/8wWe8+L8+5MvRb2XfvgOBVnHi6Hl7Px0IPxJiPNvHyEySIM0ygAjGaGidgDGOECM24wbKqkQIHt6z7jpUZJBSIhLBNi2892jqGjEECMZAeUaM8TpSvKGp2vcXafHxJ9OuO9me/zz3qc35W2xdlW7ccMPez7d1chsxfG5pZfn12pgXxUhZoIDN8RpsYyFl98EheIfEGPT7KdbJYTJeR5JnyHo5OGOwrkVwEa1tANa9b33wMAzQWQpZG0ynYwSKWFhaJMZZVMoc4SJ+XoebH3Kx/Jw5jyfmgm3OdwyMrUY6dMnt63z4OzHoM4NzF3LGYLSBMgZtXaEoZ7DWYlEsgnHW/bNl7G+LYmtPFBOlzNNNkrx2RZjJ2gf+2ae3veo/Ppyhneyas1+9I3r8BGfimYwxtE0NzgWSNAPQhZYakwCMA4yDSQWhNKrZFHVVgXEGpRRMkoBt+fNijFvPAxGBIkI8Di4+KThdztvyK0+++twpW12drz/n/INccMEBS8DdV+6/6C8H/XOuz/q9H2NSvJaAp7Z1DSEV0iRHU9WYlVNI1kfeW8Li0iJCbBFiQN1USEyCXp4jhi5ihwsJk6aQWsN7DwKDNAZcKmxurmE2XkNkrI2BbhS5+tqLXnll+2g/F3PmPJLMBdvDCNEqn94zWBhkLlubrvu1Y95mp4yqO+880190ESKwSvNw0UcXduFl7uih1S/DFr/tvf0PEuJ0qRWEkuAtB6irZOJSgAmBTtpsrR8BOOcY54yIhZyInquFntEyJne8+41Xn/Wmh35BSQR268cueRIQ39xY+1ol1Q5lNAMBdVVC667Lk2JE29SwrQeEQJr3sG3HDmwyhrau4b1DVW4dVggJLrqEXSEEgfMGYDf4GN/LGD5+Yr2452W/9LEW+MtH++WZ8ziBAYR9Vxa0f9uNx3YPj0oMviS0ekNw/ocphAVGHMEHABFNW6GpDdIsx86dp2E82YR3DiIfoJf1AMaA2PkKhJQQQtzXzBEQoRMDPgNmsykCxRnFcIMU8Rjm3rU532HMBdvDBBHY+jewM1mit1TenNdUaiK135B29/pTzhAbG3eJDSF/a3TyxtE4BkwqxktqQ8OX0/bMM89zjO2bXzo9Quy6cLW6++Nv/7iT7RlM6t+IREMwQBiNLO9BKgVlEgglQSEixu6lUVpBKoUYI/PBgbft0KT5i3WqNtXTt00OHrzo+j0PwfdFq6v8yFXjZ5ge+2UW6UdMRotaac6VhG1alMUM3loILsCNRgwB0+mkmwgur6A/GCBLU/T6fThnwVgnQLngMEYRiIgxjAj4FAPeHWazL37grr+czquk5jxYttak63fcsXpwKcpbIw+HbO1/Jjh7gWsb5poWrm6gpEKSpugPFsGYQNs2yPMBhJSgEND6GrPpFGAMaZohMQm0kmjbBpoLLC9vx3CwSM7bsfPu9k9cv1492t/7nDmPNHPB9jDBGOjYMe0E2CIBPyiVjNIry7loOWMNMczg/YRzNVGcj3NpNnnO10C0Xh85vFkc+U9jz/yo9X4MXxdqMzQNUndKAo/z4Rmbr6oeTk67+LdHt3/qkj8OIZzVWvsGxrkhMKgkARF1n/q5ABcSwnsI78G2QndjJBTFlFlreds2K1yJHxQm3zx9/Oxi/94rb9t34IFfjhKt6vIm9eKs6b/dWvti1zZ97zwTQkLxLnqEM4aqqcAZh1ISQkgwDnjvYW0LIkKS5xjGAO9c12YQIpSWpHXqpLC3N239p4HowKQ8cvvz3nTl3Ks252HhrLNWG1pdvW3ypuYPj49G19uy/vm2rl5qbd0HMTjvECMgpEGWDyGEgW0dpuMxGAhVXWBjbQ0EwvLSNmzfeQrSPAfjHE1TI+33ySRpDDFu1K45dp5n7tH+nufMeaSZC7aHkZ077ajayN7DIk5Pk+xibUyfC0FERBQoeucjuRg4o8AUayFYTSFU0dkqEM2EEqNcpZuQycgu+c3E+/WJZut0B9buvubXxkSsXEryMj9lqbp185b22muP+717D8T5mvWBwxiI9o+O3Lmw7XcpxtPruvpBCsSDt7C2BSoOmSQQSkEoBZNliDFCcA5iDBACdVmwqpiJ4N1paZq9ItFxfOarX/t+HPjTe/AA1jUnD76lN7rWvUIm6m0yMd9lXZsUxYzVdYMsy7YeLwNFgm1bUCQkSQKTCGil4ZQDhQC3FU8ilYKSEtZaeGYJ4CUX7JAx6r8J33zyK77d2PemeXbVnIcXtroasYrRLR996V9PSnU4en6dMenPEnBODBFV1YDzCoxxtK3DaPMkvLcYDAfQOkWvPwDFAGMSKKWhTAIC0LYNgvcIkXykeJwcO7F374H5B9g533HMYz0eZoj262r9yAUI8a3g7NURWIghIPgAV9bwRQkpBKX9IUSiiLyPzjoKiFElxiuTOEbkvLWts7aMFAvXulkIbsq42Eh72UmdZOsRfH188tjmbDQbLS+tjAcry5PNu6tS57admA13zjnvcGxehvyPcstH32bynr7Ihva3vLPPpBDhvYM2KdJeDgJDovVWYXqAkBJcChABrmlQFQVi8GTSrGHA1ybF7LJidOIDF/7kh+5PVQ77/Pt+bJvOzE8Nev1fWFhaOdukqS6nU6wdPw7btsh6OXp5DwBDVZaYjEZgRFhYWkKapmiaFlVVgwuBNMu66SBF5FlOTdPQdDrZBNhH+4P8D4epumb7nt8tHu3nfM4TH1oFv/LJr1jKk+x7uVa/SAHf61wwMQCcc1jbom1KLG1bwa7TdkNqhaYs0JQlGIAky5HlORgXW/9uTc77wrnm8qIe/x/PffmB44/29zhnziPNfML2MMPYPnvo0CVfu2DHWf/BK3ML4+yNjPCUe6MjSEgIpRhTAmCMRSIODggmiINpDoAJDiY4IcRom4ZCDFEp5XWSOClkyzlvAdTgYqqMGMtEjINtxtnAb0jO15RbXj/xlX+6edtnZ+O2YuOFLJ/2dF72zzqtxbabHfBiP/fMdZz7sne0Jw++5fMtxP8rpPy3PtrdjHMILUGMwTY1vG3hrUOIHv3BEFm/D4oRFKlbkTIAISSRsfO0ED+RpMPxzR944RVPfdXn/97LUSKwmz/91nPbYvqLTVW9ZjQa7+KMiwUsA0QQgkNICSW7zk8fOmscY6wrrwfdG0kPAoEzBsFF978iSEjhlTZ3M8b+tK5ml2vubt/+soenTmvOnH8MtooIfGj90Icu+biS7k5P4U2tLV83HU+Wm6YGgbCwsIjF5WX0lxchhIAUHMG6zqvpHaxtkPb6UNoAbQPb1G3b1utxXMz9a3O+I5kLtm8DF154mSNavd0eod8nob7m2/bnog8/IDjv6yyF5N3FIXnfTUQ6tqadBIoABWIhBE4hQklBxiRaGQPGGMXoQQRKlQ4iywMDj0TwnFPjXagYqBIiK/NUT/IsbmptNplJN+vZZJMVO9cZv2Vt8o1/t2EVTXLDqjRm5T13311ffc89du++A5F9h11fbd/zu8U9n3rrh6JSpzlqfz3EOAA6gc05g2tbFMUMIQQopWHSDBQCvHcIwSPGyIQQxDnPlNEXZqDxrNw9ftfqK66+dPVDf+uXC9G71Oy2jedu39b805kSPzAhDKuq4nVdI00bGGPQ6w9Qld36KISIGOJ943DnHLz3iLE7iLBNC2iCGAoobch7ZzkXNyWp/H2K8QP3fPRDJx6Mr27OnIfKha+4rDp48KLr82bnfxScf4MJ9mYX7XmMMaS9FFIpUIyA4ABjCBTBBAcXEt45VEUBk0bEEMl5W5dlteFdPf/gMec7kvlK9NsM3bGabFicGRF+VJv0pzjhvNg63k3RBHzwoEgQWnWVSJyDQoRrW9i6AUIAVwrKGHAlEELXFUkAQECMAUJIElwgNA25piUpJQklYwAij8EJzi0X0oGzGmAl43zqvJsFRmPF5XoM4aRt3MnZdLzOpdjc1uuPLBejcro2k+O0WZ/03J/dtOqfyNeERGB3XfHzZxZN+78B7KeHi0smzTN4Z1FVFcqigPcO/d4A/f4AnDGEEOGshbUNjEmQ9nvEGYtt06w1dfPhYOmyTx3+4nWXXnrNfQZpuvm3+qViP8JBv+itf45r22w2mbJiMoG1LdI0x8LSEqSUaJsaTVUjBA+lNIgiJqMRqrLEYDhEnueoqhqjjREYY1hYWiSTpDVn/IvGJO/sK/1Xwxf/X+PvNAE+57EHAexzf/zyhSrEFxHRL0kpL+r3hzLPe+gPh0jSDNPJCJPNDaSm87O5tkHTNFuHPjFOZqNv1lX1fwzKEwcu/JafqTlzvlOYC7ZHAKJVfuTmdnEhW3pWdO1PuLZ5OYidAiHgggcXHDrLIIQEBQeECPIBwToQERhjW1eLHBDdGix4v5VmL+77dUwhAIHABQfjAowzQowAEaQUBC6IA5GA6GOILkYfY2iDDy1FaqqyKBnF8UJ/YVNKsemc20Ckk9bataos1oqm3RwsLI97C0sTYabVot5hcevUsT2rT4gKo4OrF8mFZ+66oN/r//vhwsLFQmkRQ4APHm3doK5LSC6RmARd56EEAairAkoq5IuL0IkhVzfBtu0RMLY/tPbdmyeO3LZWrvEkWdi5Y/spP72wfftPaZ2c5aw10XnEENHUNcajDdjWoj8cYjBcgBAc08kE5WyGPM9gTIqmqtA2DYQQEFLAuYDZZIqmrkkpOTYmPWgS/TtLOvnirldcNl8dzXlMcdX+vWk08fxEZr8YYtgLYJDnPfgQcfL4CSBG7D7jdCwMF1GXJcrZFIRIIYZQ1/U1Af5fHYsfOLjvYezwnTPn8cJcsD2C0C1vM6N2aUf0/iWR0+uJ8edHhh4XAkJrCCHAKYKFAPIRwXmACMTQySwhIFMNxjnapkbbtOCcgxHAGYPWBkJKgAjeOfgtwae0gtLdn2OsC4a1bUNlUwOMQycpcS5ourFJtirDcDgI/eGCA8HGGCv4UBRVVXjvJlmerid5fpJztu5CWPO+PuEbd0IqM85MPmvkrFis0xrnr7rH4/XqLR99qcmWzvveNM3/o7f2u5xzYJyDo4vPoBAhGAeIwHm3Mq2bGs46mCxFPhiCvCfvrZda3cqVfn852/z84TvvOqcqq+8dDBa/f8cpp2zrDxcFhYAQOh8cEWEyGmE2HQOMI00T5L0BnHNYXz8BhIjBYNi1G1DskuGlAuec6llBo9Fo3Tb1X0jJLhsV4cYuCHfOnMcedPAieb3dvbt24aeVUJcmaXrqeDLC5toGFgYLOG33GciyrQ8nbQ3btlRXpQPnf20S9s+f/kN/8pXH439b5sx5qMw9bI8g7Nx3tATcvXbDW/6CaOGrQvCXSqleB0bnUwySSQEGji7tFOgO/ghSK3AtwKQC1xIxBACsm8xUFTg48rwHxjg47xLtqW1RlQW4EOi8bxwxbG00OUPTtGyyOYZKU2T9IZNKwRiNpphJ5xwRsVQYDclYjNZRQjE2VQxN0zomRCu1boMPJQU+AcQ4go0dx0mJ3tGZoRN0+78/UX0zrDsXN0qJMWZHq2/Mdtk9j/Fp3Lkv+1h78obnXc1i+P+8d/+2ms12u+ChhES/P+iiBramlhQJIXggEoJ3qIrQTd0iMecamfH+WSqVPwFmng/w0533p1dV0ZtNp4wz/j8T3k0CpSTyPAUQYVu75W2MSNMEWZphvLmBqiqQJAmk0ohEEEISwMikyfG0Sfc3bX1Zc7i45WWr89L2OY9d2J4r/eoq7vrhZ73uMpWpE0LKt+dZ76nJaRmG/SGSxAAAtEkglUKIIzS2pcQkVZLk1VyszflOZS7YHmEYQLjgdwvav/fG6fnn3xOV+EII4cc4F68SXJxBPrAIgAsJmXBE58E5h5AaPNEAY4g+gDMBKSSaEEDUVScJIQFwEMVuHcoZhNKMS9WtUUNE8L77GhEA4/A+gEKE1BxJksGmDRgEiyFAEkCcCeJdpEVTVxQpJlqrvpCKGGOxO3RFIKLAEBtGvAZDxQnTxsV1Z5s14emkw8Lxp/TrY+tf/dcnAudrgdE4zdl0QU5anHaPZezAY2bFse2C1XJ26Fc/TN7v8M79s7IolqQUUEpBaw2pNSh0K+u2bdHUFbwPkFLA2xYxRjjbIiaZARPnamPO7GV97Z2X2hjGARSzKUII4FxANRU4Z0iSBL3hcMujSNDGQAiJvu3DtfVWNpUGCHB1RZWdRSnU3UKq96Uyfd/Nw+zOfb/wkcfM8zhnzt/H6iriKt5/8tqDb9zPiK8tDBZ+Jc3y53MhhGsaOGu76BzfoqlrtE0TXNvWlmh+cDDnO5a5YHuU6CpdDmzecMPq1dtzfENDHWQUf4Ji+EEGrDDOu0+XQiA4B0YRiBHBB0TvIRjv6l5MCordeo6xew9NiQBGWmeNVKr01pGN3jAgDSFIImLKJGwwXETT1LBNCyUEpJTI+30wdJ457xyYB2KM8N6jbVrGOQPjAlJKFok4gcBFIM4YYqBcCBCEoBBCqOsyNHXppFJWa11yqaYiUeNUqXWOeNy37tj6zJzA+qlHxtf/8+NIhiPBaNZLkgKnTdtHq92BAUTP+b83Tl7xlj+hSKcbY36Oc26i97BtC611tw4VHEJIcMbBeQQXAowAwQWYSpiWigRjyiSJWtm+g6W9XiesGUddlVuTUqBtPKqqQt7vY1uSwSQJQghgQiBQd9hAICiloIxG8J7KqgjTyeRwmqR/uLK8+J5zfnTXsXPZZU/Yo5A5T0joWXveM/7qJ37tisWldD3Ne7/inH/5dDxSVTEDxYi6qdE2NYQSJIRooojzY4M537HMBdujzAUXrFoAx+++++1XDNrtNxLoSsHka4QULyCwPFJEjBE8RtiqRts04IxBKgkhJZI0QwwRnAswwdGtSgPQ1RdZgN1t2+Yua9smSdKcc7nCBV9WabootckjRW2bRinBobVBkmdgYKAYEdz/PHpQxnRijgFSSTDOwMHBYwRj3ZIixgDGwLjgiAQRgiPnbMI5A4dZRIwRIUammQ8+Nta2DdlYROiRlHJdM1oDsaOTaXGP+0p9ZP1Lv3rchbBmlBkdvf32+vy9Bx4xXxxjoP37144+a7Dwu6lKdxHhlQzgbVPDW4skSaGMRtITEFrBOweKEYyLLXGlwZVitOV1S/u9rvYqBkQfIKWEcxbOtWibbirnnYNvWzAQ2tYCnCEGj8l0jLZukaQ5ACKppOdC3uXa5g+iq9/3XXv/+7H5mmjO45VnXvyfy1tueduXMrn0b8jZNXD22hBCv2s3CEiyDP3BAEmaQWfZ/H0+5zuW+dHBY4jV1VX+tp9Czwh1JpT54Wj9Ptu2F3DAcM5gWwvXtlu+NN2Vk0cCxQilulUdYwzBe8QYiHxwPsZN78NtQPyq0ubrQvIRIgsy0cMY4qnFbHZG21S70yTdlqbZMhd8AIImiiJ4z4PtPtBKLbt1KhGkMeBSAAC887B13V2sKgUwdCtDxtCWJVzb3uvrgg8eaZ4jyXNqqxLVbEaKiaiMiVJrL5SyiKGqq6YM3o054yeVkceiD0c2x5N7vPd3Ly1tO9bvZRs197Nj7Y3NBRcccPg2xlbcsH+vHuzY9T1E/j8E558fnGeccWR5l8ROYPC26+0EEaxtYZ2FUhr5YACVZQjBoyoKONvdAQjetRKAc9RlibosYK2D0gp51oPcqpVqbYO2aVBXFRhjyPOckjz3Ssk7q1n9ByeO3H75C3/2k3OxNucJAdGqPHmrOMMX5U975y8Jzu8K0UMbQ0meN0T4S4Tqf9v5zP98x6P9WOfMeTSYC7bHILR/r8AP7RlsHtt8GgX3Cs75j3GOc2xjxX3/DmPQiely27ZWolxKcM5BIDDGKTgH19QhEGt0kpyQibpRRPqia9ovex1vHSZJuTkrdb05XVYm2Zlm+Snk424f4xk6UWcxzk9ty3IYnOuZLEkpROGdh5Bya8rGEYngt6ZBTAjQffVNErETjgAA11iE4KGzLiyzLko0RQmlFLI8hzSauOCIIZCtG4o+BCaEV4mx3rl6Y+3ExFu32V9cOjkYDO5hQhxmPB6uZv4eVhcnbGVH481tswv2rT7sHpe7r3p7Sp4uDs7+B9vUT2UAkiRFnvUAxlAWs654PUlhbYuyLAAw9AZ9ZMMFUAwYjzZRlSWEEMiyHIOFRUitYdsGztmt15BtrVc5fAiYTcYoZjNwzqGNJs5FiCHeIST/bxri8jMuvuz4XKzNeSJBtMpPXF+sAPqVIcRfYsAFXAo4H9pqVnysbYp/+YyL33Pzo/0458x5NJgLtscwhw5donablaWEm2d7iq9mjF8spNgdYtd3KZXqIiFCtzYVQnRTNt5ltkXr0JQFRQKSLA1Sm4piPOa9vZGC/5wPdLXs45uDE6fMMLyBr7XLyjDokrn+IMmGUujt5WSyO3j3pDRLnhR8PLUqil0h0rY0SYxMjOouHRi4FGAM8K0F21oD3hvyyznvmh2ArWvVgBA8iAAQQSVmSwAKEBFc28K3FkIJ0kmGGBxtnDgZm7oO/eHAZ71eI6UuhDaj4NxJV9dHybWHmVB3GKPvosiP9dGcuPHm28oL9j08dUy3ffI3hknifpoQ/7Vrmu0UPJQ20NrcV8ie5z244DHa3MCsmCHLMyxv2wGTJqjLErPZFM5ZJGmKhcVlMC5QFTMwBiRpBiFVFyEiOGKMKCcTFEUBKQVppQMh3mPb5jJH7XuedvEfzcXanCcsm4d+Yzh1/gXB2zfXbf1DrW05A/uUSLN/9sw9v3fDo/345sx5NJgLtscBdxxcTQanYUWQfJ4U6scJ9APWttsYERjjoBgRQwCXElIpcCEgtn7pt00DitRdjTKAMRYYUUUUj4OzGwTnn3cuXlXR5Jun3fjbE7YPgQgMB/Zy7D1frN0I7ZtZsriU5G1Dy7PN6Q7bujNMluw2SXYWk/wMKdU2maXLHMhC08rgvYiRmHMWMQQorbdy4LookuA9ILpJEkWCULJbo27lx9m6Rgyx+3PGgChiNhphOhpBa0WDhSUkWRYZE9F7711dttHF0qR6IqVZi+TvCdbf6Z2/jQl/Rz9L7mnCZGPI10v2IFeoRGAnb/iX25UXvxSs/aV6Nu01Wzl2iUmQZjl0msA7h9HGOjbW1yG1xNLyCtK8B8YZmqZCMZ2CM4as10fwEcVsCn5vZEhiwBjrLlGNBoUIHzwhUvDWHwXi5Z61v3fq895xZC7W5jzRuWr/3jRJzZOLunhDiOHHer3BNxZWln713O95x02P9mObM+fR4P9n79+Dbbuu87DzG2PMOddae+/zui9cXADEmwQBipRIShRFSgRIyR07UmzZBhN3yi5XXKZfrU67y3Hbbrt9lPgl2bESu5xYTOJWXNVVHSBOupO2XXYcAxRBUxIBSSQBESBA4g1c4OI+zmPvtdZ8jNF/rI0rWaIkigRwgIv5Y6HAOveBvdc+KIw75hzfqAXbW8gLD+7Ojp1sTsekPzCO6Q+Slo+KuC3QNMkJmz5Q9h4ijGK2/rphWK2Q0oiua82FBjArItJb1rNjHL/Mjh5oGvm5vfbwsWv/u5+6RN9gDZXZ3fLEE6ddeWkZNpNr2o2d2UD5ZNu0Z5r5xrUEvCONw02a8w0l66mU4qapzkUkOB+I3LRnwUAgpmmggQk+hOkItRSkMSKnBPEezgmwHoCIqxUunn8FKSVsbm5jY3sLMh3B2tgPGFa9hdBo0zXFio6quhLhC8LykjThmX4cnjj34rNfi3vpSZmFF646tnfxvhf+l9XvJDHddnd5/A/lxjyU3dgP//6F86/4g709zDc3cPzEVfAhYBx6HOztTUeZjtF2LcR5sBOUkpFjAjNNkQUxI+UMJkLTNBDvUHKB9w6LrW00bWuqRXPKZ3OK/7gx9+ntf9E+Q7tHMz1bVW+03d073Xdd5091Tj7UbSw2wuZV//RDP/gPzh/166qqo1ALtrcYMxAe2Z2ft3wdQD8UQviDxPSBUnRmr06UssBs3V1bT3iWlKaJq64DiwBaTKbjyZLGuCLHZ11oHjFLD5ScP3d81jyGG3b3frtOjtndgqfu8BcyQqB5q+NyM1k6hqRnspYbTMstDL6Wha8x0DWAbYhzQZx4IyJxDuIdYEApGSUXgHk6HlSDpgSzKVZktb+Pg/19iBNsbG1h1s6mfawx4dL582AibGxtGoNARCYiyk4y+XZcrZYH55577nwZ43Nd1z3pvH+8bcLjwfln+pfPn/03X+oPPrn72x+f2n27rj/p3l+gP3G4v3/n+ZdfAgAcO3ESjQ8YhiVSKVNXUwjCAtUCtanG8qFFaALSGBHHEURTnAo7galidXiImCJms5n50FhM8YIW+x+p5Z+66aMnnziqqJOqOkr3/N0Pd2duPuk+8kvvX9Y/sFRvV7Vge4sy2+VLv4zN0LlbzMsnVMsfNNXvUNWGRZBzxv6li8g5YbG5jbZrwd5Px5I5A6p4tbCJKcFKKSzSi8iLRHjEET0Ah8910Mdww+4+0Te3+H13d5f/6sfAzzV7fh5CSI20XGzLsp6Cwxkrdj2xXC/e3dJ07dXiw3HAtjWnRhVM06oHqBrIDFp02iwAII0jDvb3MPY9SKajyMXGJiR4DIdL5JSnTpVzIAOgBeK9SdNiGEbbP/9KgZboQ7Mi8CXH/LJqfvpgf++JfhieaMLia1b4ed/NLu49O/Yf/BOf/oaZT/bkbrsa5Xf1/fiTh3t774p9j27WASCsVgdwPlx+XVM+XZkmd4vCNw3CrIWmgtQPUFU4H8DBo8SIg709rFZLOOcshOZSHMd/kVP6ydvH/ktTdl9VVVX1dlQLtrc4u2/X7Z/CJjXuNpj+u2b4URF3q6q6g709DKsl5osNzDc3QN7BSoFp+bducZVSMC3CAghUiHlwImdh9CUr6XMc+HOR/Ve33vEXL30rd6d2d3f5r/7wC/Ii4DuZheR9y4Pbnp/YvhoiZ0z1Oiv5FhK5gUDXlpK3S0oLAlpTiKmChJHHiNXhIXKKSDEBZtjY3sFsMYeZoqSpnmEngCryECEi8F2LYkBJ0UrOsKxGIGWmUnIaD/b3D/vlcJ4dP++cfL2dL766MZs/AQnPhKa8fO6VF/ZuvOtnhl/7ns7/3O5mlP4P53HcBdGJ0DTIJeNg7yKsKDa3dzCbz0EyDRHApkKZALBMd/fSMKJfraaJ39BARJByQU6jEWjpfPjMajn+5IUnHvj8B//EQzUwtKqq6m2sFmxXiAcf/JR/19YN28TlfSzy75nih9M4Xj+sevbew3sPpWlIgRhT58oURDQdo4qAWaCqRswQpqJZe2g5S+weJnYPBNEHDs09duzmv7D37b7e9dGux4m5x5jDUMom+2ZLSU+mPt6ArDdryTcq9FqAr2Hh42mM7divvJAjWhc9vmngnExdQ2BdIE13wXI/gIURuhYQAdG66Ns/QE4RTdOYdx45JRuHocRhyCmnlW/aSxtbWy+17expOP66avxq349f53F8/hV96dwdn7l3RbuwJ+/701c1XfcXfNd9yjVNR8Lol4dY7e3BiWA+m8N3LTgEMBHyEJGHAWQGEF3OWAMLmtAgNA1c25ppjmmIvyhMP3Fw2P2LG+/aHb7Nx11VVVW9xdWC7Qrz+OM/1pzxp49B7YMWy+8tpfwuEF1npUBhcKEBmJCHftpl6QTEDPEe4hygOi0lB6ykDMtWiKWX4F60oo/EcfW5WPJnB4tfveE7/4tv+qj0t3N5MvX2O+QVt2oamjVlNSyy6QkIXe1cuDGN481F9WZhuYaYT4pz205cozlJSZlIBOIczAxjPyANPcR5tF0H1waQOJR1wXZwsA8nDhubW/A+TPs/x8GWh/sYY1LvQ55tbozdfH7ovH+l5Hx2HMevpxIfd372xPzE1tep2Ctjv7rB+/DXSeQuckxaCsbVErkf4Fim7QZEQCnrmJUlSlEQE/p+hX5YoesW2NjcgohYgSUt+rU8pJ+Krf4Pt37v398/6u+pqqqq6ujVgu0KZU/utn3GiRLpw2r6+2H5LhJ3lTTNNHXZr5BjAnmBa5spDuTVfaRE03LzXKBZDSCIk5LHcYhDfxZED7vG/6xD/pym2Vd3vmv30uvyHgC6/7475Qbc4LY2rmpCM7SrvLEJLadzie8IvrkhtOEW5HxTUb1WQrPlQrNIMTbLixexWh7AAISmwXyxgTCbAWZIMaJfrmCq6GZzCE9bHMymeJTDw0OshpU577F94oQtNjYKs+SUUj/GYZ/Jv9Rszp5x4r4OzReslI+mVP6dlKIXN0Wq5GGE5QJmRhwiSk4IPiClOK2isoLD5T6Wh0u0szl2dnZMxGuK8fms+R+Juv/6lv/DP3z5qL+PqqqqqjeHWrBd4ezB3dnY5KtHoo+IyN0g+ojBdkrOSOMICMPPummtFLCO3QDIFKZAyQVEDHZieehhRQsH3yvsrMb8yJjTZ1Pff+5SXj72XXf9zKXX/f0YCA99yr10vg9p40Sz2Gk2MfodZrvadd2N0rS35XG4eXnp0rWrg/0z/TAsTNX7puHF5gbabg72HqUo8hin4GE1LA/21+ufFlAAh6sl+rFHEwI2NrYwWywMzBiHXnOMxYcwOu+XRHRoOc9Xy8NT+/t74r3HbDYHrWNWxHuM/QArZVpHBcM4jIhxwOHhAYZxgIhDCMGY+AKB/kmv+T9/3+/5x4/XrLWqqqrqVbVgexswA517ZHe+peO1PfGdIvQHFPTdOaatrAV+1iG0LQxAiXHaVkAAswMUIJmOTa1kiPdmxMhpLKalzymfHYf+EYA+F3x4oOHZo9vfif03Kn7CDATczXjitAOON9jc7uLewbGDg+XVfX94YxzGm/txeBcRbmxns9OLzZ2tdr5ocsmSxwgnDjDD8uAApWTMNzbRzGYoMPTDgLhcgg1YbG7Ch4B+tcTQr4yZ0DSded8YMdCvlrx/8RIBCu8dcirw3mO+sQkCgWDwbQCMkFPGOPTo+2lHKDOZFl3FGD8TM/218w99/Qt37d6fj/r7pqqqqnrzqAXb24jt7vKFf+f8gmbb1wcnn0gl3p1y+U7fhtmrd9s0Z8RhAAxw4sAgGKaF8gAQZh3UgBwHY2EQcck5DZb1rDj3SJi1nw2eHli+8vJjO9/1X1w6kvdpu4xH4M7h5ZDP5fZ8P26FQGd8M7txc2PnVtc0txvhViI6RcC2Fg0pjlTytJUhdC0AYOwH9MtDWC7o5gv4ELA8OMD+pYtgIvjQoGkadLM5iICh7xHjgDgMODjYRwgBO8dPopvPISLTvlcRqE5L4nMc4ZwzwGKM6eHlavWTGJ7+/976e/75eNTfK1VVVdWbSy3Y3obuuedu+aFb79ggl27JpP+OD/5HYfgOsHiiVxM/CFQMlgpKKVPIKzO6zQ2YKdIwgp2AxRlgIGCKA3H+rEEfsVJ+dhgPH7j4zPOP3vp7/l9HenH+1WPURw5cc3xrq5t3GzvkcDULv1NV35tzerea3UqEkwTqhAUlJyz3D7A6PAQALDa20C0WGFZL7F+4CFNFUYUVxWJjAxtbW9M2g5RwuL+Hcy+/DBBw6qqrsHns+HQ/cL3rFczIOaOMo5GqgvBsHtN/3Y/Df3vd9/3UhaP+/qiqqqrefGrB9jZ233277gOnxy2wuz1n/B6Q/Qgzv0ua4EQcNGakfoSWAi0KkCG07TRlmjLADJjBVOGaxpwTQK3ArDfVs0M/PLJ3/twDy0sXH1goP3rTJ+/dp29hj+drzXZ3+ZE/CHfHVV3zyrm9hdNwlQrdTGzvt6Ifslxuz2M8mVJ0y+USfb9C08xw7MQJhKZBHkaMw4DlwQEODw/gvcfx4ycw39wEE2FYrnDu3MuIacSx4yeweWwHAAHE8LMZ2AniMKLf2zeY7XVd9z9xor+5+f7dr9V7a1VVVdU3Ugu2Cvbgp/xLfnOHmN/nxf1e8eGHvWuu1Zwl9iNQFOQcSkoAGWTdJQIBtu4ysXfThKkWIwM0axnHYYj98FIch4dTSg+UcfWzXcJjN3/y3m87x+01ff92t+C5D4eD1f68j4dn0qp8VxrjnSE0HwPTdft7e+7w8BCLxSZOnb56XbQNWB0eYnl4AJhhvtjEbDEHESONA/YuXUJKCYuNDfjGY3m4RCkFs80tdF2HHJPtX7o4AvYL3ebWf/r4cvaZu+7arffWqqqqqm+oFmzVZY8//mPNNcvNE2PA93jf/SgMvyv2/VVQBTuPkhMMgDQBzARdB8DaOruNmJGGATkmCJEBgBCrWu6XB4cvXbp06eHQhM9u7lz1wPyUPHrs5p94UxVuwHRcfOfJk90SesJ1Gx+YzZo/sFqtfvcrL720bQBOX30tNne2oSWj5IyiCs0ZTAwXAgxAf3CAw0uXIM5hsbmBoorz515Gv1phNl9ga3vbgm/KarV8eojxv6R5+ZnbPvqPDo76vVdVVVVvXrVgq/4tBhCe/bPtUI6djiv9qKbxbhH5MJhPqE6ndUbAOvwD4KlgIxGwCJb7+1ju76PrZpgv5vChsZwilvsHun+w1xPo7HyxeKSbLz7Hs/BZ4vYrOzf9hf0321GgGejFhz7VzWfXXbPc3/8j+weX/uSwGk5sbG/h2LHjUzcRAIvATGFlKl4VNm0w2N8HMyGsc+/6vkeKESBC17U229g8T6b/4+HB/k/ceNc/fOqo329VVVX15lYLtuobejUKpHX5GoK7S0juVsOHYkrzFEewF7CbAmeJpsJEAawODjCslmi72XSnyznkcUROyXJOKCkX1TyEMHt5Nu8eFpYHONnPjlEfe70CeL+957DLLz5w4drDOP7F5cHyj/ng/PFjJxFCwDAOICK07TRVqmogZpAT5JSQ+gFjPx2Ftt0cTdcil2xa8tA07eec939t+9FHH6hL3auqqqrfTi3Yqt+S7e4y/kMsVvC3qOnvHof+96ac3xvatgERxr4HMyOEgFIKSlGIdxA3rYlSNeSSwc5DHKOM0cblCmzQtut6Mno5jfGRUvJnHefPMuujRxUH8ps+gwc/5R/fl7sP9/f/Qc5p+/jxk5gvNrC3dxHjMGBzcxNePGCKdjaH7zoULSjjiNXhIWIc0TQt5psbRs5rGccnofmnjnv97+l9f2d51O+vqqqqevOrBVv1TXl1orQf8m0k/keabvZ7AbtlHEenuYAIIAOkbdB0HQxAjiNSTODgEGYdTA1x1QNFISwmAMqYNPV9X3J6yYn/SthoP8dt89kN2XgEN/zZvTfDUanZrnvhl4a7h4PD/7pfrbbm8zlCO8P58+cw9Ctsb++AzUBm2Nw5hmY2g06JvtBSEIcB4zBCnFg7n++J0P80lPGvn37v3/r6Ub+3qqqq6q3BHfULqN4a1hOM5+3BT/3CIZ95XIk+w05+f0v0u4dVf01JCSEEeHEgJqAoSpou5rs2TAvZVz3SGNE2DZz3ZDkDBAYwZ8j14t1pZneH5fL959Olz/Fj/+ln7Mk3QeH2xHmZL05f2zWz2SKOECLknAFVeO/Rth0sF+Q0II7jtJc1eLA4kDA4Z2g5tDj2yWBfnS1m//NXzv/cM0f9mVZVVVVvHbVgq35H6IOfTgBefvLJ3ftOh8WvlKL3lZz+/VzSD3jz2zklFFU4vy7cAOSYMI4ROUYwMXIuIAACgJjJeQ9wEcBmJeZ3ZC2nYo63k+OPnrf0QHj2b3zWnsOv4Jq/dOEoCrf9eHwec38HMzsfPJgIRoTF5ia0FDRNA+4YOQbEmLC3dwnSNOhmMzAxYkpIMVkxPR+H+L9tbjefv+uuunqqqqqq+ubVI9HqW2YGwkt/e/bik8+/IzF+sGvaT+ZcPlBK6drZDCyMooqcM4bYw7sAYUbf92hCg42NBTQrLGVAbYrHELZpj2evBTY0bXcuzGaPmZV/s7xw8YEQ45euPnfx4ht1Ud8MdP7Lu+9KefwfYHiv9w4iDsSMAoPmAjYDr3PpxiFi79J5jOOAxcYm5otNmBYblstBi/4sOfdXb/jof/kLb4aj3qqqquqto3bYqm/ZVHT8J0uz3ceeuv/SizSTz8d+9ftSGn9fyuldTWicb9tpKMEFeO+RU0YcejhmGDMMimKKEAJc42EGIjO0RBxznOUYrzOzU8zybu+b71fIzz1/ur3/ic/9yS/f/H3/8NzrXvjcvyvYwe2h7a5nYQgRYAY1gMzATBB2668ZXNtgttiEqiIOA4hgwTfatu3ZUvSfkt/4ci3Wqqqqqt+p2mGrXjN23677Wjh7zLJ+p7D7gyzu3/WhOSNOAADOO5RSMPQDQhPQzeYouUx3wUIAM0+ZZjoVPyUlpGEEMZkLjcJ0HPvhAnJ+3IXweQPdv9y78KUbH/hvz9Eu9PV4Tw/f96cXm037t7rF4k918xkTEUrOGIcBhwcHIACb2ztwIhiHHikXiAjiMGC1OoSWYuL8ctZ1/5ThfvzMh3/yK0f9OVVVVVVvPbXDVr1maBpMePnxf/Zjn2nm5SvJ2WdiHP79gPCx0HabxAzHjPn6SFEBkHN4deF80XI5kJYAiAi4a6FqxDAm5q4J4erC7oQL8s44xo9I0/3cS7/7P35g9YcWv9S94F+i13C90+4u2BW76fBw7yNjHNjsGJq2hZkhrSM7RASLzU3AO5gZYt9DxEGtIARvTF1RLc/EcfynN65SnQqtqqqqviW1w1a9LgygR+770/NGyjtmG5u/ZzZb/Afi5D1FtYEB4gTGDFXANINs2hrABljJsFxgChBPHa00DGAWsBMQzEicFS0xjfESM3/NNeFBy/H+vcPhF5/Jx154LfZy2sO74dkL5//Y4Wr5d1JKs/nmJja2tuFEcHhwgEsXL4CIsL29g/liY32HbUQaI8ZhhaZpbL6xuWeEf5IOL/5nZz7yD54+6s+lqqqqemuqHbbqdUGA4a7/6tBs99G9Lw8vsqefN8PvLzn/Phhu4PUxKaCX/9SgWqBqQMqwrCAQmAUiAnUeOUYM/QoiQu1sTiGExomcAtGOqd26TPnDROULt24e/uylR//yF57L7vn3vGc3fiuv3wB6/vD8KfbhR5omdyUrSs7IKQFmKGYwAvrVCjGOOJYSNrZ3ELoOuWQY1NhJds49BUf/4kRKLxz1Z1JVVVW9ddUOW/WGMPtp/8pjL57M4/h9TugPE8kPlFK2nXdwoUFOCcNqBVIDGwADgg9wwa+PSQ3DcoWD/b3pGHJjA+QcwAQXvJmaxWHMpaQ9In7WmB5Ss89ixIPwl54+88FPr35Hr/fBn/Z7s7O/P6f891KMp3LOEOfgvAN4mn4dhgH7Fy8gDgPm3QyzjU34plkHBg/WtbOL3Xzx/26D/xuzd/3l54/6M6iqqqreumqHrXpDEP2JBOCFFx781D9LefsRK8O/m8b4h5qu/Q4U86oGKlOlVooCakDwl3+9Gaa9nV03LVwHMK5WKFYQmpac8ySOvfj2uBXbijHeUEr+UBxXX1pdXP2br/7LP/75ndnO105+9CcPfrvXaga68PC5021o/gMXwvHQtjAmEAEpJuSY4H1AM5uh61qMyx4Eg6phtb8HIljTtjk04Wky+t9+/oX80lE//6qqquqtrXbYqjec7e7yc7/r2e1c/HuD6/6PRPQjLHI6dC3AjDyMKCmjaRu4EAAzxGGctgiEqeumOWNYrTDGESIOzk1FnG8aeO/NSkHJJR8c7i0PDw/OktGvzDc2Pr+xOPb5brHx6MZt8SLR7jecLP0399zdXXvNdX90tpj9NReaY8QCMMFMEYcey8NDOHGYb22BiJDHEUyEFBP2L10EmdrWzs5eN1/8DzHRXz/+HX/52aN+5lVVVdVbWy3YqiPz8MN3h2MXrz6tWu6Cd3+k29j8XglhpjHCSlkvkXcoOWM4XGEYe4R2hqZrYTkDNoXyqhlyLsgpwoeApmkAIjCTpZRwcHBYxn7VO+df2d45/lg7m/2CWfnCmA8fHi5dOnvjXT8zXH5N99wdts5c84mc4l8n4ve5pmEXAqY7d4ZSCvrVEiUruvkc4jysZIQQQCzoV4dmpeSu7R5rQvufLfrn/uf1doiqqqqq+pbVgq06Umagx/6X/2ixcaq7tZnt/EEX/B+C6Q0EAjsGAGguiGNEShHEDO/DFPvhBMwEBSGlhBLTtISepl/HItNGAs3W9z00Zm3aLjrnLmixZ8jhy007fzAs2i+Rcy9hGJCjfm8e4p8ZhtX3jH0fmAkuBEgIYOemaQotiKmAiMHM0FLgnYNrAwAys7yfh/S/WuT/x+kP/bUnj/oZV1VVVW999Q5bdaSm1P9/dHDffXd+6bs3vvuFzP6LTPJHTO1jRDQ3AMSMMGsRqJtCdXOBlQIzA4mDdx7SBGhMKClDS4HCQEQgIvjQEjuPPEZhQ5tzvlpzOdGF7p3s+Ps16zMc0zmQNM7Ju7mjW4gRfAiw9VICdg4SAogJpgrXrP+0Q4SSMkwLmMV826jm/NI4xJ9dnn+oToZWVVVVr4naYaveVM5+8c/NQ7O4WYh+lMX9EQA3lpwIxBDvYKrI/YiSM4gJznu4ZqqeyhhRUkIcI8Y4gEXQzRbwTUAaR4z9AOc8zICcok3duqa4xicGJVMVAjUsIoAREQPrHDgjQJoGTEAcRwx9D1OdisUQYEXBTsy3oQfoX6b9vb9y7H1/4+Gjfp5VVVXVlaF22Ko3ldPv+ztLs7sfOXzilhfBm7+iOf1x1fwDMG4AwEqBqoLFAQzkXACKEGZozlPXjQmq0zwBCcOIUEq5PGnqggcT0zj06NOhm9FcIK5d7h9SiSPmiw008xnMFABNhSKmQN9khnEcsX/pIvp+ha6dYWvnGEQchsPefHQXfNP9XESsR6FVVVXVa6YWbNWbDtG9BcA5O/u3/9n5V1ZPabE/6h3+AFSvIgDOe7AItBTEOKAAkCZMcR9E8CLYcFORJU4AMzjn4bwHgWHFwCJo53OACCJCJSX0y0OM/RJENP1eZgATfNesc9d6qNl0jy4EpBQBM+SUYKbWr1Z5WOmToRs/f913/+e/o9y3qqqqqvqt8FG/gKr6zdDp/2T51PDSL3stfxuEn2ChXyEiVVXADCI8dcucgEQg3sN5B98EtPM5QhOgOQMlT18PASIOgIGFEWbd1EkjwjAMKDpNnK5WSxzs7yGmCBIGQMg5Y4wJMSXAgPliA8dPXIWtnR04J1Pw79Av9/f3fmlvf++x6W5eVVVVVb02aoetelP74Ac/nczw9P6v7P73YHkqx+H/lGL8CGloXBMQmgYQBgHQrCBmsFsvl1cFcgGYwMIwI4BtOuIkQFVBBmjOUFU0bYvgPGJMGIYe4tx6u4Gf/hnBr+NEAGYCeYIVg1mxUrQQ2YvDuPy5vcXz54/6uVVVVVVXllqwVW96U7dq98LZL/65f0nqXyalT6nZv6dq2y54kBPoOCLFON1RIwKrgQiYMtQYzDJNmKpCsY4KiT0AgnOC2XwGms2gqoj9gJIzRKbIDjODbwI8N9CiKClCc5m6bsMIJoPzMnazxWMHh8tfvuuu+7/txfNVVVVV9WvVgq16yzj9vr+ztId3v/Ciri40Tp5n5/4IOXfGTEm1AGYwAzRlFAAwAzcBxDzFc6zPKVUVaRxxeLAPEGFjcwtt00yZalkhMk2jvrpNIaWI0HXwXQtihogDYRpsEGGIE/Oh2eemeyidP3zuqJ9TVVVVdeWpBVv1lkLv2Y12z91f7b/rjv8Kzp3VUv4USrnVzISde3Xp6JTTpgpLCTkCWjLEeQjzunAz5JyQSwELA2Zo2w7sHfy6s9bHEQd7ezAmtLMZZnkDvglgIoCmbLbZhjd2XiW453yih07+0juWR/2MqqqqqitPzWGr3pLMjA6++uPH2bkfKrH8x0T2AbPpDyDE6w0JqiiqGPsBJUf4pkXTdhDvkVPG8vAQBweX0PcrzLsFTp46jW4+B2BIfY+98xexv38JYFqvyZqGGkLToGk7hLaFaxoTlqVq+f+MOf6VnRv/4lNH/WyqqqqqK0/tsFVvSURkAF6xR//8/+/i4PZV6M/6ED5KRM10NKowEErJiHFazg6bijkSgSPGfHsLWQsuXjyPNEYsNrbg2wZQRckFLnhsbG3DGEgxYtWvkA4ivPfY2TmB0HUgIssp7mnMX9z2Xz171M+lqqqqujLVWI/qLY1u+8mDobxynyb98aL6z2E2qhaUXFDiiHHVQ0uGDwFhNoOEABhgqhBmzOdzzGZz5JywOjxAjhE5JRQtCE2DxdYWNrd2sLG9g/liA0SCGEf0qxWG5dKGg2XpDw7P9qvVl3DDz4xH/TyqqqqqK1Mt2Kq3vDMf/PRqb/PwF4Yh//Wiei9ggwEouSDFCCaGcx7i3OX7awQARRF8wMmTp7F97DhY1lEgPC11TzlhHHqAgG4+x/ax4zh56ips75yEE4/h8BDD6iCqpq8Gy1+t2WtVVVXV66UeiVZXhFtv/fujPfipL15MJ37CnFuK0H9gIlshNCACSs7oDw7gmhZN14KdQ8kJZkAIDeaLTZgB4zDAew8mRooJ49iDmDFzDk3TwoUpjLeMcVr47t0hMz/8zHPLc0f9DKqqqqorV+2wVVcM+uCn086F8KiA/46w/Ixrwl476+BDg3EccfH8eRzu76GYgrwDOweDYeh7rJaHSOOAFCNi3yPH6a5a281AAEpKyOMIHSLYgKZprWnaElx4mYwe/p8//3f6o37/VVVV1ZWrFmzVFYXu2s2bjzzyZBH++yD6h67xr3DwUFPEcZgKs2HqjhlNy+ElBPjQoGlatE07HYemBOcd5hsLhLaFmeFwfx8XXjqL1aVL0DiixBSHw8NnNI5P7O5Cj/q9V1VVVVeueiRaXXHok/eWe+7BUz/8/nf+gzFJr1r+tPP+VDefo5ghDQMiTxfO2Ht0GxuQEMCqcCwoKQIpT4vhnQcRIcWIkjNW/RJaFMyCoqnXnL+y2Dh84ajfc1VVVXVlqwVbdUX65CfvLbaL55f/3p//bwqLNbPuzzTz2amUpo0IcRyntVXMMDWUUpBjRGGBMMF7P0WAEIGIQcxouw6lbMJyQU7RUor7/bh69Iny9OFRv9+qqqrqylaPRKsrFu1CT3zXT74YqPlHzaL76W5j81K3sYARIeu0KH4K2TVYKRjHAeM4QM1gMIzjiHEcoVpANG072D5xEpvHd8y3TSGRl0qmr9115/3lqN9rVVVVdWWrHbbqikYEM8MLw9nZPypWFlLoj4qTHQIuDxwAhqYJcDIVcE4c0jBguTzAOPTY2tqGb5rpx4hgIaDkmDiEJ9U1T6LGeVRVVVWvs1qwVVc8ol01u/vZ8fnv/YfJ9Lh3/kdFeEPVkOIIEUbTtAhtM/389cY2H2coaURMCew9oPqru9xYhmYWnj5x0tU4j6qqqup1Vwu26oplBnrooU+5a/1mOPjqolNeFRb7Agm/X1jeTWTSNA1YGCw8bTgoCsc8raXa3sa4PERMEfmgQETgRCDCJr7Z98F/HVfH4ajfZ1VVVXXlqwVb9ZZku2D88Kfk81+/6DZPnpStU004Jqfbgrgh3m0m6Ob5r+rGNW2/ycZbCdhBsR1K5QawbRIA8h4hTJ2zlBL6vkfJBU0I8EQQJoQmIKWEmEY0MoM4Z8ykxHQeLF8FdutxaFVVVfW6qwVb9aa0uwv+4R/+gPjzt4djGyeaY9tdk9xGVyh1aW9/cWEWNmPEztXXNjsM22lSt52accuR2zDSTQFtppw2NJaZOGrIEMyKH4ehMcOsnc/YhwASQTbDECPGOEKYASGAASKCawLmIORS4P203kpzKmPsXxrT6oWu3l+rqqqq3gC1YKuOhN1zt+C7f4/HDbFZntvr8oWx6y9d6hLneZhvbXqW7ZLHLTqRtsCyOWRsicvbnnnbbW5ugHjhGrSOMdNcWpC2GnPIjsQ7YTM4NrAAxOu/tChSTGQGartpAXxOETkXpFJgAMq6eCPn0AQHYQfxAS4maEooOWOMYxyG8QXNq72jfo5VVVXV20Mt2KrXnBkY+Gl58aGHfJMbF44vmlJkYey2UGRb5rI5uLAh4XArvZSOlag7WWy7ONpIQ56Lj4t2Yz4XauZq1JpaQwanKXmY9ywsRMwsTNx1pKVQyZksZ1JVKBqQCDEzYIY0jLCiYBG0oYERQ7yf8tiGEWNOICb4tkW/PMD+/iUMqw5bWzto2w7sBAZDzhkkBGI3OGdPPVeePTjqZ11VVVW9PdSCrfqWmIHuvfdu/vC1Hw6bW/tdDpjpKs9JaHP5ZNgp5dzJbvOaEznGYzFi27ftpvNuS423QdhQ5hlZaWEl5DSGNIxNMXVmYJgyDKQ5c8mZiJmImKwUpJwBYXLiIN6B1geSTAwIr3M8FCgGgkFEkFVhqoAIxDmQCIgIMICJ4Fjgu3YqylJEv1qhX63gxMGxg8c0Oeq61hhQV/SQgac//P57Bvzq3GhVVVVVvW5qwVb9lsxATzzxY4H3xnbz1Jl5k2XDQFvnvlqOf98tyxOOzh1PEo5bsuNZ4wnHYacULEx1bmbzpm07KBqAxNg5YUiORdQimXMEM5q2CYBEHCEYmBmqCjCB1v+fmcEiKOOI1eEBzAyz2RxN00yXyMxgBqxzcGEwsAia+QxBDbDpr5wKSkrTG/MOTgTOe5D3UDMsNjYQnMe46uHEA2QwAlzwIBGUMdkYV/vD0J8lonp/raqqqnpD1IKtAjDdKXticdpt39QFqFv0y3icSU+e/ZI73uZyrITuKkftVezouMa47ci2E7CVNc+cuqBFGwOFYupySiziyDnH0njSopSHCFUDeyEtBSUWsCSIm+6J+dCAQGCiqVumCiKaji7ztNeT1im4OSWknEAgiAiIGTkmpBghTtDO5/BNA3YOYALMoClPhRoA0+n3IDO4JoCJkFOe1k/NFmjaGZq2h5UC9h4SPERk+nUlaym674guHPVnVlVVVb191ILtbchsl1986IV2w59ZqMMmrOxcHOPpbbJr8oquLhpPWkmnstmJnMtOgXZCzUyMO3YcNIuwmjgfuJTCMIMTJvENSlHKOU/FVnAgcSDNUAMsjkCiy6ufxBxYBMwMvz7edOtl62SAlQICQMQwVaRSkFURuhk65y7vATXNKFpA6y4cM4OdAwmjpIQSMzQlaFHAAOf9VMjJtJktp4wCQ5i1cE0AGSDOocQ4de+IoKrQlGBWinN8obTzS0f9OVZVVVVvH7Vgu8LZPXfLK+/94AyrfqPdwPaQceqlL8XTgp0zmcZrHHdncs4nhpi2ILxNzAsman039wrzOUXWFNkxE7QQIEQiyOOIlBKYACIP8R7FFFQM7ATsBCIOTDzlnc0ImiJKKUApULNfXawugpILzAziHFwI0JQRU4attwuUMh2RNvPO2LfwwWsZR4t9DzKgaVuI93Aixuuum+aMuFwhjiORGZmREDOECSIB7AQ5JYwxTsWlGaxkEDGEGcUM/WoFqMI5t+7+IcGFl5rZsk6IVlVVVW+YWrBdQR588FP+Wr8ZFhs7i3hYTlnJp887nG5kvIY23DUGvsq7ctIiHzfSLXK+M+Y2J/VjjAxAQteS9458aEBOqOQGOScQM0ouYC5QNZQyhc16JwARSinIuUyX+J0H8/R1mAGg6ehTGGI2db1Up8LOBzNTK1lLjKk4NRUXMhGPIOoN6FmkF+aenRvcvBtJfITlJM5lCUEJUHEO4txIRsVgMC1IKWMYRqQUSRxts/EHcso3WIwkMcI3AWaAmoKKoqQ8jRCIwBQY+x7LvUsACLP5HCF4ECga6cur88fqhoOqqqrqDVMLtrcoA+iRe+721334wwvrx+1xWF1FRO8IkHeU0a5l0TNqdjXAOynqhniek3CjqTgyEnaeAUemBlUlYYGtJycBnrpdRODQwPmAogUwoKSCrAXkPZrZDKSKmDJKzmARuLYFAOSYYQqIUwAwM4A9F6gpmKJjGVl8b6p9GuJhTvFiLvmCFb3ovbvA7C6CZZ9I9plsSaAle78Sx8PYj1FZk7dSRFBULBc4eBfK9HRWAGbIy32UmMD9AJuHmYn8cWL+szlFV2IBCAhdi4ZbmBnSGJHHCB88WAQwQwgNmBlOBAAsF40kfOEqWaaj/h6oqqqq3j5qwfYWYbbLX/qXh92Z63Y2h0uvnHiiH6/emnXvsGF1o5Bc37btaYKd0KLbeRzmxUrDxIGUZEyROScKbUfEBGk8rCjSOE6haSKYbW2DhKemmOoUjzH9k5FzRill6kjBoEWRtQBEcN6jlIKy/jVkBgOZqaaiZeRsg6qu1LAnKheK5gtqesGzPwfSl9TKK+NqddE07QePg3HIhz7xakYWLzZNzv1Qtjc3CwanjwwX7I6bo87ufcRw9x2GHwfwV3eNvoltA/b4jzXn88ZFJkIq06SoYTqCDV2DnAoO9/dwuL8HHxpsbm/DhwYhBIAANUBzRkmxL6NdwKVGj/p7oqqqqnr7qAXbm5AZCA99yr2yuLmdb7jtFMcz5x+N15y5pnsH23hDmM+vMeeuZuKTJaZN8pgxcSBxAio89iuOfQ8STz40SGNEMYULAU3bQkUxHC7RLw/hxGG2sYGwWIAdA6YoOUNLAYFghnVBVuCI4IJHyhnjamlmZs1iI/u2zQaMmvJ+SmnPO3exlHLOMZ9VtnPGek4xnMtJXpGsFwrrqnOuT4eu3zxO4wVzZbl8TO84d4fiM1Ds/t/t1V7fb+7e6W+73+RDde+n4F7eImZBXm8sGIbpmfAMPDXU0PcDhmFE03ZoNlswT9OpSkA2s5zLahzHPXymFmxVVVXVG6cWbG8S99xzt7y32ZgtrppvvfLl7au8txslr24qq3C9EN8AdlfFrMdgtinMbTfrnBZ1mgsVYlIBOZF1NAaDSKbJyldT/F0AmFCAqbuWEnLJ8D6AmKA5AezALCAWkBkIBCIgtI2ZmjonBcKRhFcEOiw5X1Kzs965s178i6usL7LipYLhldjHiyvj/eNxsUyLfjzWXUxPLJvy3NkvlzvvvFOBv2yz39AZu/d1e74XXno6yKlwjFUJMBQAKSeslstpuhSAMGNjcwsEQFigJYMhMGaUnJFTBDGtvOMD7Nal71VVVdUbpxZsR8R2d/mlH513rpVtyYenVqvlO0zjTURyCxTXZ8bpHOOJmNJCvO+ExbNzTFMgBV0+miwZ7AQEQU4ZKAXiByT7AQAAgABJREFUHJquRckFMSVABKFtASLklMAAQvBgWkz30IgxrlaQ6CDBAzBjogRgKEV7gPbY8StMfE6znhXm55rgnyu5vFySnkejF9uu25fOhmf2D9PMjfm51ZfLZ/75/bq7i2/Qibr/DX/eF8LYLFbpWNN0057QrgUYKGPE/sULUFWEpsPGxgZYpoK1lDJFh+SMsV9ZTFmZuW/b+eq37wBWVVVV1WunFmxvENsFP/WxPxpOXPv+DSrj1StK18693KSWbjajG+Yb82tKCidSjJtacufYOWGSIUbOOSK0Mwqzboq4GCN0HT/hvAcAaEqIMYFh8M5DnAfRFE1hBJAaLE/3ziAOPgQTYTjnlWAZsKg5L8G4SIrz5Phsgb2QhviCOXuhYf+iQV/xzPsEW760n1dN2kxXuRcKbjpTQH/pmzjGPKJnb6DHH+w3SmnPTGsQDCyM+XyB7EbsX0qIcQSJQ7AGeRxRckYIASICVZ3iSMzgnetDaPujfk9VVVXV20st2F5Hds/d8vUP3LQ4tk8750TecTw0tzCtbqaGbwb8deByipJuq1lHME8iQsRs0GlfpvcIBqgqhAm0XrFkrx5XuunYM6fpzlkpBeL8NIgJBjuBRwBgcE5MVS3lnE3LSOAV1A5ySufEyQvM8qyaPi+M5xU424/9+ULlkva8DPO9cUsl4ZnjBZ+BNru7dtubtDj7hn58l+Z3ly3PfJ2wgFkgLOs9VsB8YwNN112OJ4njCNX8q8VwUZgamEmFJYqnGulRVVVVvaFqwfYau+//eWd7/XW3bM1PX336UtPcMFsN71zl9K55468n58+w8LYaLYDSlFicqrGaUonTnTJTgxMHFgfolMwfmgYiDrEfIMxw3kFVMY4jYkwgm04dfWgg3iGNESJmIbTRiGJJaWVmeyxynlN6qZT0PLN/moWfM7OXVPMrXUMX8oXcb5zcHJ44eCbf8q6zGbhXv5kJzDe7h374BbkpvON2Ao4zMYSmbQs5ZTAz5lubMAAlF5ScENppMtS7AFPDMPToV4cIoVHqulHF1UiPqqqq6g1VC7Zvk9kuv/Slw64J7mTi2XUY4y0pl3cS+BYYX69JT6WUNlMJHQNOAQYpxRipX00nayEEEDNIGc7ztAeTGeMwIuWM0LbTvsySYcaQVwu2vkeKoznv1YWmhDZEZj5M0zDAeQVeBOyFoukFynjOhfACSjgPvrTfDXywaofhYDbmW265coqzb+SmnZ2ZJ/4YFA2pQTUjlfU6KxHAe5ATeO8QmqlYI+Kp27ZcgZjRNA18aBTA8nBcjUf9nqqqqqq3l1qwfQvMdvmVx7p5f/6VU8987uJNfrZ4t1G4rWG5hefzM76UE1qwKCn5OPQyDisGGxkwFV8Axn7EarmEAWCRqYvm/bQNQBxyitNRqDAgBFsfa8LM4jjmHPNYivZgOhBxLwcnL3rvnifIc8Lp+ZL0xcNl/8os0MWSlivZs/GVayXd8v4ruzj7jZ/V3bL3lcV3U0MfIyIupSCnjJynbQsxRxgRZos52sUc4gVmU9KvqUKYMZ91IJ6DYKWkvOxfvFALtqqqquoNVQu234EXHvzUbLZ55vTyycXNzobv8MG9Z4h6c4rDGRbamc38jJ14z8yZCo/LHnHoEeMIAyA+wDXN5WDapm2Rc0ZOCcICFwLEO5gqTBXOO2PmQoSBYEsnsldyOZ9yPpc0Py/BPyNwz7Hnl7zDOcd2MSW3NObhYPNiutI7Z78d293ll75cbm88/UlT3KBakHOG5gwr0+7SUgqyZsgo8G0LcEZJCSmm6TMggmOGCKOUYsVSuhD3agZbVVVV9YaqBdtvw2zXnXsEJ4ZxvDWO43ulz++jLr0bhGtccDuB0aUxun7Vs4hQ5z1ABGYFrTcBqOmUlr8eDAAAHzx845FjQhwjxnGEeA8wwYqChM05rzDdL6k8a1ae0oKnQXjct82jRfUcAxe71O0fntgbt5+YJdz5l0v3Ni3Ofr2LT+5uP33+8CONhv/IoJ9IUb1pmTLXRKAweBG4WYtcMoh5PcCRkGLEarlCjiMcMZoQ0DYeRGRMTrtmXgu2qqqq6g1VC7bfxD333C0f//D7Tq9e2PjgbEu/r1y4+L6Dw8ObseqPA5iVVBxgHJqOSBz61RJxjPBthqyXh4vzWGxsYhxHDMMK47CCeA/fdlMsBwDnHLRMxZ14N+V+EYOFUEqhOI6BDJumeibG2HWz2VXdbP4un5u9cRgOE+KBe1FWz8wPl80v/9/GV77YHKqV0Tt/WHQcDpb9MN/YHromlMUYe2xtlqee2LflOegdN71g+MAPKvArBnxzK57erOy+XXfu5Mtte9XNZ5zJ+9KYv3+2wMeQy80AZmpKfLmALiAoJHhI8GhgMExDCFM4LsMHD9ICqE0L4Q1gZkgjNu82a8FWVVVVvaFqwfYN2ON/rzmgww/qQD9KnX4fATd4cVuOpUlpZFsamU15Z4EIzAIGQ3NG7nsUERAzxE3Hm84p2naGotNUIhHQDwMcMaSbIYQAFoEQA2WqBYgIJSXK4zjzIVzjnDtthkJAsaKJmAqYS1LN7HxpRTITspqOLoSBmHodeXDsRwFGCU0a22awXNLxU9zPZocH5+hYah59dE/AhwX/aVx+rd03jygsSZyu0mApxqT92I8CxFXOxfVjksDj7OSJcv7cJX0pfj1uPodyx913KPCI4cfvNfqGYbnfnvvuu9OdPHkn33EK4SCGJo/j3JQWvptvgemqC2O8IcVTt7pYbnKNu5E9n/ZdO9eYHJHAOcDKtPDdQMilYOx7+FLgGw/AsFqtkLRgvlhgvrkJzOZAKYAqLCeoGUiNjvr7s6qqqnr7qQXbr2N2t1x6Ot3Oif6Eqf1gifEYAC9OaLaxQcMwoOQE7wNC0yDHiJgSvAjETdsGqChc8FDGdLk9ZzRtC9d4iA9TblrOMHEwLRA33V8D1gvGS4YWIy0KZiECBR8acz5AS0YcR8AJnLBRCGAmsHOmMULNjImNAC1ZTRwre1nvY3/1Kr2pQjMrFSuaTDixSIHjRIqccywpIgu7nFWLqS6L0SqYjejavpvNlygYG6F4Kl/Vu+v98MpXSiJ9d7Lf/5fjxT/kB9OSs0pmz8Wyjp3jEU0wS1lznyxbMdeKOTjknHE4LGlcjQxiUkvSdTPpukXHjbQ55ialOIel7sJBtxGc21LNx0vWkxLTDnu/ANNWaNsFi2tKUT8Oo+QYwVjvhcgFmhNyJjAz1AwpRcAMwoSYE8ZxmKZ1MRXM4gQkjLye1mUWCAg7O0f9XVpVVVW93dSC7dd56KGd5qad8XvJue+hnI/HmLypkjiHdjGHEuFwbwCVDCkOaRihWrDY2JgGBkoBYLCi60R9BxaFmU3/wRdBjhFWFAUFJWe4JkCCg2aFqkLVkNN06X3qyDHARELTTss49DACQtOQc366QF8KIAJmQE2hebovZ6YoMSERg5nt1cw2IkbJCVq8sQQQkZXp97E0DACZsQ/QHK3kokykMFhoG+U2qKkVAlu7mBcRZ0SiJCgKy6pIwj4LLJeYSykxR2qzV6iBDIFVFKbZkMWQzJBiJkUh7z13oWXvglOGZ7UQgg9GaNIY/bjsuUh2asWD2RlIFGDvPcE5AhGlGNH3PfrlAYJr0HUdbD1soEXhvYP3Hs67aTLXCTSOIGI0zbTSa7m3j8Z5OJmKO2aeCjgAl/a72mWrqqqq3lC1YPuNEqv9vJH+fTW8o5R80gzHhLBBxBsgbALYSGlcMKhRUyEmLqUwuWlVFBGhlAwDQbwHOwfVAs0F2SJyTMg5A0QgcWB208YkVZgBsGkRuRFBVWE29cRSUcQYkVICC0//nJyxWi7hvKCdzQCidT5bQs4Jqgp2HhICNGeytL47FwI0M0BMBpqKSOBy3luMCT4UsJuyyUwNRIBvAsg503FE6FoAAAFgIlgpIBLzIRgLmw0RYxwtpggYGYjgvDPn/VSYrotYVoMTgamDiKO27SAiVFTBxOSbljh4YhANQ49SMsR5akKAazzIOcAUWgpymf4OGFJKgDFm8zlc1yAPhJIHFFU0ThC6DuwFpRS40aFtOzRtg2EcEIcB1HQw7yAicE0DJiYwM7+8Vwu2qqqq6g1VC7Zf54Mf/HSyh3cfvtjJ15xFXyj6bN6LwyzGsqGGTSPa1KKbRrYlzh0ntWMxpZPGOOZ82HbOzQyYDeMwE/azbjFvFSY6RtKUyfLU5Vpvmpo6YYPh1Tv/tt5bORU2BVoUWhRxHND3SwgLmnYG5z1W/QH6YYUWLVoAICDnjLS+PC9NAzfrwCEgLVcY+hWEGU2Yvi7OwQBYySAmsBNIdhAuEBH4rgFA0JxBNP0Dcj8QMcE3YZp6LQriqfunBsK6K6ilIMY03RlTBcYRqjrFlzgHAmBEcKZo2w7Oe7A4vPok2E0dMBKGlHXAMICpkCPQOuAWZihpCsLNeYrraEKDza1tEDF824DMpm4oDJ4FvmngmnC5kHZOIG7qvOHVKA+aVleZTa+TRQARWYX1D1RVVVXVG6QWbN8AvWc3Aoi/9mtmRvff/+Ny8iTzvrwo81XLpzonPY0+jTPfB20apNBqaRWyZWbbcRiP+UDHlHnHO2ynLNsl5x1mPt407fGU88aq7z1gXlgcmLwwezPjaUf5+lh1vfOSiCA8hew2sw5M0wCDE4bz0+ACiBBCAMwQ2g5uPrt8VKumKLkg6QgRQde1YBao6VTUsAAwJCKIyDS1KoKUMnLOCN5DU0TKBaFpwH46IiQhMBNIGBoj4jCt0Eo5gUWm1+oc0jBg6Hs0ZgCaKRNNdbqD5z26rp3CbMcRnAmuaabdqanASgZgU7cPQE55KtIkgRiIw4iUE0DT0WVoW7SzOYBpvVceRhRdH00Lg72DmWHsB6RxnF6DCMwUIXg0br1NYhgxjj2YAJvNufVenN+oBVtVVVX1hqoF2zeJiAxA/u1+mu3u0kM//HU5fvyMdNFkmaK7miHLsfdBrE3Gs6I080SzZDq3lDZLcNsotq2qJ4vjUwzbZnELEM2K6oKJN0qMW1qKD6GRpmmEwaxFmZipbWdougYkAivTEEPLHcKsg2vCVHD1AwjTXtKcabpcTwQwgdmDvQBqiP00VGGw9Xqm6RjWVGGw9Q7O6ceDehAzVBUlZagBqobD/QMABhFB27VouhYAwVJCTAk5ZRiAOEakHOF9QNt1ICfQIWEY+mlYYBxhIDRNg+AEzgnYezDRtAd0+l0BI+SUEMcRPkzhxCwezALTtL6rVyDCMO9ATACmRe/DqsdqeYgQAtpZN72foiDC9L5yRkoJ4gQgogwK4q0WbFVVVdUbqhZsry2j3V3DFGvxGxaEm4Fa7BLwKD300A5vHTR8oSzlenhx8yADZc8leUcc1IW5MGaFeWGFN2IedlDKMefdMQK2S4nHrNgJcbIlIjNmmZecZ1pyl7U0AnbMzAQiVgVUaTpmdSCajhXHGNE0zVScFAJUoSVDS5lyOWw6og3NVAw6IuRxajzGYQCts8nGcUApinYxR+g65JiwWh5CRBC8B6lCbf0A8OoEpscUpjHdjcs5oywLUk7IKcGJAxOjpIihZHA3m/LtzC7n1nEIePU+XGgaiLipCDVD7HvkUiAwCDOIaSrmRC530l6962Y2FaIsbrpvWKZOZIlTlEfTNgizOULXkSr8/sEr9d+bqqqq6g1V/8PzBpqCaXdfDactv9XPNdtl3A9+5CTzHZvgl/ZVljhw6I47sRjm0fss1DpxG1l1y7LtFM7bVmwHqsfBfMwMxxl6nMAnmHkzm84cc1AzP/Qrn3N2PF1MgxFBHIOZ4IMHaD1A4R1sXfBQUZjIFCqbaV3olcsdMxFBO59Pd9NUkeKwDqNNsPXkq5ohiMB5B2D9NVWk1erVYQI47+HWXTe8uqbLeZgZ4jDCDBA3Hde++mC99xBx06qplJDXU7Lee0gbQCAQ0+VfxyIgInSzGZy4KSzXe6xjcqejYpo2IzTdbBroAFjT2KSVNUf9vVRVVVW9vdSC7U2KaFeB3zqAdmpY3c145A7B1iV55Wnv/HwmUsQdaOqaY74rGXMrZUGCHc58zDl/MqVyquR0NSlOaMnbatgyYFukmYvzIZiJsgiYqeQMVQM7hqzvuDkn8CG8Gu6L1lqwkylrznvQYg7AkPoGzjkYMYomsDj4poE4h5KmTpqWgqKKUgqKFgh5zGYLuKaZumHANOxAhLIuDIloGj7IGaZTty2miHGYdrI75xCCByjAObc+ti1gYYibpnZJBGQGMIOdm2JW0vpOnXeQMMWlFCg4OJgp0hg5pji3NnVH/f1RVVVVvb3Ugu0tbOrY3Vumv/5tBhBsl4DbCQ/9K8bxM3IuwxWfnB5mr6W0TmRhZJuabcscHQPjKi12dTY9iWxXkdnJlPP2GOOi7boNmUlHwswmPMWXAAxAvIML09Sl2bSP08zgQ5gWqmM68mRh+KaBmSGPI2CKZn3HjgB4CWi6Fu18DnEeaRwQY5piNbyfhiCChwHQYYTlAvMetr7TlmKEcw4ueDRtC2JaDygkQA28jgApRUHrAYRxmIYOAEzZeQA8T904EkbgKdA4xYgcR4bp7LrtjdlRf/ZVVVXV20st2K5Q0wamf+v49TfeqdvdZXwM/ER/XuanjkvpxPsDDZBVs3KztqW8ncd8qpR8EqCrieQ0s15NbFeb6tUAbzNz67y04r2bCrGIMozIKcM5B3ayDg3mqVBbd8UMmO6UeQ8eRqgpnJ8KLRGe7pjl6XgTplNmHROmFQ7TXTu3DiKG8FQY0jSt6poGEvx0PBrTtFUCBlNDigmxH0A2RZEM/YCcIpyfCrOSpvy6tptiRkAES9OvVy2UU5qlkRdH/flWVVVVby+1YHsbo93Lx66/YfrVDHT//XfKDbjBLTFzWyd3PDwHb7pR1DYTlW0yd7UEuYbF3QjT60rMV5WUThjRybZtGjC7/OrRpxmcc1MlqVNXznctiBlIaQq/FcE4jIjDNO356pCCFgNonbOWEooavHPws2kYYsqE81O0CDMkuGmoYjUgxRHCDHbThol+1WNYLRFCM+1wxZTv5kODXAqGfjV1B31Y32lbU0McRwzj2KWUZ2Ygol/94aqqqqp6PdWCrfqGpmLk/oxfV8yZ4WXgbn7kkTvkRNnzV524xi9Tat2A+ajxpFo5GVy4Tpx/p0JvGpbL61JKJ0Jotn0TWgLEghE5B+fc5Sw2YgaJIMUIzQnE9Gs6dIDRNMigNsWFEDNizshmcDINRYgIODgQMVIcEYceOWe42QwiDjkmDP0KKSc0XTcNHjBNQw5NA4oJbZ7u33nvQSAIYYogyRlpjATTZt51HX58l37NAElVVVVVva5qwVb9jvy6e3OXw4UNoMZ2n37qKbjNPAZpu84O+g0r+ZQS3SiNvx5EtzLhVnb+WnKyBcPMVIPqrwYEN20DLX4Kt2Wa1l8Ro6wHFNr5DM4HlJgwDgPUDF4EoZny13LKMJv2sNqrE7DreBJiRjubI2CGtu1AZih5GjwgEYSWL3cBCYDmacK15IKcM/kQaNY0rQ/t5kM//KSs41uqqqqq6nVXC7bqNbG+M/dqR24AsG+Glx9/6FNPX+s3vyi9b8qMNkX4BBuuKdluMtN3l1xuzUWvY6aT3rsZO+dMC2jddRMfpgnRXEAiED9Np9J6o0PO01GpAQAz8jgixgi3XvDORJcz13zTwM86mBpgijyM61+vMDWIMJjXGx6YkYigMSGXDHGCWTuHb5o2qp2Kce7wDe4FVlVVVdXroRZs1etm6sZ9OmEqbJZmuIj7d5/Dnf3DuNg2q5dpnlhPgfQaduFW8f52VXsXil5bSj5Ris6QijjvCLBp/ygB4zCg5IymbeG9g5YC9n7aLy8MYFo5Fbyf1nTRlKtGPBV6um6MmRn6vkfJCd55sAja2QwztwAzQ8RdXo/lnMB5TyBqxezUdVtbHYD+qJ9xVVVV9fZQC7bqDbMODv61Xbg9s7tfeur++aOzd9z2QLK8wUVPas43x5RuXi0P36lmt3Wz+fVNaLaJ0cU+8jhGiLgpVy0EEDGsZOQ0XbdzzmHoe5gWhLa5fFdu7HukGFFyhnMeKUWslodT+G8IIDIUU7AIfHAwA9IwIsYRoAaUMwgamOXEfKddALhw1M+0qqqqenuoBVt1pIjuLZhiR14t4F588aE7HkshNuP+3qZ6XNUEd6N6/25kvWMY+lsUfE23aDfFh8AixOKgMCBnwAwiDm3bwgzTFKkIdBwR+x79colh6OH8tKYKNnXbQtNCRJBLweHBPhofYFh34LRAnIOYocQsRGWHhTeO+tlVVVVVbx+1YKveVKYC7t4VgJUBl+6/787nj2V5OKn7V0WarRLLtTLzN/rgvsNUby+x3KicTwnxQpxzKY4MEELTACAwTflvWN95MzVADaoFwoK2nUHN0C020M46xL5HGgaUUqBmgAhC0yB0HVgYOSYGsEPAzlE/q6qqqurtoxZs1ZsWAYa77s/reJGVGS7goU89+wg2fvGYo39upeykXm9kb7dB/G2qenuM+QYz22m7pnU+OGKe7rgRgdcL4hk0bUXwbor3cA7NfAHfBDgmRCIUVUAYM+/gQrh8rCoEYSfHTNzp3V3wbp0Uraqqqt4AdNQvoKq+Vbu7u/xX/8Nj/pK70GnZ2FAfT42H/U0Ge89ssbi9m3XvzCmfTmPc8I4bGDjHhGG1QhxGOO/RbW4gzLqpE1cKcoooKYFEpp2ibrorBzMMfW+lFPM+vACyn7yUh//mxht3h6N+DlVVVdWVrxZs1RXBALr/vl1518YL4QAbs6tPnznmF811/aXD20rKH2rb9gNW8k394aodhwEpZ/gQsNjagAsNSs5AKevJCJuKNGaYKsQJSBhxGGAgEyfnxxj/23a5+snt9/6ti0f93quqqqorXz0Sra4I0/Hp5QnUle3uXsBf9U+ffXH4Wqu0Usmnzez6otPdtKZtEZpm2i86DFBVOO8gIUBLQRzHy0vs266Baxqwc2AmAGjI9JoX8sEmgFqwVVVVVa+7WrBVV6Y/eVN37qmzt7pSPhpT/EGyfLsPrhUnCNyg6WYIbXN5m4EPAb5rwM4jxoh0eIjl4T4AgITgmvWiehZAiw8+nFnMj50C8PRRv9WqqqrqylcLtuqKYs/+3W5s23cMdPBhx/bxkegDILo2lzx35ti3DRrv4XwAEaBW4ODgvIeIAERgJ2DnkNVQckJXCogZznsQMUo2D5ETTdddY7b7ENFuHTyoqqqqXle1YKuuCPbgp/zFMzddHWeL72bID5qlDzkvNzRNWCB4xwC54CFNmJa+r/eTllLAzCDhaffoeo2VEeCbFr5pLk+SmhlMM5WcqaS8JWzvABAwZchVVVVV1eumFmzVW5rZLp97anbqguj7vDQfh9lHiekWQLadD76ZgWAA2bS2iohQioJIkXNGzgXOEXJRkGUQE+IwYFj1cN5jvrlA2zZg5nXBpiilEAgbxHLTkw+vtlALtqqqqup1Vgu26i3JbJf3n8P2xSfDHU3j7mJH3y/i322EE2bqTZUBAGooOYOIIJgWG5hNGWtaClR1/dMMuh4yeHVXqVt34mA2LZBXRU7TvvcQwqwA15VSTgB46aifR1VVVXVlqwVb9Zbz8sO7ixe+FN/pQriznfGdwngvsZwyaFM0s0JB66PNEhNySnDeATItjycWsAiKUzgYxDuQOOQ4Yux7mBratgWIkPoBoh7mBDklLFcriBBm3cKxc6ePHdu+xu65+1H65L3lqJ9LVVVVdeWqBVv1lvHss3+36yTdMF46/EiJ+nFP9AECX21qM9PCakZqCShTR4wBiAiIp6NQmMHUID6AnUdggakH05S5BjOoSzBmeO9gRiglw2BQVagackpIUSHOyyy4qxzCrc99+NrPAuiP+vlUVVVVV65asFVveg8/vBtOLOSaYOP3cpKPR8N3p5Kud8ktonNCWkgTprtpIrBSUFJECAFt10GIoDlD8zTtCRhgBiYCxF0+8iQAzjmAgNA0YJJ1waZToC4xum4GNSUfGoj47YJ0U47jFmrBVlVVVb2OasFWvWmZ3S2vPP2dp8T5D5DaJwB8X07p5pzTppbkYhKS4OF8O+Wp5QQhWt83M2jRqUgjIOcCsqlQS+MIHQYwMVgYxARTRRoTcslTscYCZpm6awbYegOCOIGXMB2xamljSjf5uHEVgLNH/byqqqqqK1ct2Ko3HTPQ84/+xWNnH5XvCBvu421ofkBV35lTOmZQ78QxdzP40MA3DSR4gAFiTB00AowLTBW6HjiAKsAMVUWKESllAIYQAnxwIKyHEHKGOQ8rZSr8coGawjAVeilGAJjuxTkf1PINFt1N99135yN33XV/PupnV1VVVV2ZasFWvWmYGT3x8//njed+ZfvdaviYtP5jDPkOEE6A0ICIWITargN0HdPBBC1lmgIVgZpCzcBMUwfNuylnLeUploMI4jxo/XNFpgGEKYuNQJEAGEwLimEq+gDEcUCMEbaeKi0lg5iEmU9224t3vm/2oTlw/95RP8OqqqrqylQLtupN4dl/82e7J77wf70JLnysaPl40zTfGUJzGrA2jpGZiVgYzA00JaR+hOUEzg7wAgkeBiAOESCaOmdOpu0EIJSYkMYIMEPWmw4Ag/BU6BGmY1EiAgAY07rLZiilYLVaATC0bQfnBERM4hxYeMN59+6Mq04CqAVbVVVV9bqoBVt1pOzh3fDwS+evuzQefDjQ7Afbpv2gb5rrmq6b+xA4jiNpKfDOwXkHIkLOBeMwZdW2M4ETgXMehRmuFICmooyZzMzMVFPJmVKKnkAEM4A8oAUm0+/56vGpgSBOYGbIOQNqGMcBOUU0bYuma+FEAABTwSYtQLeYH24wu/tJohrvUVVVVb32asFWHQm7b9f1121cfb70H2wX/uPDkL7Pe3/jfD5fhHnnmIWIAO8cCjDdSzObul5lqonEObgmwK/XTTEz0JHFmFS1rFTpHNie05xfMpg48TcS41bnZAEDclawZRQG0hgRxxHiBKFtwSKAAarTXbjQNOhms/WwwRTzgVJQVB0Tzhj0PU88cccvAPfuH/Wzraqqqq48tWCr3lBmoIMXf/r4MvXfpYg/qCl/1Dt3q8wXW23bemYmNsL0vylHTXOG5fVWAgXMAOcDQhOmo01iTJ20kuMwXBqG4Ulx/ouuDb+oeXxcV+Vl9mLm9Ebn3O93zv1eMO+AAGbCVA0qzAyaC/IwFW4kAnEOLRMgjKbtAJrus6WUYAAxM5jpuBne2yCfBFALtqqqquo1Vwu26g1jF356K1+w272LH89WPpb6fDtAx5umCSBi5wRQhWpZ3xMj5DFiXK6QUkbwHs57wAAhhjg3Nd5SGrPhnEEfzWP8fL/sv9B1+phJObedz/e47ew0vfnch792WJZPQPOBgv+YeJmZKkrOYOfQOYcyJvSrFbJmNN0MbddCnEC8gzgHtYJiBSnH6SgVDAO3wflb5jK/9b77dp++667dOi1aVVVVvaZqwVa97sz+8Txdiu8ckb6/xPTxEuP7chxPlZgbENiHQCwMEQY7B3EeTARbbxYYhxGlFBCm2A4RNrCZqfaa04sG+zLBPleMfn4s8WuvjPnSh98/H4l29d9+Jff2dt/u45duKP8IaN8jbB/LKbEWhW8bCPPlTt44jmBxCE0AuykqBAQQpuw2HwKcCIwAmDpmuZ69+86bju/9PICLR/3Mq6qqqitLLdiq1409/veag22+/uDCwfc7oY9z0ffnEq8dV8tOU2JhoSk8jUAigAg4NCAm5JShZdow0DQBRRXeefPBm3OuL7k8X0r5RRrLZ63xv8D98NROwv7OHX8j3UCw3+w10V27+YUXdr+6Qf6fgOx9xHyMaRo0YGJI8JgtFnBti9CE6T6bYxDLq78DQtshhCkSRFXJVMEsOyLyHcdPbV8L4BLwm7+GqqqqqvqdqgVb9Zqz+3bdpXctrjmXVh8KxX1CC38PCl8vRBvEJAqQGuCYLmepmSqUCUQEVUMcBmhRiDCa2cy0FANhyc49D3EPOdD9msqDSPmZJ/aeOfjgBz+dvtnX9+lPY/gLf0b+VYb+Ad+EO02VzAxZM4gJzaxDJ+5XtyBgCs7NOUFEEMQBQiAwjKcfMy1NSen2nPFusx/7KtHfH4/6c6iqqqquHHTUL6C6cpjdI8tz506WcXj/Ki4/UYp+xLfNzSE0W8LsSI1SjIgxAkkRmOBCAHkHMwM5gWtamCriagUimDinRLRU1WdN8RBMf5aBL8zn5Wl8cVjSt3hfzOynZ8tzhz9GRH8lpjgf+wE5Rog4zOdz+KaZ9oeaIuepkCNmMDOEBQBPK0mnYs5yHC32w7lY9GdSsr/3jtt2Xzjqz6Oqqqq6ctQOW/WauPT039wZXjn7XjN8XFG+X2N+d9a847zzzMwiDjH1ODw4wLhawbEDtS1818GFAIAAnn4vJjLfNkbAipifYeBBVfpMGfODcZ6e3vk2CrXL7v1Xo37sA5+FyrNFy20pR6xWh3AsaIKf4jtgKKowACwOQgKQwcwA6DSAUMp0LJozjGgzBP8+cXqT7e6epd1ff4euqqqqqr41tWCrvi127r/bSH58ZxzHu+I43qlG74HhVNO2jTNlF1o459abBAwlJvTLQ3gXMOtmIO9BzsNUYZphalZyWWpJzxHJgy7wfcXowRTsme2Te4cLem0mMOmT95b9/e96DKP7gqPwLt9k8jGCQcB62wGIQCzTMAS7KU/EDEaAmSLlfHn/KNQgIkGc3OyMvvPrP3zwZezWzQdVVVXVa6MWbNW35IUHd2cbN27eOFL8qGb7BJN8l4qdFtWOxbMzR6o6DRPodP+r5AznPRaLTYSmQbOxgHgPIjKDGYFWOeXn+9XyIdNyX/D+FzSlZ7ZvwCG9RoXar/XMM/Hghmu6fy3KP1p8WnSzObxz8E0DWm9AePUGm1oGX06HYwAGFgesl8bnkohhbGanFPie2db8PjPsE9Xhg6qqqurbVwu26nfkwZ/+lG9uwTtkiz/M8B83zd+Tc76OCTPnnABEZoYyjsjjCAMgwUPNYKpo2hZt2yK0LVzbQVVtHPpBiJ8jkYfU9DM5Ln8+xuHJqy5tH37bR5+/hTvuQF5epF8006dA9J6madCEBiIOl6dXTUEwGAAiBmG6v0akMBCUpqEDCINFYGYzTeV2x/zez3/+7q8D9/ZH/ZlVVVVVb321YKu+KWb3yLOP/cpVBy+f+244/kFj/jAT3WCmm1qyMxBxmIYH0jAgrQZoSTBiqNmUnzYF3aLkDFW1HMcxx/hCifGXXPD/mqn5BTL++pnb232iv/W6h88S7apd+pvPXhrxCwx6jwQ/raQiWo/jTM0xAkHWQWzTjbYCmCHGAXHsARCapkEIDbQUyZKvM9j33Hj1HZ8H7n3qqD+7qqqq6q2vFmzVb8lslw8Orj62fOG57wxEH3ez9vudc7c2odlhYqcEBhE0Z6QRKCljXPWgovCNB3k3TVvydIyYcraS8mjAWYvxiynm+03xb2LZ/9o1r9ywTx/8E990PMdr4rlxqZv+cyR0d8l5Q1OejkKZASaoKqwoWBggRkwjcspgEZSSkFNGN5vBhwZMjtgxmGVToR8w5ffcc8/dL3zyk/fGo/4cq6qqqre2WrBVv6nz53c3D57fugPu8M5Y0p1EuKNrmhOhbb0PnlUNpRTkmFBiAtkILQpNCUwEdh6+a9Z3wGB5jKmovUwsX7KCz/QlPzCuDr76Thzs0e8gR+019T8i0x8uXygFz6eUboMqxDmICEBAylM3zXkPI2C1WmF5eAAwo2tbNG0LcdO/RvbqdTWmQMY3N1340Pd+7x2/CNxbIz6qqqqqb0vNYat+gxce3J2VBW5pHH2MyX/cCO9FKVepakdEHNoGfh3FMfQ9VodLkBmcTAeHqgpTRbdYwHeN5VxyMT0P8CNGdJ+QPXBpb/XYErjwnvfsHnn3af8rf+N4bPN/k1P6URjggoc4gZkh5wJgWkJvMMRSsDzYR9/3mM3n2NzcgvMOxNO6KiYGTdkfAxF9Lpf0t77w5YPP1P2iVVVV1bejdtiqyx5//MeajhfXU8T3cUo/2Kfyfu/LtSE0HXkRUSFxAuc9CAQtBZYyNEYQM8gJRASkDCIyZs4pposppUdB9LPcNZ/pQvsrs0t2fvuOPx/fLBOUG7ddvTx88eWHQuN/RBVOvAMzw8wQbOqbaS4opuhCgHcObddDnIMLfor6UAVUQfJqJIiFouWdVuwjt18fHwbw0lG/z6qqquqtqxZsFczukQtf++IZkHyPmvyQin1vMbrewS1804pvW2IioJQp6d8FTDH/Nq1qWnfbnA9gYRODknN7YP6qpvxAUdwX2H9pPy5fOX7mz410kt4UhdqrHnro8+Vd197yhBVEFnJ8eUXWtMuUiMAOYAiIGc4JQtvADAAMqgomQinrIN1SyABWtROq+Xsj2Wfuu+/O83fddX/tslVVVVXfklqwvY2Z7fJLX8OJC0997f3i5p8oWj5CoFtEbFu8dxI8haYFs4OVvM5SMxCmi/klJlgp01GgiLGIgemAwF+XRj6rhPulyC/bwfDSsQ/8+f44kQG7R/22f8MzePHr/dV7ly69M3ivIgItUzhuShFmBOc9RPjyXTWYgZlRiiKOI1QVzk3BukUVKUaUkmGGRpy7rfGzj1533Z1fAe5/+ajfb1VVVfXWVO+wvQ2ZgZ758p/a7ppjd/imvUvE3UnC7zbFsaLFg5mdD3DOgVkAA3KKiH0PzQXeBzAzhuUKcRhMQjBxssyqT7Hwz3ez+b9S736x1/jCVVcte6I314qms2f/9txn3QlCNyvxd+Y4fGiM8QNiuImYnfMOJIKcEsAMdh4oZTr2ZcDUQOv7euMwoKyX1L96jJpSRM4ZZmZt0y5D2z2wWg0/+fiz9322dtmqqqqqb0XtsL3NnP3in5s/9QjdCqM7lfwnSPx7wXySmBpxwmweIAbRlJdmVNaJ/wpihlmGWoGpYYyDDeOqb4SeB5VfiMP4r5yjn19sHX8WJ+/vt+jectTv1+65W87dfrLD1nWnG/E3u9bdUFK6JUFvjwnvYNbjIm6zm7mGQGKqlxe9NzINExgAJYKZQVOBWoGAwOLQTOG/ABGEBUSM0CTkXFByBoM6y/l2KukH5vjurwD3nz3qZ1JVVVW99dQO29vE44//vWZb9m5wzn1kHMZPqOkH2q67Rpzr8hiZhahpO7BM4bdxHBD7AczTkSA7BzOsIzvYVPN46dLe2WHoH2y62b8OLvzc0L/09Zs+cPGQjrBQMzN68as/fpw7nA5+9o6ientJ+Tbn3XVt251houM5xfZg/6DLKfrgG26aQOI8SKblUylHlFzQNM3UXTMF1gVsHKboEnEC5zx4Wu4AEQETw8BQzcglIfYjTItBaTDY5xP0b+/9Iv71ez559JOxVVVV1VtL7bBd4e67b9e9w1044w6f/VBpux9qvPvQbNZeV2AbLE6GvqdhOUVUTKMEilIKhlWPw4N9+BAwXyxARUHM5p2LpejLKaUvAvk+KuWzq2X82vlw8eCDR5ClZrbbvvyy31qEjTNO+dbDc//lO10rt5SsN1Ogq5rQbJr3MyfiidkDYAUopYTl/gHabjqhlKLwTQMWQUoFeRwhxCA1EBHECUpRqK7nJQzTMnhVsEydNTVDHFdI44CYC4QIYZoibXLKt7HaJ+TW8VEATx3190VVVVX11lILtiuUmdHZX/pLJ1I6eH8m+aFS8keLlluMeFOcODKjlDKGwyVKLr+anwadjv5UkXOBTEunTHPJVvQCCz9sjPuS2meSpscOmnzpgx/8r96QQs0M9MQTPxa2/elj7NsbOadbzn5tvMU7us243KCOjqHoFgytFxe8886JJ4Ot17gb1HRaMkVTtprzHuLctMkAhpITcoyATQvrkQuICSUxcpmGLVzwEHGQ9dEpMQM2DSCsDg4xxmmH6mK+gG9aIlOA6Bhy+ejmBj/4v/6vn3r5R37k06uj/h6pqqqq3jpqwXYFuvhL/5ftl3/5L90BTR8PrfuYN7p9LOlYKcXnlJhluhxvqmBmwBFyysDQwzk/xViIoO1aExE104vi6Csg+oyAPkONf+TqS/15ev/fT8Drm6V29uyfmw+X2lNd664+ONu969Tc3qMFN0DTtWMeT5vpnJx0zC4QiFWNiQXsA5j99OLMQOvDf8G0JqtrWggYoWvhQwNx05FmTHE9bDE9Ay26no4tMNP1RGwLH8L6bt+0XzTFATlGsDDatgM7h3Y2g/MODICYg43jzVbKD952zfEvA/jK6/3sqqqqqitHLdiuIF/8F394vrG9fUsPf2dOwycY9B2Na65yITSUhFbDQMvDQxgBIQSId2hnc6SUULSACoOQYQYzVRXxe+L4CTA+a0r/ul3Il17aX75yw5ndkU6+9sWG3XO3XPzATYuldSc7312f8viudD69Sxg3OWmvc6HZMdCmFQ3jauXHcXTiPEkIIHZQGNg5BJH1xKYipQioTvfweLqjJixwIUy5ablAOMOJQJzAw0PWd9IMhDgMMDMQEwCB6rpLRwARAUYwU2iZsti6WQfiabWViMBMUVRJVZHHuJ21fK9rmh+4555PPf/JT35676i/Z6qqqqq3hlqwXQHswZ/2vxIfvb6R8hFx/odK1g+a6jUmrgOIyTlqgkdSwzD24N7B+2bKDlNMoyfM8KExaLEc4yGAJ4X5cyzuf0+qv5TkwtmdY393uPH4axd6a7brLl3aWjgtZ4a9/pZnDlY3pYPyrsVC3umCnALCMQo0Zy/BhyYAwgQjBVBseuHiBESGXCLMpvtkDgK1ghhHlJTALBBnMDOAADWDloKY0pSttm6/qU5Hw7KOM8mlwMzgvINzHmaAmV0OyZ2GEQggWxdoDJZppRWvBxjUDKoFJSWkPDomfsfmxuyHPvY9N3zxnnvu/oVPfvLoJ2mrqqqqN79asL2Fmd0jz331i6efHb/23Rve/5CT+Ycd0w1pGDeYSSQ0RH6afmRxEO/BOYFEYExQAEaAeA/fNCbsVgZ9GqCfh9G/tth/ocyXz1111aIn+ikFfurbfL0/7c899eJxwJ/2Tbhxdc6/O7C8K496jRldC6ZtKtyBuRUXhNkxOyFZd7+KZpSYppVYALrZtHg9xwjVKcyW3ZQbh1JgBjjn18G3crklaKpT2K33aLoZQtuCafo6iMAsMFVYKSBhNG66s2ZqyCVDS0EqOh21MsF5B3ECAFNg7vq1gAmAoWjBEAdKOZpzfs6i39V690Pvec+tTwF48ai/j6qqqqo3vxrr8RZktssHz4edWNJ39gerHxyH8ftnbXtr2862AfIohYgZ7D3IOxBPd9RWyxXAQLexAEAoKZkXMeebgUVeIMYXYOV/z1F/Lh7uP3vslkeW305Eh539x/Pejzuw8RpCvjWn9M4xpZuhuFFETvombBowS6vRqxWnZmxm1LQdfNNAzZDiCF5f9C85I43TESU7B+89zBRxjFOh5dxUjOUMZkZoGsj6Tl7RAhhNAwQxYhx6kAh8CBCeFrszM5gdaL1uSksGiOCcAJhWVcUYoTmv/8WhaQOCdwAZcspIMU1HqyHA1oHDOSfEmDDGAd5567r54Lw8mJT/Xub+n505s1sHEKqqqqrfUu2wvcW8/PI/WOw9e3hbyvGuFPMnSirvEZIT3nlPRDwd4fkp8JVoCsBNCeOqB8EQ2hm8c+iXK+uXy7hYLM76pvklZvvfS8bnor/05C8/isO77tr9HSXy2+4u449tNmcP96+iTm6atfMbe3fxnUxyW/7/s/fvYZddVZ0v/h1jzDnXWnvv91KXXCoXcichCfdIuAgGxAt4QcUKaquAdoOXtls9Ho/afX6+zzk/W9vuR/qndp+Gc/ocRW004Wi3tiiiJihyMwhIQhIqpEIqqUpSl/e6L2vNOcf4/THXW6CiQihIAuuTJ0/yVL2199prr11r7DHG9/vVeF6OaZ+prnjhoEBFBDFVijFSl1o451E1pdPFXGKeYtuiXSzgdwPnuRRlIgKW4n+mMfedteIfl2ICM8H7ANd7yrXtAovFAkyM4It5iVkJdIe2SAQwM1yoQMjFCLcEhYKJQMR9HBXBCSOBASu/55yDEdC1Xdl3Q1Gfas7IMaFLHZgF9WSMSpuiTmWphOUqcfx1qfUfA3A7ynB6YGBgYGDg0zIUbE8Q7NAvVtvj7hKR9OUt46sWi/mzYtcdEOEmBM/E5a0kIkDK7lTOCRo7LBZzqBlG4wlC5S13MS7ms+Nd234kjce3kNK7qorvxp4Tm2P6zAu1u97140tdtX2WM77g2F59ytjpNfXy6KLUxSfl2O2HuTFzrruu9bHrWES4jBszCAxS6pWqBBcciICUElgAGNDO58g5I3gHwCBEQC8e0KyIXYscOxCVWCjqu2G7AoKYEmLbYr6YI8WIqqrKnycCmSHFCPYeLoQiKtAMNQUMyDnDYH2nLEOzwlBGonHRgphQVRWIGWq9klQzQgilI9d7twVXxrbeVQAMSSOV43WrmtPzOfg77vnwjz10+dP//ZAzOjAwMDDw9zIUbI9zbrnlBvekpaed92A+cb1fhK9qyK7XlC+az+fjFDtZGi+TD6H4gQmX7pN3SFnRTVvMdnagZpgsTaxuqmig9a6NH20X7a0px3fOkO4+++z1U0T/sPv+LbesuWdcjAmNRvucqy9r28VT1k+evCxu66UQvjC23d4c4hKJBCZ4zVmIiDSXiCbNqYwSmeFcH/nEAnJA6Jf6u67FdGcHLALvAlJKRQDQB6urKlLsenUmkLuuFxIoukULFj7tq5Zzxnw6Rdt2sKxw4hCch/OuCAtE+scX+OABUDnG3ijXUIpfIio7azFCVdEuWrTzOXzw8CEAAAzlz4SyC1hG0QQQCB5lDEvEAAiMkppgMAfQBczy8gPnHfjYLbe85o9e/OJfWTzW19vAwMDAwOOToWB7nGIGuuv9P7iXSZ7BdfXVsPyinOIVKcpyTsnFtiXNZRzoq3A6eBxSRqG5Ly5yjlaPJlqPx5sk/m7k/GdJ8y3TxfT28YY/cfGXrXV9ffI3uOWWG9zTnvYtowC+kNUuM6RLCbjKgCsAO1vV9nnvRzxZqZjYi3NsZsS7D0UEdtIXPQaCoV0soLmMLDUStItw4uAqXwqymJBjROoiLJQCjcyw+09KCfPZHMyMuq7hq+KFZlYeu2tTEQz4XkBgCmiZNBJTiZ4iArHAVaEUeM6dVnYiE1RzERwAcJWH9Z1KoIxAuRcZeO/Bwr3iNME5AZMDS3kM4ZI1CvDf2BQlEmTtSDUZE9Ui7lpm+carL3/GfTfddPCOQTU6MDAwMPDpGAq2xyGPPLI2OXlv9ZSz9utLoqYXC/O15GW/OOfZwLAWwQeYK5YTZdm+7wJ1tmv4aga1UDdbo8nSx4Ov/iKZ3prRfTDXSw8//Xk/vfjUQu22217n9+nKvlOL9bPrUF2ysrr3qT7rZT74C4F8gcKtGHTERDUACd4xLy0XQwxCb07Lxc6i9zvzvhRUiYp4IOcMVUOHCNUWXYpFOKBjMBerDB8CUkzo+u5Zse0ox0hcxAeWMowIoQpg6q0zTKGLFilGEICUE1LKpbNFxWaDewuPlGKvEi2dPsBA/R7c7ijZULJDM4rwwAcPqCH0e2u7cQm5t+xgIoCLMIEYAEoRSFTsRFLuYP3o1mBQTcTiQUyrgH150+Cer3vx848DNw+q0YGBgYGBv8OgEn0cYYd+qNpevfgiwL2Is34VEZ6VNB8w05qYmcURVJEWbVFLAvBVAECYTadYtDM4dlY1Y3VOpprzJ9i594SmvrUS+kAVtx/AASyANbvj1h8YLx84e3k0dhdA6cntbH75YrG4okvtRd6Hs5aWV/csLS83zgVvZi7myDlnYu5NZc3QVy3IKRbPs5whIgihgpPSXQMMXTvH1sYGFrM5nPfwvuqVm4psCuvD1Ou6gfOu2GAsWpga6qZB6Dtprh91xrYDi/QiBUJOGfP5DIvZrHiuiUPXzhFTQl01CHUFCR6hqvrduBk05VLwSbHjcN73we8RqW1hZpazmhK6KnhmJy6nxGSGMgRFKQCB3uutGOimlMprDNVp24+sivliDgKhbkZgZuSc4MQbk0BzmnUxflCVfvnY+vj3r732B3ce62txYGBgYODxxdBhexxgtsafuNOfs1G560X5a5nxPHbuIiYa52gyn07JzNCMx/DeI9QVvHdQU+SUEbsIVTVmMXZ+IUwPiHd/6Zz7U+er9+W6PTo7uaHHWz0/nqSLkf6ni5b2y5Mbx1cG8ucq21lo6mXxrqpyXTkfJDQVGUBZE9q2w3S6g5wTxktL8MzIXVl5Y2LEti3CBlVUdQ2ECmaGGIsFhqYI09LFCiGgqovdBkTQzufY2toCclFqEpfdL+cFmktnDL2gAOjVnL1ilIrjL7JFtIsWKSVUlTttYOuZUY0a1E1z+s9rPyIV54xdUGFKatoBaAGas9ApYj5hmh8hohME2lTgWRbj88xsz25sF3HplgHUK0kJMbXourYIEHIuofGhfMRsNySe0XfjBAQqwl5YA7Vr1NK3rFYn7zc7+P7PxU5lYGBgYOCLj6Fge4z54C0/vHrsEJ62suxe6iTcoGpXJU2r3nmXzWgxn2N7cwvCjCAO0vuBQbXEmRc1o4WmjiO3dJIZdzHJX5HQX0sIxzz7K9u5vWwrVpd37fYFse0uEKK9o3rPuG7qhr04NoijwLsJAOjtQFKMUDMsplNMd3YAAnzo/dHaEvkkIuW/zH0xWYOY0LYLtIs5oArvPZrxGM14DOc9nPR7Y1x2wCZWxrjMjMVsgZRTMb31viQXuGJkqzlhN93ACGi7tpgAo1iZOO9R1aV7x05MmLMPdXJCMavOLNs2TLe8cxsAbbDICSI+7jQ/osBxMjtF0G0nth2ZtnO0OQesmOYJMV/vvQexFOuRfp6smsFMp6OqiEpRGGMLZoaEUkCGKsA5B0+uFzTwrhCBRBws6DISnisTfsXmw896eG3t5vvW1garj4GBgYGBwlCwPUYcPrxWy9bO5ZbpxcH7rwq+eroCZ2tOASBWzYhdh8V8DgAIzoMMSPMWi/kcWRPquoF4Z6bRstG283yMmKdgutjVzeUC2W+me9nJShX8pJ2Rjyl734wkjEdg78nMeu8wD0cMlYScUsnATBkpRcAMdQi9kGFRukVO+sFgiXsSEYSqjEJzKuIBGCCuqCldCP0+GUqgem9wS6qomwbel3EnZ4ZAQCKo6hohFIVoShFQg/POQIQUO5vPZupDiFVTL8aT8dSYd5h5ZjlvMNNJ58PDQvywmR4H6boBWwZsEWRTrNvW3HR1bXE9zdKexZ6IA3sS8FG9+eY77ODBm/W++9aqfW75OZn0Wc7JkiPXF2oGWC52HykWgYJjOBbUTQOgV46CIMxgEnhHEC47dGR9CAIAtQQyI+c8s8i5mvVlbbSj3/iNP/mWtbWfPf5YX6cDAwMDA48PhoLtC8xNNx2Ur3/e08/tIr0gNcsvA+F678OFBmtSTBw1k3MOXVuKNSHByvJKCWsnxnw6xfbWJmKMoD2CpaaBM1COXd217fkscn7VNIFAAcQeBGE49lVNdT2CDxXGkyX4pinL8KmM7pgVxlSCyx1BU0K2DMDKCLaq0C4WyGbw3sFXVRl5xgSFQdiBiPtOmILFoQlVr8LkfoTJUM1oFwvMZrOS0ymMqqoB7+C8w4jHKLJSMe9dIkWXNLYgdEbYNuAkLJ9IKZ6MMa47x+tO+BQxH8/gE7Gz9RG7+dxSa1W1WJyatst1jMfiPB+Y7s0f2Lxdn/3s8zLR2j/YvTK7SVbH910lzn2rI3oqE/ki0SCYZeQUodrniRoAK5YqFdcgKkZyagrhkpJAzP2vl+xRMyBpB80ZwgIvHkzOZ+susxy/+ezV6uiHP/xjf/D0p//76WN9zQ4MDAwMPPYMBdsXkMOH11ZX6+Wnp2xfA+peTGRXEGMlxSgpRVIDSBgAEGOEqaKuG4RQFuJT22I+Lcv1IIIRQQFyIRh71yS12gBSA7VdR8KloMg5Q3NG3TRwVYUQ6k+O7YhKADqArBkiDkQCYgM7QVkhY+Sc4bxHcILQNFBTdG1bHreq4foIqJKQjrJDVtWQfsG+WJAAXddhZ3vLZrNZDlXIzjXRQDMz24Zhk5g3mXiTnTtJcCdB+hBAJ7LqpmPagtK0s7wj3XwndV3bTUIamSTMqoRTxxKuQQJ+2gIIn86u5DPBDHTkyEfPqYy+ScbyFcHVK8WcJEMtIeWIlCOoD4KnMg8FqHTTdlWzQlKKWCgIdFqE0fclT4fJqyoSK5U409wQ9GlNCDdeeNbeh2+/fe191177D3vkDQwMDAx88TMUbF8ADh36oSp0o8ubHL5SFF8F2DNiymflrvVKxArAeYdQVSDHMFMAdro7lbqu5FQuWszbBYwZo9EEPgTEmEDM5OuKrDd4ne7sgABUdVEqxq7sm9VVjdBbbWi/JyZVgGMHM+09xzJyF0uEknPQZFjMZogxwTmH0NTFdLYrJrOuz/QsatEEwCwnAzvOBGtV88KyTiE0BbCTVTdSSifN8LCIPCLOnXSONkyxBegGGba5sm2N6EZViEgcqzRLx7suX3zxgYxbjxlugBL9zD/QIVv7nN6vu+/+8cnExa9aKL7R+Xiel8CEkmgQuwViTKVrCIISfzJY3oqBbs5FL+B2fdjMkK0UrFQ2D0FgOKmhFJFTh9i2QGm/wXm/IiwvSKaP7N/frd9008E7B3+2gYGBgS9thoLt88ja2hr/2HevnjWNm89PlL+OLD/fUrwQRI3mLDFniHOo6houlMikHBNiSiUT04BEEV23wGK2KC0ZAprxBEsrq2BX3Pp3g9K7tkM7nyGmiLrvbpWuVynGyh4ZYFqMYJ33YA4AC6AJOS2wmM3RLVpUwcMFj/lsis3NDTAzVvbsRQiVEYs55hhCiMG7zjnXasrrMaUTKebjBjvpOGwkzeuieoIUJ7hyp9hsY9HFttPUKtKCMrXc1mlWzfOBnXHG5acyAP3HxpWfT+yWNbdzzuSZcRFvjDFe6VxpPyoyVDNS1r4zRuh6UUY9GsEI0BzB4iBOwCDYbpEXI0wznAeEBWk3VF4qCAkS+nD6oiQlX9XMxGcj569JbXjk0ksv/RUAR4BH1zEcGBgYGHjiMxRsnyeOHn3jCCePXT2NGy93IbzUU7jKsu4xUzEwhaYubvvM8HUFZkG3mCO2HWKK0JShRL19RLGRABHqqkLd1GiaBikrhAFxApjBE4FDQNOnAHjvYaZgLo/vnO8LCYUP0isVd/3DOsx2pujmc4MhZ+acYpd2trambbfYmoyXNkNVbwvLpgEnmOlh5+URFjlBkHVKupOTbnfRtk1ms5FHp3PK7ZzTYvxQumD1qgQcy+ftWbPzy8L94674MDNqN/+PJ3lLB8nJc5wLjRNPxQQ3llgtH2CujJljyshZIS7BB4eUFKQJ4jyYHLIp2naB+WxeMk59BbWSl0pEkF6E4cQVS5Nd8+FiFSya0wWxi9+0d0ynPvwX33fT01/wn4e80YGBgYEvUYaC7QxjNx2UUy+44Tydn3rRqenGN+Tt9Jzl1T0HlpaXgoK46zqAGNWohpPQu+FTCR1Xg/QZm+pKsSbOwQUHVxdvM/EB3kufh8kQ8hAuJrVeBKbV6ceLXQe14v/lvYewszL6hJGhM+iciKZkNo3tYhZjuw7CSUfuYRAe6bp4omvnG5ptwwltwFXbBtvWGDuLGmmOOB9bOmeLEjaXdPLs9ylwsz7a3bHHms37f26VRu4bTfG1ZroHRMTiILDT6k7vShFMxKjGDE25KF+1GAnvZoZmM3Rdh64t62fOeQh7GAzCctqnLufy/gRfivesCV3sdpWxFZldOR5NXrW0snTqg7f88Nue+eL/sPFYn6eBgYGBgS88Q8F2BjFbq+PJvU8bMb1i1uGrzNKTp9Odifeeg/OUUsRivkCoavimhqaEdj4vrvlE8FS8zJz3SKrIvWWEhIAgRXkIKkv8KUaQCFy/S9XvuZtptqyaUkoxq0YJoWOWKYGPw3Dc1E5mzRua0waDjyPII2R8gljXuUvzLkhba1gsYmwbn+MjjeWVR+p80VdMMrD+mI4rP8/vXdg+4b48dflgTPFJxCSlQ5lhvZYiay5WvVy84ZwjZJ+KcfCsTzIYexgIXddiPpvBVFFVASFUYBCyKTQbcoqIKSPGDj4EiAuAEeazGRbzOZz3FKpgVVPVTuSpAH3HgYv3TG+/5Qf+5NoX/6chCWFgYGDgS4yhYDtDbHziZ/fsHPMvFmcHCfn53vtzRqNJiDFR7Dpsb232cU6ArzzECWLXYTaboWvnCL7C8mQJVQgwMsTpDIvFHOwcpBOoc/DBGzPl1GlWs+xBi35EuZW7bivOFlsppVMKO65mD0PoRGA+6YimSLQNl7eM4xQpLrZzzOc3k4j2VMIppN/76B354MGbFcCjVlc+UTFb4+nxlauJ8ncA9lRh8uKK51psW0SWkq6wq4IFTnfSmBkplSitECowOzDKzwkI5DxEPunfllPCYjZHihESPHyo4ftM02wZi8UCi8UcFYBQBXLOgUBLzHT9aGkyu+ipYfvIu3/kPRc+/w3zx/q8DQwMDAx84RgKts8RM9Ajh3/m7FbTt9AifZuT9DRmXiJiHk+WyEBYzGfIavAhoGka1KMRhAnmBCEEZM0IwZurK5Ajy13sunYxj7mbBsasnc9n7NwWwY6HunqIGCeE+BQcrbPSuoluWsZWzHG7Myy8tLFzVRxPkTbcNF08heIaKB7jhf7HI2agUw825/kQX5UzXpRzHqsqiQNSTGjbFuI9vA9wLFBNMNXiK0cCAoOZIH3aAfqkA7MSLi9OSl4pAYAh5RJsDxiaukJV1eDeDiSZwfuA1IfJU58wT0QgdntY7UWaw1zO39fdftPa+6+9cbD7GBgYGPhSYSjYPkfuu2+tWq74Bk38Wsv52hRTDVUS5+CqgAkvw3sPNUNVV9Y0TRTvYjZNIhJHy0vbrq5OCNFx5+UUTDdi6k51cXESSo9YthOJ2/VA3XzRuYVfXp6DU7uaqvRwbPPyfDPj4gMZf/znOhmsHz5r1td/blnRfV3M/huY6BwQcdaMxXxeOqJEcE4gfcFVIHwyyRQIISBVsXTaZmVa2S46eCdo/AiuD5hXzcgxgQio6hpVFcB9LUcAHAua0RgiDGKG9xVEHAAlM2PVtJ8IXzkeVdPRl8v89tvXPjJ4tA0MDAx8aTAUbJ8jqvOqW/CVzsklIQQP1ZhSTgRMAZsG77eIeKqat5x3p1jcMSZ5JGY96QingsiWNGE7WdwWtWmmxWI+myaNW2kExFN8XvroR+7IH/3ozfbTPw37UhtXfj45fMtr6m47vtA5d6MTdxkzCzNDc+msETPqpoGIQM3AKMkPzAwmByI2paSepfPedzF1NtvZDjmnKsYkVVUjBI+IT4pKcuzgRBCCL6bGySCOT4sagg9wIiBQUQeD+lSESDBjEM7JMb0sxZTO2eN/xeyWvyZ6cXqsz+XAwMDAwOcXeqwP4InO4VteU+PABTdM6ur5dTVSMltPMS1IbYuYdojcZjZMiedbEeNZ5XPMbYorzXJ8oG3zBZtbeXdcCVozehzaXXwxYrbmth5qnpUt/7C48DLvwgoTUdKIdj5H23VwoQS255jATEUQ4pwRORNyC4BPqKX7U1rcF2N7Yjpb5J2d7XMsp6c6liuqZjQKoYJ+ipFuzhlEhlBVYO8gzkHEl4QEALCShoBPyX1PGpFTLOHyzBa7mGLKDwRf/Q6Y37x6fPtOGjptAwMDA1/UDAXb54gBdOzoWjPR0NhSbcvJpRPH57Z/eZ7RQnHxgQzsUeDGJ6zdxRcbZmvcnli6XJm/34BXgXAOkzD1Ks6UWqiVQPsUE9rZDApgPBlb5ZuWhY9C8SEje2/q8kdM4v2xTTvz2batb20tCfD0yWT5YF3VL+66uBrbBUKoUNUVNJU0CnYO1ahBVdV9ZBX6Iav04VUZMEAtY9EuELuujGbFAwwjomSqn0hJf9eFya+vnlXdTvT6+Fif24GBgYGBzw9DwTbwJYUZaH7q355vxq9mF74bRpdly2KqgAJGBBGGwUrOZ1bklCzFlKq6Ol6F+q8A+hNmvKdlOTw58fAWLr4jAkVhe/PNB/lp5y2N9l148ZPFude0s/lBVT2nGY/hvUe3awEijPHyMkKoAQMMCTkbspbcVxDA4qE5oWvniCmdPh4woalrc+IiM98v7H6PQ/Pm+x48/tFhp21gYGDgi5OhYBv4UoK2t//Nfh/DQTX8M2Z5ihpVCiB2HWLXQsShqisQgGwKYW8AbVvOdxnxO5zIH6WF3vmJk6c2r7lmLf59XVOz1/mt45dc1M7j93rnXltVo7OJQTFGdIsFzIBq1MA7j5wzUioxVzFGTKc7MNPi1+ccmAlZFbPZDBsbG4hdh+Xlie3bfzZGzSgyuSMG/R8Z+S3L6/d/iK74pfaxPtEDAwMDA2eWQXQw8CXD+uE3rLioX6OwbwfoKgOCmiHmjK7rkHOGiNtNnzAiVoI9RCx/bkK/R5reXZ+gY7jiX3TX/iO7hkRvimYHD7cPP+//VM/CzK81w37na4AZFiOQE7qUoJphRpDgYURwrUPbLmCa4V0FHypk0/74GK1mtG1HOauB2YPpSWz0CoKv5nsv/lU79EMfHIq2gYGBgS8uhg7bwJcEx+/6L0vL582+UlP6PlV9AYuMsykt5gvMFwswCFVTw3sPqJlB595V94Dk98n4fzTk7sCeY9ufrY+d2U3SnnjkMnL5J8z429S0ibFDThEpln/FOYSqhgseICq/lzNEBMEXQYKaYraYYWtzA13bgkUwGk8wGY8RqsaYOJnq0cVi/kdtu/kbVlV/ed55a7PH+rwPDAwMDJwZ+LE+gIGBzzd29I2j1QM7L4Di1UZ0vcLGWZW6LqJdzKEpwYeAECqznHU2n2+0bfcuJfwnEf6/HzyVP0B7X7/5aEyHiW7M1Sf++jCL/J9q9qEYO+vaFlkVBiCZFmEoGXLOUFX4UGHSLKOpJnBclUB4Lga8znssLS9jdXUVwbuSawqAiVzWeP6inb28bfWf5ll60cdv+19WHutzPzAwMDBwZhgKtoEvauzwWr3wi+tTtteA6IVMsmwgSjkhpwgRj/FkCaPx2ABoSvkhzfq2to3/aaed/Xa1fNbhK674F5/TeJGue1NcX9BHcm5/M2vcBgHOO4S6RtOM4HyAmSGlkqLAfYFGvdXHrgBChFHXFUbjMcbjJTSjBs4XY13VRICJd+6cuqlfOqrDP632+q+6+7bX7X+s34OBgUeL3fZGbzbcpwYGgGEkOvBFzKFDv1ids7y4zgf/z1TtZcS8H2A2M2SNyCmDmeFdbSISc46H227+ttim3z413/rwlVf+/M6ZsmIxW+P1B/iaLtr/w0LPDlUFFgci6ou1DrGNfXzZCMyhqEctQ1GirhS5RF6R9GPSDEMuHm4GqCkMZoBpjnm9jd1fidhbCdXb9xxYPDDEkg08UbCja6OtzE91gnT4lA6JHgMDGDpsA1+k2O1r4fy982eSk9dkta/OlvelFNmgYCIwCYQZ3nljogWMbheVNzsf/kvLJz9w1VU/v31mffPWbL2bbrZxtjGd7mC6s4MYOzAznDgIC5jL9ycDYGbY/QcwZE3IOffdNwKgAJUQ+k/9WSYiYcfseG9VheeGavI9IfB3bJ8Il9922+v8Y/2+DAz8Q5iBZg/82wum5L+fPX1vmzB661sxJHkMDGBQiQ58EXLbbW/08YLNay3xq5nxchDOtmgCM6Sug3O+7H0xW9a8o2x/ZRm/lST+werq1gNL+97webhB3MSrzaGntIv2knlu0cUOLnpoVUOIACpGvWZWfs8DAoZaRtctEFOEMEMIfT6ZwUwBFEFCTsUzl5khDDIYiDAhy08z6BgZK5dfdMlbDx36oduvGBSkA49DzNZ4drR6unL3egHfYKB3RrRH19b+v4Ph+MAAhg7bwBcZdtvr/GUHHr523ulr1PLLU0rn5JyFhaGqiG1b7DFYzLHbIMM754v2TYudzf/2jne8+wjR2hkv1szWeP2RQ9cw3PfVVX3BaDRCVdVgLh+/0x01A1KKaNsFco4wGLKVZIScEqj/ObXcF2ul0OtHoQAR1AxdbLGYzanrWsqaGlO9goS+1Yt/3dnjc2+45ZYfXn2s36eBgU/Fjq6Nuo3xNxjbz1rKB3OOjSW9c33WPYIhrm9gAMDQYRv4IuK2297oZ0/auRbavkbVvjFbPi/FJL6qIM71C/1kDBgTnzCmW7TrfqPd3Hz3//Vb1fra2s1nfMfrppsOyj13Tp8+GY1/uKnkJUbsRQTsPZi5FF5Ugt7NAQIAZCUY3kq6gfMe4hxYPvn9iiAg4v7/qXTW+qzStuuQYoQnR2ZmRlSR6UWauq+H5wPPuOa8808d/bk/3vOuDzxIN96cH+v3beBLFwNoeu9PnT1nfyMnvJaInmI5S+zyx1xwdx479v75Y32MAwOPFwbRwcAXBWZrbuO4v7aS0Ws6jd9kOZ+vps7M4JwvFhptZ56dhqp52AX3dmL8xlarf7l37784w/tqhdtue53fv7r/y+Ns/i+rurlhaWllmZhIVeFc+a5EVIqtXUsPIi7ZolS21wjUZ432SlFmMHH5HWJkTVi0c+Sc4L0HESPFDlkVzvEnu3iaLWdV8W7b+/ouBn5PYb/7iaPTjw0L3QOPBba2xjuvSVdmyPew8Lf4qrrQQC7OZ/Oc429PF/F/u+Ap/+6eIYN5YKAwdNgGnvDYba/zxx/01zRVeLWSvcI0n59zds57sAgIQIrRCJZB9qBp/h8Eecv67OSHzz57befzcUwf//jPrSw3+DpN8fvF6FnBh8Y5ITMDSfFUs5xhqsh9JJWpQpwHEYFE4KRoBFQTci7dNphBqTQCRQRd7LCzXQQM48kYo9EEvm4gKQJkEBHklNHFRGbKzrtlJnoaQMsADpy7f/zf19ffcNvq6o9sDjfGgS8UdnitPsn4ci/VP7Ocbsg57ePkRJyo824LpHcv/PbDwzU5MPBJhoJt4AmN3fZGP7to9tSRw3fD7BU5xwsW84UzU4gTEBimamxI5NwnYPbfY9r+zVObm3dceOEbzvi4xQz08P1rl4jT7xTQt4nIpVzVlfOlEMu5GOYKCGZAjEUskHOZTFKKKFKCACcVCARYLLtrqiUYHgYihppCUwmLJyKweJAITHN5DnYQEmRkxC6CmIjYgYgbEF1muVsW0YtE/e9tPvzv/viWW6ZHXvziM7/DNzDwKdBDH/6xszYtf1OA+y4zewagIwBsmpFiNsvpRNsu7t7cfPcwDh0Y+BSGgm3gCcttt73Rx8t2roXZa9jkFdn0vBSTyylCuHTWTNU0pcTgj5OE305t+1snF++7+4or/uCMKyUPH16rH/oEPY/Y/hkDL2Gm/ZohRAzQJ606iLl4p4kBiUFMcCKAGZgZDCDnhHYxhTgBYHDiYGzIqgCsH4sCJIzJ0jKIBaGqQDCkFAEiCHswEQwEYob3Dsxlvpo0hZTjAQIvieBcz+HC658l/8PsFz5C9KPDjXLgjHPTwYPyon990VOk8q82wyuYcKEBlVpZzVFNpimlnPKxre2de6+77gPDl4eBgU9hKNgGnpCUztrG06O5V5vaN8Tcnq+qYqoIIcD7AOecacpRDR8n4t+azbubz31SvGcv/cEZ3dkyA81P/ccL2rjzynY+v5GMnirixkQgYoXj8jGLqYw1vXMglIKqqkqaQREaEIiAlDss5nOYGaqqgnMe2k+GxAmEXPFfg0JYAU8AlV03tQwSKWNVYuRe1FCPRhAnMBi61BIV/zcm4iUCXQvmPQRcvHPKfv/++9f+/L3vvePhGwdBwsAZ4uShH1pu5+ElxvgumL7AoPsNYDPtp57Ui2i4I8pHmkyDOnRg4G8xFGwDTzhuv30tbJ278YzA/rVE/HJlO8+iivZjRecDQvBGRl00/RjDfpOce+u5587vPdO2HUeO/EKzc0KeS9S9GmpfqapnO/EBTEgxASgqT80ZsetAxHDOYJJB5CDioZpg0BJHBYKqgazsqIkwck6Yz0vTazRq4KtSsBEYJgY1Q9QOOWWIE3gXQCAYFItujrZrUYUAYYFZhkFBYBCY+qSFmskuMqJV7+iSfeOVq77yK57zRydPXnPHvn1rW4/1+z3wxMVsjbcewKU56qt8aL8la77CoGOYsWoCqOjeWLhk5hLNQXp45Gfbj/WxDww83hgKtoEnFHb7Wtja3zzLEX8PgJcb9FwCi/OuKCLLDcBMNRrozpzTW+bz2W9fcNkL7iN68Rkr1sxA8/X/cCFEXhm7xas05qvV8hgAa1bEroOplaxPIuSckWKEiINq8VDbzTUo/moKtQQmARHggwcxn95z67oOjgWmBtUMYQZ6tShQdtssZ5BzYCqh8GaGlBMWizlgCu89HHtky/0zG2BGhAwYOTLba7BnMeuBpnJXqy2/bb7+72+tV5/0ANGNQ7dt4LPi5KG15a0H6YUi/G2O5Ssy45yYoocRkTC0v/aJymeBTU1NZ20XH7r30N2DufPAwN9iKNgGnjDYoV+s5ufk60Lm7yGjrzW2c9SyEMqCPZMCIEsxdgm4k0V/PUX67XuOvPfIhZf/3Bkr1h55ZG3Snpq8AEzfBbOvIOBsVQ1EDBYpik8yhKqGC2WvTLOWaCn+pNUG9VYd5fczskaI9yVjFICaFSVpVggIzjkQM7IqzCJY3OnoKu8cTByEioVJttwHxrvy5/pOBqFPS+jNdqkvHNUyqSa0XdcQ7KJQ1XsIclHS9OT1Y3f94eHDax+55JK1TQxjqoF/BLODsn3smitSF78lzvUV5v2TffDLLgSCd+VC1IyUInJSEPU7mkbIWReqvP4rf9wN+2sDA3+LoWAbeEJg9gtN2rHnisprje2lZjg75SSqvboSABOZaW5jinea2q+7rL99zl/eceTcG289I90hs4Oyc+xpT1ZqDqrxNzHoChiNiIR9KOa3IAJ67zfvPWCGnHOfUGAwIihK2DucB6PsoqkqLGck262HSoqBoXTbhBkkpSDMqog5wgfbnShB+ucm4r6kYmTtkFKCsJQ4LgIMxRKk7AtpGZ2aIaYO3aKltmvhvIjzYdU5fnpOek7s2qfUgj++/56f+rNHNk7ce911b5o91tfDwOMPM9DOQz+5f/5g9WIxfKsZXc8O55AgGJQ0G4gJ4KKQFi5CG7OS9QGzzExTdqP1N73pA0NHd2DgbzEUbAOPe44efeMobdoLErrvhelLiGhv1iQxxd1CDUZkZtYp7A7L9usxb/7OLbfd/8CZWpy3rV/eN1+PX80hfwcMz4HwHoC99cpOgS/FWigVlIhAuHTbUtfBzIoYIvT7bCmhqgzehz7toGSExrZFTtHEhRyCnwqLc86NUBlZLyxIKSGlBBKG96F4s1kpxBgEIgcGla5diiVg3pe9NrV8uqhLOZdK14DtrS1sb23BicPyygoREcioEuELiGmPEK6Y7Jk8c7Ky9Kf3ffjH3rtz9ycevPbGmwfD3QEAgB35kWb+idEzJfhXQuSlluIl4mgEqRlEVDq6CmSD5SIyYCdlqm8GBqBqJk5mnnmGoZM7MPB3GAq2gcc19tCbx23Y+gqwvJaMvsIM+8zADIZzDoxyP0ix6xLsDpj+ekz5t9/1gfsfPBPFmtkvVmmbnxXNvp1BX93l9KS2S7U4R1U9ApOAyyYOgE8WTsT9EjUTzBTEQF2NIU6Q2g7W77mVRWuARaBqllKKmvWk8/QRET7GzF9GxFeCSka8mYH7wo1AYDCUtDfWBcQRHDFgxTS3rhs4ETgXQDBkjQC05JbG2HflBG0bMZ/PMZlMIM4DBiq7ck6q0XiZgSeLyNmW87VLZ42vG+97yp9O71/74OjCr3jkTO4GDjyxMFtz20flsrnYyzLpyx3J08Cyl9icpkRABnZXAJihKcGygkTQL7ABWgb7LGJgnx25YjY4MDDwNxgKtoHHLadO/dzKXNdfwiLfY4bnm9pqNmUmBpHAswMBpprbLqWP5pTerF5/58Cl9MCNl31uuaBma9xu7r2k3aBvNIuvzKbXphQns+lUUsrwIYBZEEINgJFyye9UzSCmXsXZzyuJ4FwoI1IURZwzB5CBoBAJBs0xp3zcVD/ETH/sHL0fsEtTzs+mnAnEEOewex9jopKSoAkgnB67EhNADkIEch4kVeli7EZb9f5tRgRigmZAgsN4sgwh6W1EHFKKMM1gJ+S9NybxsLQfyMvM8qQqVE93PrxrZ+Mjt2w98JO3L/1fP7tOazjjWawDj0/spoNy7OoD5z/w0a0XVaPqG+q6vk4cnwviugzziQCCqZXusZZ83NP2HQpY6n/P+jav9M04yUOxNjDwaRgKtoHHJXbqjStz7r6WGa9l4+eCsGQgLiv2pWBhEjNYBNJHkezXptuz37n0muYBorXPqXDY2PjZPTun5CXC+TtguD7n7qzFog0pRcSUoGbQtoMZQFxC2Le3NjGfTlE3NSZLy31hVAxufQgwALFrywhXBK5yIDIDKKnmR1T1gzmmP8oxv0vQfoL3e80zeUHSdA4ZiEWKalRLnBX1RruaE5i5qETNQKECqPT8BAQ7nRa8q8jj3vUKxd+NgSABfhLQNCOQlbMcu4i2XUAcA1DyfWQWgIqJzyHilZxxWeoWz1p09Ttn/+Rf/cXGayd3rzzpJzaIaLjhfpFiBpoe/qmzt+vq+W57+nXdYvbcnPjCrH5sYBbTovnUcu2TkzLqzx00ZTjvAQEs62nF827HGCmX2o2H29LAwKdj+GQMPO6wrV/et8jt1zHo1Qa7LiNPTI0BK6axZVPLDNrB8FEYvZkdfvvSa5oHP5di7dChH6owr541W5/dOG4mX8NeL2KmOqtyjBEsjLqpMZvOMJvtQFONqm7ArmR6zucziEjf5SIk7X3YnEPbdZjPZiAAo/HYfJAIo0dyzn9phrcz8nvnZJ+475H37rzznTfoT/zw6JIF03M804oBZGpIsSsB8QBcVZXiigiqGTn3UVzEvddaKdCSJphpn6naj2sNxVAXZfGbiIswgT3MiiccQq9gpTI+NUPv2yYwU1bFSEQvAPJeFnqyq5ee56r6L7a3/+N7H3jg33zs3e/+4MZgvPvFgxno4b/+vrMeOrTyZZMmfC2ZPV+Cv7imZolFXGwjCTHADDb01w6DMgDubTtYwM6dTvtQVWgsI1N2AlMlJoJ/rF/swMDjlKFgG3jcYAaaTv/D2W2ybwbTd6acn9F17UhzJsDKCLKq4cSbWW5V9U4G3gzQ/7v3vHT00RZrZgdl85Evuzjn7hVzmn1zzvkahS6DILDSlQqhggulyxS7hFwZ6qYBUyl6JkvLcCLw3oNZoLa7o0bwIUDEwTtnahoBHIPh/Qb6I+T8nm5ncWTv5VdPJ73XmdkNbnaCrhLHVxOJBxFS78UWuxbEZTzKIsVrLZYVMu8rMAnUUu/rpohdC7Ni3gsohAXCDiklxBhLIoR4EHqVaXnJEHEIVb1bqYHIlzgsIRiU+m6beOeXhN2IWM4102up6164XLv3vuyGZ7/34XufcfdHPxFPDvmkT1zM1vjhj2P/iXu7L+Ml+xrvwnPZ+UvMdMV5OFfXSDlR7jpgVwGqVkaevRegoiikqfdJJBCMFVCAnSudOBYQPnV+OjAw8LcZCraBxwVmoNmJXzrgnH2rQb8zp3ztbDGrZ9MpERFEpP+G7o04Rs35LtX8a34c3to0px51sXbkyNrezYfGL/VBXiVCzwVof0rR5xTJvIOimN764IoiE4TxZAnNaAInDkRlPDuqRwghAJrLDUmL8pOZweQsBJeE6KGc03uY6Q9Y+T0V2qP48w/M6G91otbX6zFnfZ4In0fExOxgnqDzOTY3N5BVMemWUVUBrh+VlvPDvfShHHPXtei6FiJc7D/MyieeegVpzmBOUOTTAgZDRswROSewuNM/S0wQKsVc6R5mkBp5cXDeS9K0nHIcE/gAMz3NSF5UL9H7nnWlvP+BQz9+x/mXjx4hWhtUpU8QbrrpoDzv6Rec+8jH2y8D8JUGXE/iLiHnVowgqlYuqt7GhvoxvO0Gg/YYATkmLBYL8Olr1aGkklgvcDFYToAaZVXM0pwe3VEPDHxxMxRsA485ZqBP3P2vL15enr4qILwKZFct2kW1vbVFOSeMxxNUVQVmMQE603xn0vxm7RZvXd6/86iKtSNHfqSpuuVna8SrEnUvtSRPEpYmeE8gwHKCZu3Hm+hTBUo+QRXq0pECYMi9DKCoN8Hcd7cMTpyZakype9A59xfOuT/ykPdX5h/E/uOzT3fctrbGO4YLjPnZzDThTxEvxBixs7ON2WyG6Wzbmqppx5Oldml1tam8D2YK1bJnF2OLlCKcc3Cu7LvBgJwyYixWH6GqwFySEswUCQk5R8Sug2aD+FLGQQEIQ40RY4ed7U3MZjuogsfy6irqUBpuxBCYLTHLiEBnA3y1sr1o4vd+aOc4v3/r+L/96xYnj5511s/vYFABPi45cuRHmrpbuZAoPydpfiE79+yc80Vt2y47NcckXC5962u1YgidU4QSwzsPoOxWkgjIgBQTunYBF8rvmSlOv/2m0JQBKMjMUkyUsg4F28DAp2Eo2AYeU8wOyoP3XnNZcPqdXey+lYkvBSPELhIAjMYTjCdLCC4Y2Doo3QXTX9OO3rr3vPRZ76yZrbntk5PLu0X7zQtdvMJMrxInEygLcUkG8AASANWS7yniYGZou7aMFJ0HkwNQLDvQpwaoKnKKMDVjkUiEB9uufRcx/X7wo7/suq1HzjoLs3/wmH96r6f19HQWXMks/a1RQTD4KmDv3n0YT8aWU24V9Fc5xbcz8x4ifpFqvlwNSzlFMrUSReVcUZF28VNOQhFLhBBglstNE11RnBpK7JUZUhtB5Eon0RiqhNgmrJ86hc2tE9izZw/GS2Po7l4hEcUuwrKK834i4kZidg6Aawh4oTE+WtvZ75+d+Dcf1Gz33XVk/6nrrnt9xMBjitkab95frZC0V7aL+Bz29gJmdzUbn8fslphJcuwopUyhAsQJgF5tDAMLI2cCi5QxvSrI+9I9g0FiQt00cKF0qHPOxaMGBk2pdHqFwcwswTs31wqnHQIHBgZ2GQq2gccMs5tkuv7A1cuT9OoU4zeZ4Ukgc6pKxITJeIKqaVBVtREQYbhLid4MSzfvPW/7syrWysj1DedOT+avTjG9KnbxWV3X7Q3Be+dcfxMCdnflRIoelUB9/mdC13VllEMEcb4UaZZ641pDip1Np9MEtQerUfMXtZffN83vy4KHzlr50TnRP34D2t7ulijpcw1yVtZIORVzW+ccxqMx6rpGzslSSidi173t5HTzV9FZJsHNRngRYDd4757hQ7Wf2ESzomtbmGb4qoJI+chzsURBzG1vLVIyTIUFSXO5kabyvJaBbraAGpBSQtOMIO4sTJZGYAfE1MKJh6lie2cLOSZa3bMHo9ESg9AYWQ3YXia+3EDXZ3aHQPbByy44/qHjD6zdaRs4etY1/0ghO3DGOXToh6qzls4+b/awf5rz+fq20+cQ4xJh2c9eRppUiImcBIQGSF0HYgY7D1LtTZgJPpTriqjIgWACRvlSABjCqIHPHn0YWxnPOwGZAVl70UyEOhEfwhJr2HvDDZBbb8Ww+zgw8CkMBdvAY4LZG/1s84GnE+S1PrivA9H5KSfHXFz4XdmNQuUrM0NU07tI6dfQ8M1N89kVa8eP//jS9NT5LyBnr6KEFzLR+c77qoZRSR+okPuCTIQhvgJx8TyjfiIq7OC8IaW0+wIA9PJJwAgWc873a+7emZP9QbWgD839ykPnPOknZ59JoVYeco23TsZLYszPBKgxAG3bgQhomqaMYpksxhSdD3ePmvpd51/mjhL9K73ttteduOKc8+7Kvv79IP7ZIv4lYHtu1HhBytqYGjwxvAv9iJXQ5UWx7hCGc6HsqFkp8La3piATVDVhEReYz6bougVCXWFl7wpCcxZEDEYRMXXlJKGIILrUoe06hCqSFcEEMVHwnvcQYRnKF5LlZ4r4I8JyJ53LH954KH/kgQfWPn7++QdOAa//jIrbgc8eu+11/vi+lX1xGi/rZviyFtNnIVRXpZQvSJpXRTgYwClG0lzUxc57eO9gu8kYKJ213R025mJVwyjKT6Tc23aU0bswIysj51wKvuBOX4MQLgVbisRcEYBlJT1ww5OfHW699QNDwTYw8CkMBdvAFxyzX6x2js+vV+j3MNlXk/Nng1hg6L+BE8Q7eB8MTF3u0t2m8ddSDjfv2XvqMy7WzNbC7OTk6Rn2SiJ9uRkuTjmNQcxV06CqKzCVG0rKfeYnGBBDThE5R0AVIg5106B4kTEAQkYGG4yIIpHcnzXfysDbcqIPhJV4/OwL/t3isy067rsPYWXkn92l7vKcE/tQQZxDThExxl2VneWc1kn5fbS0526iH1cAuO66N0UAp2666eDmwRtuOLwY860a09PYyUuCDy+Ksb3U1CYKE0+lsDL0sVrOg0lKdBUMWTNibEEmpROnhNRFTHd2oGTY5/ajDjWIFMkAdVpMRIgQ6qrvNkZMpztgccVnS4RyVnIixMwTA0aU8jlqdlWM3QuFcXg1jO9abLQfcfLLd85m9EDTuJPA6xdEgyHv54Ld9jp/7LylFdqWSx9Uvpo6u06q+imN0cVMvH9ne7uez+euqivyoaKuLQW3cw6j8Rjw5T0sxs8ETan4pvWLi7tpGQSCpQ6p7YCUII77LlvZCTVViHNlxUC132Wj8mtOELwjFiyNAp70Lf/8ymrtTR8YMmsHBj6FoWAb+IJy9OjaaOuR2Qs143u7tv0KcbKvbiDFAkCRU0kC8M4bsaSc892m+qsp61v3HNj4jExxzYzazTdc0m75b2CxV6bUXtN27XLbdm4xnyH4gNFkCd6XaKvd0HRmAROjeJ4lLOYztG3xVtvD+9E0YwDAfD43y5rqujrifLiVSH5fSG/bh9Hxfdf868+6UCvHDJrN9u21Ll+ftdqTWyNmga88YkuYzaZYLOZW1+PsnD/MTO8ej93Jv/04xfvs5h0zTB848QsP793rb3OZftcs3ADDV8L0Kcq6UlaPBHVVg5iRLZeqyIAQKiwtr8CU4KXYfoQqwDcBvvLwwX8ydJ4cjA0xF6HDbrbqfD5Du5ijbsYYjcclessMKWcCEcxUAGOF7SHQipFcSCLPBPSlSfV+RNy9PY93kv67Q/P1dN/2g+2Js665Y040eLt9Jtx008Fw3VPO27Oy56xLpsE/pVm010a/uKabzi8y1f1+aTIJdeM1dhy3OmrbRbGBSan4o5nBhdDbbQBghrhe/WxmljUb046QbAOozfKebORi12Ixm8ERIFKXwqz/l6jsuREJSAg5KzQrxAU470CUoTnVDFxwwXj/EoD1x/o8Dgw8nhgKtoEvGMfv+rdLDvrSrPjemOLzUoqrZsY5pDJaEYFmRWyjmVp0gT6WUvuruQu/te+CxbHPoFijQ4d+cv/DR3/mpeN6dJBFn2OWz8o5+8V8QV3XIqYEIkZKZdQo4uBIIMwo41gFgctCvibE2AEg5JSRczRLObbz2f2x695JsvK2qpIP1KvtI8CPLmjv5zLGO8jdLF6hsX2mmobgQ7EEYYL40tnLKcE0b4uX20zxEeD1f+/IqBSNPzoHMD906IdOLi096faK8x+p2osM8augeDoxrTr2pDDknKCmcOwQQo3gG8B2/bEYrvYgX+Ks2PHp0RhMQCgRW5a1JDt4D025T1Mou3I5l30n6+1FrLjxEpGQeCI1NLHrahbeC8MlMHs2iE+C7IGuo4/TKh168NClHzt0xw/eh7D8SH18sXPh89+wwLCYDqCId9bvvXTS5Waf0c6T2jZe66vmyiB0hWO+0Lzfr7Cxk67qcuJSsDPADuPxBM6HEkcGhROB+Aqj0aiMQgFYzmBmI2aznDey6l1Qfh85udc0H0gxfz1gV8eucwZAvC8ChN0EDhQz3di2cH0sWsoZKSU4kT5aLcFyCsS48NQMFwA4guH9HRg4zVCwDXxB2PjEz+5JHl9PLK811euEaeyahqX3Zipeax6ttbaztRmZ+e7J8vKvms+/te+/LI7R2j9crD304R8bn2y757ZbWzdWoK+sfDjgWRo1EBGVDpH3GMGgWYHdWBxmwKQ4/TuH1O/ZOJHTUU4EMjN0bdve50Te6dj9YcTOBzqcOl6vvuFRddT+Li+tcjz5vNl8dmFOmaq6QUoRMTKqqsLS8jLMTFnpCJK968T8wYf3fobPe8UVv9QCeMTsjevbD568s63xJ7bIX81M3xJ8/RQRVxkD2nXIphDvwBCAGNzvtUENJOUtUE0wod4ppGQqCDkEXyP3N+HxZALvPFgYKSa0sxl8CKiqcNq7Cyj+cYBRahdIKZEPgUTEAdqY6j4iuhhmzzLDhhI97Lg50rjqsL90+Z71Yz9zWMbjo0A6ubS0swWsxS+V3Tezg4Jj11TH5vP93bQ9/8GPugubSXNV8HQV0dJ5zYgOMLt9zrmm69owm045ayYSoboeFfFJX0iFUMGH0I/CIwwG5zx8rzBOXYSpmnc+kpMjCr1VNf6hUPXB7PRUnk4nueNDzPSjzHRtPR6R593PTzHOLSHvGTlGGDFcXcFQutqaDbOdbZgl1E0tjt35y2F8xS23rL1/MF0eGPgkQ8E28HllN72AuvwtrPRdTPS0lNMIGeS9hzD3MTYCFmcppaSmH5ttT98s4/xb5587+geLNbNbXNz4yDXTuPjW6pFHvm423bms6+IkpcygBDKDiJxetldTzOczqCk8Mxx7EBWbAucMJCWpwEzBLlgFimZ6OHbdn6im38/AR3K1feLSy37xDBVq5Ry1m9Nzg3fXpy4sddZBNaNrWwCAdx513RgZ5qb6wTSPH+yLsM8KotdHACdvu+11W+ev7LuXK/9+Yv9a5/zL2dySWYIlRaIMcegzD/q9QlH4pjc5RUKMhpx3w7ypCBckIKcZck6oqxLbpZoxn83RdR1YGKqujGBTApFAnMCsjKRLuD3KsjsTMTGxSEOCGoxVABcS6NqqrqbEskGMhyTLA4B94tRR+UTKa0cePoKHOPvj+y/avwn8cfvFMEI1gHDLDbL+pOvH27nd56067+F76UJGd7GncGly+eKYunO4W+ypQrUk4utQBSESUc2Ybm/TieMPw4iwd3UvxpOlUihn7RXO5eNlamAQXAi9JQcQuw6L2cyYZeF8uNMy/vts0b2tUv+xP/rgX09vvPHmbIaN+z/yE/99stJ45+WnTfX8rusQROBYQGplJMoCob7QTwYfKsAFtPMZ2raDaqZQj9hM9jKlKy6YHxoB2Hqsz//AwOOFoWAb+LxhBlo/9tMXOomvct5/OyFdZeBaWIi5JBeUMPIEIzM1S8z8MR/Cr+rW4jfPP3/095rimoE2HvrZi3aOf/DrXXDf7MQ/fTKZrIiwq+oGBEM7n4GZ0TRNKczAyMjwIQBm8K6CUFGDlkB5KdFTOVns2pZY7mGWd0LxNhH+8HbSkxdf8q/bfWd6TPOBNzq6bPpU58NTqhqOU1nULh2KIsAoHgh0FGrvmp+io5/L0/UCheOHD6+9cwV2NKbcacrfbtlcu1iApUMzGUM8Q5FLEgITAnvAADXDYj7H1voWnHgsrawg+ADrfeuqUPWpEEDqExXEuRLv5XxJHzLru5xl5815f1oIoTHCSCDCJCXmi2Bg55wwcxCRJSI+G8QXE1nbdWnaxW4z5u6kk3A8jKoHp5vTY7DnPnTywesejDkd82Kb3ni2CF27uXkyXv7BhxI9zrJOzUB33LHmr9m/4+852o4SxfFIwr4Tk6XzQhUuhuoFYUEHZjtb580W87PH4/HK0tLKpK6qepE15JhYRcickKpBBIgpoevjyYS49wpM0BTLr/V2HN2iRRcjXHBwIYBZ0LUtZvOZtV27U1X1X2XT39yZ2zvaQ3/1wHkv/4PTXxjKF5ef23j44Z/6XZ77J8+2t/855VzLaALxvfGyGth7QAQxJeSYwOJhBuSo8L6Cd8XzL+c8zilfXh/w52Ao2AYGTjMUbAOfF8zW+MRhuQIO/8SxvdI0X6aqIatSUYoFcHFtApGaaooKPZS67lfZ/G9e/of7jtJzP32x9sCdP7nv8Efbl9Sj9tsnE/8cM+w300DM1IzGqOoaAMpCswiYpL+rGMgYVaj7G1iJXspaHP6JyFJM7c5s9rGuXbx9NBr/oYz5zp1ZXL/kkrXF5+1kXTYbJ6Lr2XCAmIiZi/DCF8NeYTECzQH96w7pA//5d358fiae9pJL1hZma3efOKJv1YyvUctnz6YzEAGhqWDel1xSKJgIbIxdnxNVxc7OFkwZoapRVQ2AsrMG9mBmpJxKrBURnJRIIuGiOGQnUCs2EQwGCZByuZHnlCBWYovMgBgTUoygEjQBNSVHQmrWdNo1SdOKqZ5rGZkFnZC0Ap4RsKMhn6SIk2p6apbTcXT08J7xucePP2/lxMn7/tfNqh7vuKZZ2HS7q5dpgXh1ezweTbfeek+ePJT0SNXqsWMfs6vvuNUA4KPXfLJYX1vrTV1Qiuu1tbLZd8cdn0xn+oEfuIGWPvZkend7hKvKydlnS7j00qWA+TQ0shqWVs+pR1UzNpPVneO0csl+2rve0VnEi3N0kc61ptoL2AqM9oTQLOVs9aJb+O3tdS8ivLy8St4HqCqZGsyA1KWi2s2KdrGAmmEyKVFqmiJ2NjdKV82A8WQJVdOcjjVz4ktAuypSiqaqG87794j439jaWtxy0e2HHqEb/+DTFbr2znceOnHd1Rf+OmK8rhL/FQyi1KWSegBAyncjQA2AIseIxXSO9VPHQQBWV1eLICGbd04u8X7PJWtr+Pja2qASHhgAhoJt4PPALbesufUH/NWR4ms9+W805gsNcMXWzPqbeIKwAxEbgTKRHFJNbzZr33L+5Tj66cagR478QlO79vp2Z3ZQ5/ErU4wX5pwb5kw5F5d+HwKCr3oH9t0CgZE1QlXhxJW9LOrHQCjBTDHGhWq+M8f8x+1i9kez7fb2mdbr1178v35e8y/N1rjb4Qs44zlGNmJmhODBvQ9czglEZAQ5nhXv2ZjO7juzN7A7svPP2cqWVLtyflzorRdQ9vzA1ofDAzACmcJ7j5WVVSzmLVKMSLGD+LKPyCxgEIwUzIy6rkqGPKG3f2A4Nlgf70XEUAXaRYvNzXVADZOlJYS6hvbq4VL0CcCMlDKMQQotaRREFKqKSUic80GcjJlkDwBz4rLmFFOrEdBF5aoZEc/BzbZUsuW837KUpim46WwhG2aHNjh2sxc8s26FaPpc3j81d35ndkOOscvXbc5TTpYmZ43zD7+u0Yja5tncw7kVEBNR4pMnT8liuuOX9+7xK83ET/du+a9Ol1TON+MquFXL3cp2dhMVHgvxRByWSdwqQBMgNznGOsdYe++r0WTsvQsizrF4R04rGo+XIM7R0vIeuFABMPiqgpkhdV2xgOk6dCkhdi2ECE0zhnMOXbtA7LqyV9i2AJXPjPOl88le+k8FmfN+syb6M4B+NY/8n//Km3Bqbe3mv/fau/HGm/Phw6+526d9/7f34SmpS+e07QJdu4CIYEIMV0sZfVtJ39je2sD6qZPlS0LwCE1NUgW2nA4AdNnVVx/8c+DmM/IFZWDgic5QsA2cUW6/fS2sjvGsBHyviP9aH8K5znmHPueya9syBvUZKmLCnEB0iJje7Fp6y9L5+DtjULM1N11fvTrH9lu6Tr8uhHCF827CzEJEIFgRCdT1aU8x7dVuBELKpaAAAGYCEyFrhhCMmOZkuNNgb8s5v13T/GNzG20+9bk//wUJKr/jDrjLLuRnmOUnJ82imuHEw7FH0gRVM0qxg/BdSuF9F198x/aZfP577nmhO3spPZPE9iB4uLAMdiXXW/vOIxQgR3Ak2I3jYiasrK5iNEqwXHzrwAZ2pZtZBAVlzKk590VX8bBTWN9dM0j/mACgmvtOWhElpJRAVPasnPfw4pBywnQ2Rc4ZoSpjNGOGeA8QEbOg9w2BGYOIpW0XYX39FJjEJksTq+vanA8q7FIypJzbbDnnRLmbz3a6za3N5IXj8mR14Rq/IJJkpJmYNTYxWZciwatIyJEVoOwsJyYjct7zysqSjMeNH43G3gk7n4LnxM4H75242tQHZnLE7EIVBOIcCTtiltRFyrkj54TqZgTnPRGVOCeLCSKM8fIKJraMUDfFQzBFOCfIWdHFDu1iUexpelFBVTcITVPioIjgmxGqFMHb2yAiaD+yJi4j0xSzCfMiOPeXDPnVLZrf+v+86Wc2P5MvCpdc8ivtfbf98B/HOH1bnM2/e2e6LYt2jqWlFTSTFXjnQGbFhNdK/m4IAcQlPUScQ86RYopLTHTJlVfuWwIwFGwDAxgKtoEzyLvffbCpaed5pqN/6r17CYvb78SJsO+jnSK2t7fhvcdYJrZYLKJpvsf78OuTyegtzd+KmzJb482H5aKt49XLmfM3q9ozYLbMIj6EphRk/ZxMiCGMvlgw7MbgaC5dhnbRFusOEYjAQLQNxkfM9G3k3a1+bncfPv7AVr/f9QXjov3jVTI8z0D7c85EVvbWDIasCbPZjuWc15vxynuqcXX3mVyiNzM6eeznL81q32RAcMEjmyKnDtpG+NB3I2GAAsoGhkFRRJ7iHJyvkGKHlBIsGcgUQIQ4By8BBmA6nSKmDhNTLGiB2WyBrAmjUYPxaAzvys9VVYWV1T39gjpjsZjDu5JJyb2SmEpyAubzefEEq+sybhOH3VGyGRA1AsRQzTRbtNjc2IKZUtu1WF5exurqXoMjnz61wEGyk6dO2vHjD2HPyqotj1cMIDUzmMKICM1obHWt5qpgxKXQjDHSfDotZrKoUXlHo7oi5ysiBjXjESFracARE5GRDw5GIBfq069NTSmmBDND3YzgnINqBokVmxRTOF/UzkwM53y50llhWopiBpfzItRHiAmc8yVeLScQlygpqosy1HIxqlbVYrkCAjEZgY5nxR+1m9t/8aSn/fuNtbXPeG/TDm9/6PhZdOmvxS69YB67JxsRfNPANQ1SVrTzObwTiGOMRg1iWoKvaoxXV0BMiLMObezquqovPaup9wF45Av5mRwYeLwyFGwDZ4Tbb1+bnLckN3Q5/lNy/suZeY9znqnvnhAxnHelK+K9Occ5Rvv4omv/K0t8S7M3f2qxRkfvXtt36qH6JTB8m+X8ZWrprJxipTnDiOBNQXUD15u+FqUpAUZgKfLNGCM055L/WWZyZjlvG/jD4uht4ugdPvp78THZoet+6gseQm62xnFrcrGZXUfEtXMG6l9Hn/hgOWtezBcfN/Lvvv2u2caZfP4TJ35+gpxeFc2eLcwkPsAyELtidBpCgO+D763EUJRRMxXPOiOFiIcjX6wbUDptXdeCxaEZjQAQmBld2+LEfI7t7SlOnjgBFsb5558HPucAMAKICcTF/64fc/YdtqJAhRmyKVgYzWjUd+uA6c4UKbZoRhNMlpYhfReuXcyhavCh2FNMJstQy/Chd+sHKOVc8mHVwJ7QLVrKKWPUjDAZL0FCQEoJKeXSmeXiMSfOWT8uJiIGEUyEYGaY7ezAO0eT5SUw9x0tH2BSlv0t5+JjVy7z8rqJoCkipghNEa6qQATkVGpzFgdiwHoLHCEP7r+YWL9Fl1PJf3Xeoa7LPmGoyqiTabdzViw7KCc4cajrEQAgti1yLPFi4sREnArTw/N5uuP/8x9vWX/Tmz47kc2LX3y92UcpAACAAElEQVRruu0dZ/3V8tJZ/61ZGv8IE/zKnn2QOmA+nWJnZxOj0QgjP0JoaiyZwvWWL0W4Qgi+duKqCz0156yt4e5hj21gYCjYBs4AJ0/+4rLE6cug9D1V5a9XtaWcMxNHEAsUAIOKSnDVGzMngn28qvAbbTv9r3/+l4eP3HhJ2Y156KEfG7eb4bkR3Y0++xuI6ILFomtARugjj6wPIVctN1eWPmrJMkwB4mL2GbsWsYtwzltVVRvE/AEi+r2c7C9y6O593/vb7cfW5+mioJg9B4SLYSAhAXZ366yY0E4mS1veufdutfHOF7/4Z87Ysd5yy5rL24sbpPL/xIsfSd8tYmI4H8BUFtAJjJhb5JhAwqeX05lKgcUgEDuEqoxKF/M5ZrMZ2rbF0tISlpZWUNc1YuywsbGBnZ1tzOYz1HWFlDK6ri1FkPelqEgJZgoXqtOFmQHIKaJ0tErhNBqNoGqYzeeY7uzA+aoUPygduOnOFGaGiSyh6WPF0HdYue/UzWcz7GxvoaoriDByTliaLGH/vv1oRiM4cYhtLD50xmX3shRmVGLMAOcIzEy7oouuW/TX4ifNYs2sL5gSiAyCUngBpTtsqoixQ7dY9F3Lci4MvBtHBmaC9f51u9FOMUaUog1IMZ42J2YuXcbd1Anafc+Y+94z7Uo7kWLEYj4vgoPgIMRgIiPwFKTbb3rTBx5VR/fZL71m+6E7Fm9Vtq9yzM/0VTHmZQJCVYG5ZIgSlZGtC6WLmmFwdU1iYFI7S1gvevWrXxPW1n7l8yf6GRh4gjAUbAOfE1tH1/brYucV7OTVcPyMGNNkZ2ebYteVvaMQQOxQVx4hBAtViDC+F6a/wRz+6/v/+q/uL15Oa2Hj+OjqvGi/1Xx+OYDLmHjcxU5ibIvRJ8qNrwo1SMqODohO76xB0Y+XetNOZk05n2Dm2yT4t2XCn+Wp3f/h+3emj7Uhpxlofmp7vzh5viKvqim6GKE5wYkvjqOwFKpw2Dn5i5M7O8fP1HOvrYEvOHt+ZavuX064uth5T5oTtrc2kVVRVxV8XYPJIeYOm+sb2N7e6o1vK4Sqwmg0RmDfh3gbHDloX2SWkWWJ9Krrplh9VDWWlpYR6hp79+2D5oy6rndHgbCUAFCx99DevNh5dDFiZ2cbOcU+Rqt03qpQI1Q1RqMJTA1VVfexYmVXrqoqgAjOlVFpMWiWEo0EKjFZKSGliIrqUiAZoRmNUDejIlRJGQD1FiXl5xn4ZNSSlMcDDKFcb/DBI8cOxHxatKEpwdRO72mxD0AuEVC7mZo55V5ySn0KRPn/2EWkrkPorTaMAePia5ZjRE4ZMXbIOZd4p74TSFR2NTVnCFMptn15v5wPABlyUuxs7WBnexOj8Ri+bkqyRSkmfTYu7chHAdGaHr/rx+9KRv+NvVypqRt1bQdxgvHyEpwrKQg5liJOXC9YgQEiSF2kGNMynFy+1188AjAUbANf8gwF28Cjwgz0yOGfOrvLcqNz8t0EulrVmq5d0PbWJhaLFuKkeGw5j9XVZXO8nIXkMHu8JWS85ff+7M/uP3jwZj1259rFJ4/6b3CCb1HQU8Xxig+VI2awZlT9jT2nhKoqnZdi+VBUjeWA+rGreDCxJssnSeTdJPy7Hnh3Couje/di5zMNjv/8cxOrPXBVF+NTCfAlvimdLhJUFTCdeq3/Cq1+5Npr186YCOL7vu/f7Uc3/z41ej4Al1JE7jrs7OxArYxCWRxKwdBhZ7qN6XyGEQHe+3JTFwcjQtm5YwNRIjViJ240HoPEw3tXDHOJkFVR1RVW9u4FGTDd2UbsIrgPB9esEO8RXEDOEWYl8b1tF9hYP4WUIlZX92A0Gpf3uPfxa+qmGL6yg2YDSRmhjscTgMqfj12ED750rlKGeA8RKYVnHpdEBiI4l4tQAoS27RDbFt4HhKYBenNZUPGWOz0eZQGjdBxBhJQikpU0jVxyN7G76+Z9Bef6cWeMyF0qgg7hEqJ+OgIt993Hrh+peng1sJQRqqlCtawBpNihaxele6ZkbYwGNa3qSsQ5MhhSF5E0o120CL6ChKpY3aDrX1If3i7lfe0fbK943re2dlDW1h7d3uT+K0fTRz42/++k+eVqdr1qAqnB+TF8qJBTLJ5wOZcxuCqginY2w/bWBmDUjMdLl7ZZ9wA49Rh8SAcGHlcMBdvAZ40Z6OjH/9UFEPkOx/IdzPTkpFqZWYmBCgHFa82VPSIvJuxSynovcXqLSv6vYWXrEy972XP2nXrwGTd0NLsxL/JzhdP+rp0FFkdV3ew630PNYGpACBBx8FLGJ/3RwMyKWrA4dBxVy+/KKf8BnHt/vSQP7PtPmP1j0VZfaE7cfdsIkz3PA+n5Io6cc/A+gDlBU4apqpk9mOLi3Z1Lx87U8374wz825rb7dmP3Ki/UgKh0f0TQjJpyLkMoC/tW9tLqeoRQN2iaBsF7VPUIjl3f4WQj0BZg9wK24py7aDxZkmZkyLmkNcRcLFVCCPA+wEuxkGjncxis2FBQGf3l3tDVh1C6UzmfNuHd7dSJ+OITFiNSV5THqetKR6+pARCk32Psug6bG+twzqGqGlTBo+YlOBZUIYBROmNM5b+73bOcUsm5dKV4FSpikNiVIoOD70UGQBnTlmvVcosUSxGSdj3oXBnDMpeoL7UEzaWzZrvrYUQgLtYpOZYdvKyK+lN8BbMqhBlAOUYmgjiHikfIKWFrawttu5jW1eh+573LKZ+npktmhvl8julsiqZqQOJQ1TWcCCbjcRHv9K9dYUSaKXXdHka87BWvOH+0toZH9WWBaE0f+vB33WOjc3+LiK8JtZ+IOICpKKBTEUGAS6qC9l3D2XSK2XQK76ugZhfUxueb4d4vldixgYG/j6FgG/isMAMff2DtUg703V78QXH+UiN4U6XdnZmm2BH0nTExMktmelgVN7ex/a19oT7Vru//6sB4ZXL6FYAdWMznDYGo61qEqoyoPMpYx2x3BFW6dWWvqRQTXHSVmYgeJKM/79Li91xO718mdxz7pvPHT0ftb5xDevDe0QHR+LzgZElkd8/IkHPZ7RfhuWb7cMyL2w6c/7+fEVsDs9f5I/eMXtq2i+8T7/axL50qJw4EA3PZ/4JZyQu1cpYnkwnY8WllqEi5wZZdJ2wC+JNk8e2L6fybk6YLxYmE4GHmICKIXYna8j6UwoUYTd0YE2nXttZ1rXQxUtu2gBnGkwmW/Z7TxVoZoSp8cKXDB0bXF4Nd1wKqIOdKRGmfEWtKn3K9ELY2N8G8jT179mE0ngDFWhbiPmkrwgzMFgvklBCcR13XECdQM3xqrbCrViUYYKUoA31yRE9Uiqs4nyNrRtM0ZUQLwKC9tx3BVQGmJRAdZhDXe99lhXMetfeoR00vGsilA5sz2BdrDABl1Cuu5OPCDEQzhb1r2rYfm8/nF+QUX101zV6RkkBhIKSug/cezpf3h4iwmM/RdS2asvRPi9l0mQhPO2ck5wLYeLTX3H/+nV+b/+h3//jbksg3EcmLWBzMMtJ8AVOFcw4SQlEj5wzNhuADVlb2UlVV7H11bkz5kjtuXnsvcOa6zAMDT0SGgm3gM2ZtbY1PHG2uIOR/6sRe6Rxf4IRd1kzWq9pSSnDeo24a1FVjZRqWPxFjfEeX0ge8uEtbda9nxlea4WLn3DhUgc3KX94heDgfyo0EgOsXqGMuuzxljMbwzpsIZzh3P5O8A5beprC/3k7TRw6ctzanMx0fdWbPpDSNe2pKdhWzEyKGaYZqKSKcE2PgkWzp3Sen7f0HzkBnwWyNTz40eYaT6Y/mnC93Tth5f3qHDGb9PlSCasmXJACaM1gYzgXkvOulVjSjDNoG058o5V88ub7+4Hx988UpK5aXV7C0UvzwnAdYuIwc+4X/sj/v1Hv/gKk+qDlPZvP5OYvpdJUc+To33C4WZRetblC5UMZ/ucN8NiuL81TC6Z1zCP2upDhXlJhmgDCoT7pomhHaRYuUS9cs5wzmhFnv51ZVFcR5aM5YzBdIXQu/sgLvPbouotvYQAgOrg+zF/Zg1wsD1MCupDeUore32zCDmSKmfsvPimq56+JpRWYIFWJvK1JXnxz7izBc05we3aLPw9V+X82jJEoUj8Gy+0hMWN27HzEmnS9mD26c2Pp9qnLyillV1z/YjEerIVS9craISkwzYio7gpubWxAhrGouRsdqgYN/SgjVtXb72r30KEfya2vQ7/3eeP+yn/y/YvTsdrEYzxczMDlUdf8auYyDDYDzHuKWAQBETGa2R5Qu3bpgc4JhLDrwJc5QsA18Rpitubix96kx59fGnL8xpe78FLMj4tOjz047GIDgHEyzzRczdSInmOiuru1EU/o2c3R5tnyZQSc5myNQUfEFX3aThEG9bUTpZDg4IuRgaLc3MdvZMhZp62b08aZubuFK3q5RP9Ts3TgBrLXn7Xk8F2qFzfurJanluTEvzokpETvXv2YDszOCtaZ6B0Tff+V5o+nn/t6BFhsrT6o8/iWsek6MnQOAxWyGRduByeB8KKNBKR2l2XQKJwLX76wxO4hQn1YAg9mWqt1CRr90Ykf/cnNze5nVMtD7yKlCLSPnMsZzUro5pdRTEHMUlg+quf8KkkUVwkVYWroqeP/kqqkvVc17WWjkmD2I2FTRLTpMd3agZhgvLaOqa5h3pVPGRSmsQC8G4NN7jT7U2NN36UQEZoqu6zCdTtG1LVZWVjHqX38VPLyTsmOVM06dOI6dnW2srK5i31lnl+KOy2PEGGFqcLvKTFjpWPa5qDlnBGIICxbzBbZ3tjGfleiv5ZWVfn/PjJiKGIJBu11N4V1FbjHNjV2H2XQKoC+4mMuaQM6I7RzOB9TjFfKVManZw7P8yLETJ7auPrDnTc1oQqGufiB1adWSnu6qaZ/lqr3wISXri8eGmvFEXJALvYTnYdK8F8ADj/b6+y//ZaX9n/559Y44n71yc2vzRbPpDpaXV9C4CWBAXLRld5MYPlRwItCckLPCgJF4uvzcsT8LQ8E28CXOULAN/KOYvdHPT24808heT8RfS6znqKpADQQ63Ump0O+aoSyVz+bzvLy8PA9Vc+6inV+VUjorhDAC4LvehkBESsakc3BcFt2TJsTYwVyJllJLYGbzIcy6tv245vx2zfjDbp7uWF3MN+nzmfN5xs/lGk83li/KcXZ9jrlucwfnPKqqKeeSzcz0VFR9N3z78TMx0j32sbV9XO98Hxl9/XQ+reezKVgYsUtouxZNXWNpZRXjqoYLvu/6zJFSgleFUw8Wh+DDbm77NhH/KSH//+pVvPfCPT/a3XvvTy3X4sEwOOfL4nzvzA+SYspKDLOMqAmUzVJOx7YW0/dW1eyEWQhjsrFrxgcIcplz/inOyTUsdHFO6bycdUXE1XXTuJwVQsXCo9hXaImnZIb04fIiAgYXzzhx5WdyKp3EnNH2o0/XjwaJ6XQ3zkrSfHkNOWFnZxMxdiUxwAdACLGNSCmCSruvWFT0dhno98FS2UUEMaOLEdtbW7axuWmjyTiu7FltSXgm5Ge1+CkTKzHGOadzurZdqqoavh+jpq7D5vopbG1vYby0hKXe6kPNkFX7gstABnjnBeOxb85lvfG6/yObrT2wfqx+IwGA5e9X1T2US2SY9iPcuh6V0TF2R969qpZ4JVl67vrOiWceetsPHb/i5b/UPprrb21tTb/3e3/kPteF38yan+pDtaeqG0AN8/kM0+1NLBYLVNUIK3v3oWpqaFZ07QI5acWOLgpOz7/ppoP33HjjmTOOHhh4ojEUbAP/IHb7Wlg/vnk9qb1OdfHVdV3tYyKB9tYCqhCUrEfvPIgEOad+wwzMImcR4SxNVpuqgIhsd2dISoxRjBFqCvVl9BNjV3apShfAZrPpFEZ3eC9/GKr6lrjId20ssH7ttf/qCbfTcs89e/0F+/WZSnw5MzP3prRdihBmc+DEJHdn6967unzH5uf6fEeO/Egz25q/0rb1u1Lsljc2NrFYzDFZWkZdV5hMljBZWsJoPEHlq2KPAgBEmM+mWF9vUTUV9u7dX0o10qmwv5VEfnGy6t5L9C9aABiPxxj7ADMga8ZsOkXbtmjqBuKoH2kmaIq9/Qdl1TQ7efLI7Prr3zxHiR/aNFt7BMdw58Mcbl0W2hvZLmLGVQS5mr2/NoRwaU5xbxdjmG5vnRYzBM/FdBgAepEKSbH1IGUkTb0/mWFrcwPbm+sIVY2V5RW4ELCYzxHbrt+NJAQQfFXhrLPPAQGYTafoFnO08znadlG8y6wUhtou+g5Rb8lBhlDVRX3JZCC2UIW4vLqyISE8VDX1kXo0+QQ796CHPZKNTolqlzQvxS4+pYvtS6qq+jIiqnYVwzFFEBGqUBXfQQDJFNNZ6RI6F6CpQyYSEvF7sPs2rqnZ2gPzY/bGNgNq+QfZeCXFiNlshvliAccO4/EYoapQZqWGlBKRmjPQ5Snpi/351e0ADj/a6/CCC96wuP8jP/H20WjpG30VvrbyFdrFAhsbp7C1tVn2Jg3w0/q011+ZyhunLp+bUrp0ZWXzfQA+547zwMATlaFgG/h7MfvFaudk+wKK+fs15xuy6t6cHbMwmAkxpqL0U+3HNMXjCs5haXkV9XgidRVGUFBVV2QIEMeAlW/4zB5ZM9puhnYxg3ceo/EYLGKiagQ+RbAP5RjfbqbvSm58aLOV7Wuf+r8/4Qq1Xc49t13JnftyJttT1zWyKdrFHNvbx1HVFfat7l03Ce/bnLq795z1uXUTbrnlBrf1ULyhHvsf8C6cS2AKVQsXQhkDjkZlRywUf7OsirZbYDFfIOeMRdeiTS0qbhBztMXmfIcZf7YyXvoPq/vOey/Ra/9Gx0Wz9RmkipyLPYUBEF8UpW23gKaE4D1EXFKl+dGjW3/jNRKtJQAJwMxs7US4D/dt7qneX8dqj3l3CRE9O8b2edOd7WcvFotzq6rxu55rmovKkJnBTk97keWcYJbLuDeVvTNxDqGuEUIFzRmbG+vY2d5BCN5G44l570lEaGl5GcKMrc0tCDN2trew6BYlA9PXyFnRtrOym5YzZrMpAMXS8qotr+5JTTPaIMcPVK65W1y4I4Tpx0Ti/VXAI6JuZyQ6P5bm+cAB6MMP74h3+OPYubeL0L/IXfdK9sGx90WpGwKaZlREDqqwXDproapQNeVYUp4zE/vpfIs/5Zyq2doDG3fGN7nKjUH0z6bTndFDx45iZzrDWfvPwdLyCnxVA1z2FkEM0wQzXYXIC7mW99922zc8fN11vzd7NNciEezw4froqOGbWfm5ZrqaYixK2LpB3YxQVUW5qjGVRAfn4DiQWt5rLV155b6LlzEUbANfwgwF28CnxewXmnare5EZvj+l9CLNeUXEMROBUZRpOc+LlxRQFq3JkFKppUII5BGQNRMso6oC0Jt6AmXniEhgUsxS25aQcrKslisvR8njvST8jhjtfZ3IkYsvlh2in3pMzW4/53N600E5vt5e5X1+JolUxWcOWMzn2No8acvYk9T0Xu/o3RddNDrxub1/a3zPR7eeCnU/UtXjpzRNwzBDPZ4gxeKNJlLGX203x2w+R86p7GAxoxmNQSIAFHUzspzidDabvTNnfYNJ/Z499Nq/MYbuuhhj6jR4IXHF40zEIYQKQlxipQwQ5+GchxMXNWPGp/TvLUr7cfACwMLW1jbx03sf3H5w+qGU0jviIr2QQK9g4i9LXVpilCSC2WwGcYJmPIJI6RdqSog5gai3DPEOzq+WLE3TXhkLhOAsVNW0DvVxJ7LXcl4hV17DaDRC15Vdq+ArhCpAuCgezarTO2vOOYBgMXULqN3pvP+TbLjNkb+HdHSsWt3aOuecnQ5YzkT/86cbd08Pve2Htuej7pfbNl1bj+galtIBA1BiqwDAAC+C5eWVIoTwRTSRYqJM0Tdj5/72uTRbO3L8AfmPsV0089nsO9u2GxGKmS+JQ4zFsqTkjXqABLHrPKV4RY75ZUt88Z1max95tGP6iy9eazceWvtTqL1HU34ZYFiaLJXc2aoq6w8pA6rA6Qg0ARsa+HCFX95/DoCH8LgWFA0MfP4YCraBv8PRo2ujo0e2Xlo79/qc7AWLxXypazse9S7wpgqNESklCAmgQE6llmrbFmaGkRRD1NguwCSQfmcmqxbjVSKADGKEpm6MiLqU02HHciuI/sTMPthp98j+c+bTvY9Da45Hw7FL91SW0nOE+Uk5JYpdBAnD+4A9e/ejGY13mOUDNpXbafn1jzrbtIgM9l+4slL9cIrxy4nJmym895iEgHY+LyPL7S1sbCTMZjNMZ1N4H7Bv3z6M9+xFVdWYTJaA4n8xb9v2z2fznTccX99699Of/ct/Z2fwwQdPpnP2jtVJg+DKLmIxZC0+a6oKFilKUSYz02jAnJN9Rl1EWltTrKEF0Nota5vHK/tEsxI+SODvSine6FhWVO10/iYZTvupdV2Ltm1BAoRQPN1Sf72iH6MurywbbDWJl7sYuEkNLzK1l1nOor36GVZilYovGqFt54AZ6tEIPlQQN8eK2wPvnaWcH6iq+i1q9e/SzsPH7tnZWDz72W9Kn4mX2BUv/6X2rru+58Mj3fcHXbu4OnWRhAlVFaA5I5qBUQyCq7qG9l6ERcUqbFl91MXf+bu9FG0H7z381+f9QreItHfvvn8SqtFoNBojx4TpfIrpdBt1XWHPWWfBBU/EZDBMmmb0/OXlPX+9fax6EMCj+jJBBDt06OSx/dW+t+acnw/DSlXXRfAiDGjp0LJzZV3Cik1L6pJPOV9CzBfbbW+8g6579J+NgYEnMkPBNvA3+PDbv2t87N4HvmayPPk+myw9h4Dlros0XxQrsKoewTtXdod8QPD+9O5OCeqW03E7uwIE7lVwOUVAy80l5gSzaMK8yew+2jSjP7UcbyVLd262mxsHDqzNv5iMMs1Ad35w6axmMXue5rSUYsSinaMZjTFZWsLyyooyuyNE9u763Pqhz+W5NjbesOKQv7eqwje07aLZXt9EqCqsru6BeFcCwpsaW5stTp44gdl8Ch9qLC/XqPsxoZcAg5ppmhnwriD+Dac26T0vfvGnz3S8Yt8+dBUjVH1cVZ+hOZ/PsVgsICRYWlnuVZqGrBaTdvMTYfuzLsapxIqdOnToh95fqztOFJZE5FU5d2RmiF1Eu1gAMMSuw9bWJmKOGE3GIGLEuEDbzkH16HSwfF3X8KHeJqJbSdNvzObzu41wTZfzJdvbW9iZbmPUm9gSCF3XIbUlfi1UFVCXEZ733irv22z60ZRx6yceuvvwdde96bMuMK68cnv20MfP/dP5bP49m+un9tZ1g1Dt65W2AFSRk33yA9L7v1XikL35rPxp/24nujnbTQfv+cgFe35h/1nnoB4tfafl3MTFApo/KZLor1kYMXlfsQvhfOf8y0ztziNHfuSWCy98w6PyBrz88l/qHj70E7eq6Xty1q+VXjTBBMCoF95IGaOrIcdcvtplPZthV90fDr8TwPrn8vkYGHiiMhRsA6d5+5u/evzQzvrLxrzyA5PlpS/zzo2EHTEJpPd9yimVYPDe/2o3dmhX7RkqFOf5mPobWID0XmrMAgOs67q8WMyOac4fGI3Gf9yMm/eNOBzG6vY20RenOeatt94g+5dm125s5mu9E+99yb4sQepsXvzMiD6Uk/4V/a1x42eD2S8026foIMFeo2qrseuwaBfFnoNKuLt4h+CqUnjEiGY0wvLKHiwvL5WUCnYAsUHTLJv+OUN/YeXsnT9//jlv+PuPay8gXfFZA+x04b41nWJjYwPj8QST5WVk3S3sOTr186d2T87ArY/qtV5xxS+1t9++du+5Y/mtlPJXzeazfVtbG/3if/H1K+HppQBRNXSxA2Bo6gZVXRU1Z84ggjpx9xLxO6q92w+tP9j+hYvy39p5+4OLxTzArOSohoCcMpwIZDSG79Mb2rZF7MPNE1FnsHst7zzwaIq1ws2aae3jIt3HQwh7fW9yu1vw6q7Dcv+6iAzKCgXIQF6t+3v/bqcbb85mBw9t3/fkXwh1hRTTP1HVUaPjEvsWHGBAalvEbtfUVyqYe5paeoXvJkcOHsRHb74Zn/WOJRHs8C0PHQ0Hzn6rOHmOme2NsQMngg/1/7+9P4+366zvQ//PM615z+ccnaNZtuRJ8oQM2A6D3YQECE6aFPmXZn7dttDSkldDM7R99ZadmwZIQoHALa19S0pye9teK+lNAoUAHmRjG9tY4BE8yJas4RydcU9rXs/w+2MfE0M8aDKanvefeumsvZ6lfba++xm+H5DV9izjtAgNLTVACKGc1kqltldx2YEt2KzzlC3YLADAo1/58bBXhe82YB9o1BtvbDbage/7hDEGVxt4vkRVloDWq5uCCYzSqIweh2yvxvQopcYn6AhBVG+Acw6lJLRRBtCF1OqZLCvuS5PkLqHkIwTOfPBMlZLTHMb+etux411+sty/dhQP18IY4rguhCOglUaRZ4ZSepRSdm+q9An3uzKmy4eL+gZi9D8rtFqbZTkxABqNNqIohCOc1cJ7vCRdi+rggo9zPl1vtbBWIMQYY1RuiLlfA5+spUfuJZ1PvnoRubIM5XQgOcAFB8y45QYM4Hke/CBEWUmkSQLHcYwfBBXnNHu+1Tup5e7t2yGPPl8eqSo1KKuyo4waZ2+6HpjgIIyihgbieIThYABtNBqNJmqtFgQfZ50yYQylLDFGP+Bx+m1Cutrctqu/7+Lp22Sp3kEIdvgvbornAoyJ1VOg4zgrbTSyJEGaJiBhiKqSssiTlf2L3z3hhIrxJn0s+F6wz+HijUZrZEkGHccQwgEX46gtg/EeSFVW41KNO0RBC51T/urXHxdt5fL8Jxij3BHiFxghDuMvxmONs1SHwx600mS1L1wrS9MbluePPvur775hcffuPSc0E7z5hs8X80//xp2EiYe0Me+EMTAgKIvxHkGK8Qnycf+5CpxzcCFcVNWFwjObzW279hPb3sM6D9mCzcLCEx+IVlL+7noRf8B1/Ws6k1OBHwaE0nHcD6NAZUoUeQqiAWe1MWhRlKiqAgYGfhAhajSgtIFU489SpaThnBsCLBmjH9UGeyDVfbqQz8RlPNi589ZzatnzlXS7XeoRZz1ttq71ozBQq6HnhFJIqo1UVZ5l6eMa9OH/6/P/9oT+kzemS+eex1WuW/0GgEuTJGFFkUEIB34UQTgOlFaAJqBEj6OMVpewlZSQVQFwDkqpAUxmjLzfUPPvG218nXRee/lrZRngrQwlytXMUAEYgjCoIao1wRjDYDBAv7eMMAzRIaISvpPv2rX7pAq23bufJDdce7VHaBUZAghXwPd9eMF4r2WZ5yjLClVVYjQcn/AcJxtMQQgxbo+hKi21OkSq4k60hsvAeBbq2S998Dtsmt9GKS4glAaUsXGfN4xTPZRWoC8u+xMGzhyQcTqpzgtV3nff7MkWFbkQdB/lnsyyjA+HA+R5Cs/z0Wp34Pp0NVFh3AAXBqCEEgXDNehrfrYTcrMyd3X3JZc1PwWgzgX/KcqYeDFY3pi/ySullBGjDcuzbGOapTcFrejAo1/58S9f+RNfPe5Tm+NiNJrzi3x3JeW1wnGblHOkSYxBbwWUENQb44K6UgUKWZCA1SgVYq2CvvSxhvcN2NOi1nnIFmznuaNH/zDUWfqTAc3/qR+4b3A9L3Ach5RFAWMKCGccHVPmOeLhEIJxOKIJwhgMKlRSoyxzgDD4tTqE48IPIlPkSZKl6SEmxAOU0bu0VN+mBT0yO9LJjh0fOSeXPV/Jhz88w+YPrVzlCHpJ4IdUiQpFXkCPl9mM1mJBZsV9y9ncgW4Xx13AGAMymHc3O475dRByPQgEZQyO660uWwNKVtBkNctSKZRlBcE5lFYYjUbjIPB63biOmwO431Dz8bDeuPtYl2eXsQJ3JQcBRRhGqNXqEI6A6/tgnEMqCbO6dM6ZA0JoVaU6P9mC/YILWtR1nY3GiCZ3XBg9jtLKswy93hKqshpnmFKKer0Brwrg+dH3UheKPDWDfj8FwUOB732zvuYPvvf8t76rE+9/MvsrIchPGmPeRIUglPLV5zeOSXPEOJ3DcdzVPZsUnu8TQgSbnNwO4DsnPLbl5VlVa6/Zp0BySmnkuh6krFBVFfI8h3D9cWGlNBijIKDQ2hBCIIRwj+mzndzYlcbc8t3BXPZRAUEIITcBEONcYIZGowU63psHUALHcdxmq31Fo9H42SB0jjxxW2Pvjpt3H/fv8+bN3eLAI//8Tu44D1BK38kYA6Pjg0oaZtzWQwg4yoOUEgSGUM5aVV5dVgW9DmzBZp2HbMF2Hpud7QZCmnczN/iAoPwaY7QHGJJnKUbxCCAEzVYbnud+r2knZ3y8cd1xwDiD6zqQsoLrecZxXM24u0R882hZ5feoorxXFuX+1gxbDlGdkUHsPwyHD8/VPIi3aoXOaizTakSTMYygEo7zlMmrBy9/aml4Itc/ePBfNnmZ/yPhOu9xheMRQkFrbLWBrEaRF8jzHGR17+Hqa6+e2GQQ4zxHA0PKqpIPME3+fX0S9xzPXjopR8bRnmSuGAd6r54SHh+uGF8mjGqo1Rvg49B5qYw+oc75L7Wzs5bF1FxMqBBcuFCqQjIaYPbIYSwuzSH0a5ieWYsoqoExDlAC1/PBmYBSJfI8N8Nh/2hRZne5+1rzL702IV199NHf2IfQ/wtjyHZKWQSs9pQwBozS723Q55zBOA5AKBzhUM/x3He/ezsHdp/w2J5/vqfXhI0XOBW5cJ2o0W7D8zwURQnGxWo7khcP9ozvQxkNwriYXjt1zJ/thLy/euKJ7uPr2/SjjqHEGH2T1oqDjNt7sNUZda01cT3fMOHUjDZvYRSH3Z3NZdPdvo90j+93e3xiVM2F1N3NGL8OQMPzfUytmR6feBUuKCEIwhDj4pFCK+WpqtwmY7Fx1y4cOZE9dJZ1NqMnfwnrbHT00d8Iy0H+LmL0P+GC73R9zxOOQwgZn4ArqnIc94NxJ37X81BvteH7PnRVQVcSnHEEYWBqjfowCMJHOMEtlNJfc1zxoYj5n12fLN/fXvsvDxHym8n5WqwZ06U+dy9kgr1BONwFxr3ohOOAMWLKslwpi/LBUTJ89kT25RhzS+DT6BekUr9cVrJmDEAoAediPCtCgDzPsLy8iPn5o1haWkSaxOOQcsHhui7qjaaJ6s2cC/aAlOrj9clDdxPyoeNamt269a16Zv2GbGZ6xjRaLXi+B8YYkiTB4RcO4OjcHChjCKIIjMJURZ5TQk4+UqwJT2lyOQEhbLVJbn/QG7encD3U6w24rj+O//J91KI6PNcHHX/0GeE4eRhGj7gI7r/m/X/7gMCaK6JMVuqvCfA4ATRWW2gIIeAFPoTjgHM+nk0MfDi+C0IJl0Y1QsA7maHt2rVbs0rPg5IRoRSO68IPI9QaTfhRbdz6AljN3B0friCEUAoiXNcc15fxHTu65eGV7DFD9O8VefGFIi/ki73lZFlBVhVkUUArRTillBJMK6XfSSl71+KvTE6dyPi2bv1M6XC2B8Z8wygNx3UR1sb/XqqSkFW12qBYrDZbFjwK6xs7EzMX33DDDf5Jv3cs6yxjZ9jOQ+bQJ/wFmbzDVfIDBvqNWquAUUqEI0AoQUgIhD9u3+G6zjjMmxgQypAXCfI0NmGtkUX12hHC6LcJIfcQpb8lZbE/zL0hZpZyt3Z+Fmg/6PDhgVvn028mnF/AmKCAgVQVtFZmNIpVlibPuS69//BK57iDrZ94oussvLD4LkLwz5gQ05RSUpYldK4gGAMX45ByQsm4WSynoIx8788opRBcGEZMoSQelFr9YYG5uwg5/pYNawAMo5rhjIBQCmLI94LFiyJf3ROloJQ0g/5Ay6rMGu3OSS2NG2NIvPLv11KYrUpLaILVVAODeqOJKBqfetWrqQ2g47EzPj7dLKUyAFsIwvB2fnT/yx72WJ1le5Y3g79Sstouq6putIHgHIRQSDmO2yIA5GqyAxyHEUbbuUzCkxkfIcDhR8tYSzrLudgCMR5fWRQwlEJKtZocwUHBYBTAwUEZ4YKy4/5s37GjWz7xRPfxGst/D1pRAecnq7LiSqYglI5nTjmBMZoYJTmArUzQn/Gd/Kgxn/sSIf9gdJzjM/v3YzYy6s8Fc97MGGtRSiALoMgzKC0BE0E4AoQQAkLhB+EEE2LHW3a+6SvAnvhknq9lnW1swXaeMabrJMv4kdAE/0hV8o1K6bAqC6I4X/1A5vCFAw/jWB8CvRoflZvRYFDlcTKvpfy243oPExU9wEGfcwVfwtRKuhorZK0yBiRb3tDWwHWUsQYMgcY4TihNEiwuLoxGo8E3eUi+c+ONnzmuZ3fbbbtYy8M1w2HvQ0qZC70goL4foCoLlGUJ4zrgIoAQArVaDZ7nQOvxQQNKKVzXgxCOMURXqpLfNIb+YS5He060v9Y8AF6VADjo6syMkhKe52Ht2nXgXMARDpSUKMpCV1WVOxk7uQaoe36HpVvIxYyxtYSOW89wztFstseh5r4/bjRbliCMoawqDAeD1RlIZgBUjLAnQOl929795Vdcnl1zRZQt7C++nOXqp7MsfbPDHVJvNABIDAdDVGUOxji0UWCMIyR17lIxVZSsDoxjMU/0LXT04FLuTkZHGRUQjkAcx1heWoRUEpWs4Ls+arU6tFbggqPRaBGHUUFE84Q+28dF267Hm+7W3yeM1IxSN2qtiWAcbLXn4ji6CqCEelD6qrIsbi5fmF28a/+v3n/jls8f16zp5s3dYv7pf3OHF5KHCPATxhhQNi4OVSHHjbYrCW00XM8HE07oMXrZdIdu6HYxeyJ7Pi3rbGULtvOIMV2eHa1fBYf+Q07pdQSIjClJlhUoRzGE6yAIAnDHAci4MwOACgbzRV48NRz1H6yy4mEH/ClCqsVwbRYDv12dDyc9T8wuOkyyS4LAvZKCirLKIZWE4MJ4rifDMDxQqPLeJHto8Xiu2u126d95i3+RluW/KAfVzjRJOWVsdTZJQggBx/ch+GqTWjqO+NF6nHbgui44FwbElErpbyulP57pZM/atd0Tyokcm4eUdUBr5EWOeDQEYxRBGKHRbI9D0o2GUgZBVFNK6oyS2kntYfuGOxCdnrkyjKKG7wcAxgv4jI/7lVFKYZTC6i58AOOEg2Q0RFWVRjjuSi0M7wpy9vyrvQ4hXb2w0H0uOzL6YpYll1e8DIXrAgYoiwxKKRBCQRmDEByUg4GYCZ877V27QE9mr5UXydx13DnhiPESKCGQq7NsWivwIAKMQpolCEgA5ghCORcg+oQ/23fs2F2aZz/9rSWy9O+EI6jrOG8jlFGlNfJsnO7geB6hgCnLogbgeimrxS3lZP+uu7qP3XgcLXoIgTH7+dyI0t1aqTdro5sAEIQBHNcBIRRKyvGpXCVBKOMw2EKIuvg977np0W73xLJNLetsZAu284QxhhSDz2wyyH9Z5uZGw2mDEBClNaSUyLMU2oxzIw1MrKpqQRvzlBDiIUnI49KYp4qkWEgZTe6++49L+832tc3Pb/KkKq4tcmyAD2KMAlndQB2GUTLNxMPUrz329u3/7ZgLF2NAlg5hmhr9z0DZO8Ko7grHReD542U6YwC6GgWmDbI0wfLKMpI0geMITExOIggCQ4BKK/MIlPpUE/SO1kkVa8D8PNB0C5TUoLeygqXlBfhegBnhwHHGxY1WCpQwBH5gYJBRwk9mSZToeNCquPtmJX2mtIbKcxRVCRgD6lAoUyLPc1RSQsOAU45aVAMlMEkaS4B8tyiruy++/tOvOau4Z8+T2ZXb1n7Vc/2btVZXDAd9aKVRVeMhcM4hhAsDkKosKQiaYeis/cAHflXs3v35Ey7Y3PXTVUNMHPU8D4wz1BoNGAJU+fgtE4bhamNbB67nEkYZMVIJchIFGwCQbb9WHLr/1x9obmz+HwD9sNL6rVmS0OXleTDG0ZmchiaU5FkMSsQEddg7QPXyRn9uZAyeI+Q4Ph82dwt19KN36LJ6SFbVj9PVgpsTgiLPkaUplFYwAHGMASV0UmtzZUQnvwrAFmzWecMWbOeLw5/0tIt35bJ8TxrHHYDQqFYDdxzjKV1RSgeU8+cIo08zkMcKUz1apPow64jFeIjs8/+NF93un9oi7RgZA7J4uDPjOuZa7jghJQxUEBhOIJjQBOQIC537K1rMHteFex+rOw7+t0rpXUrrKIoiMN4EIxTGaFRVhTSOUeYZPNfBcDjA0aNHIKXE5OQUGGUGgDTAY4SoT+XEfKk2/Vsn3SLh6NF5qCgEZeMeZUFQg++HIJSjLCtwpsEd/uJGeWW0yRxKT3iGzZgu2fd4vInAXA4YxKMhlFIAJQiDcHwa1mhQSsG4QFlUKE0J3/XQak+g0Wz3lZL3jKr8mWN5vZtv3q0Wn/r9pzIz+F9KVpcWRSa0USiKDGVZgnMK13OhpII2mnAu6pqYzRgfPDjhwxVbt+7Q8dzyEmCkrCrOOEe90QSi8cwh4wxSKri+DwKgzDOUJOeuf3IFGwBsuP6T2aFDv/5Ak0/8jizLDxOKtzHGiB6fGAUYRSUVcV3BBBPrVV7eJI1c+u7eX/wfwH+dO9bXGZ8YnZ1zMvFnspLXBWFYcxwHcTxCr7eCqirhet54tlhVEI4bUEq3R1640RjM2Rl+63xhC7bzhVdxVZFmlib9hYW5BCA55Wyp7rZf8P3gGc7Ic8rowy5js8UQydGkSrdv79rlzhO19xbuXZjuoJpsJ5RSYgzGJ0SZoYRlIHhUZtXemZnuMe8ZM+bT7vLR0U9Tw/4BCOlogAhK4QoHlDBItbr5vSwgSwMYDUKAWm2c4Vmv142qKpmm8eO+G3wypO6XgslfO66N4q+mKIrxPqpmazzzQyiMVtBagXAOzgUIIZBSalCaab844T2PTz4JXnPEGw1MpyxKpFkKxjnq9Tpc3wOjDLIaB6IzAHk+3tsnmTTMGMkcsc9xvDvXXvB/HPPG9bseezh9+xsu+V/a4T/jB/4lWisIwVHkGYIggOu645k8LmAIwkHc28S5UwfQP/GnOmfAneUiy9M0SesvLmv7vg/mCBitQUFgyDg2TqkKRhuhsXxKPts3bBgXbRFt/o4XRB+eFM5b0zSlWmtwhyIMaxCcE8aYkJRtdbj3XqXd3oP/82e+8Oaf/f+Wj/V1tm79TPnE/e+702H0ASmdd1BKkaYpkjiG4zrwPB9cOC/GcnFGyRZHNLZ/4xu//ihwYvsuLetsYwu288XEllTOPfN/Z0V2RzzqE2Vo0mlODElDZpUj8uaUzLEHktz4IXtw4FS4oBdwhG8j1Expo4k2495nhhhjqJknhN6fs/yF2jEWxMbcwJ97/Pm/U0j9IUqwgTBOQShCLwBnDI6gIITAcV1E9ca4dxUjIIxh0vPgua4xxlSj0fBxkptPUMd8Mdr2myfU9+3lXDm9BkOMo5rqjSaCsAalShRZCoDBcR1wLsbd+B2qDFhWVvqEDx20BeqGseuVUi7jDH4QjNteBOO9bEkSr8YaCbieiyAMIISDMs8wGPRHBubexrR4sn0cX0huvnm3OvRk97t+yO5wPOdCAyMc14E24yKNs3FfNsoYsiQVZVFuqDE1DeDgiT/ZJ43SV/WUkkmRp/WqqhAGEeB5UJUcB7arv1lx5VwQZQwjlcvNOLb1pL9wvVi0tcTUv9Ou+xHCxRtVNW68TLkAI2R8yCPwPEeIy7XGz7nbgsG9f/lbt7/lp//gmL4QjDNGyyOm2dhNKX0jYazpByGaRiMIQoS1OgilKLOUlEUGo/WE74dXXrxxy1cAnHCcm2WdTWzBdp4g5GZlDA4+8vQNRxAC99y9R/+dm+w+tNeDMV1a9rxNhJprjDZ+VZUo8hyyKo0BCsf1vhv60UMTE9kxLUUa06VLs+IK4Y4+NBgtXZZlKRsXCBy0AURhBM0VjNbgnCGMImhjUBYZpNFwPc94fqC0rJ6Mqf7k4guLX7zw3Z88ZcUaAGDNGoheH5SS1Q3/BAQc2hGgIN/rGUYpAwjXWsq8KHsn9OXAmC498my5XmtyhdYgru8jEuNN+QQESRxjcX4OlDJMTq6BcBxQxsGEhtJa62R4IEn7d/zZ5/6f/vG+9voFDGfXVX8plbzJ9ZyNrueDkBcPgo7TASghEK5gUa221vOcDbfdtmvvzSecfbndVLoaQJvMdT04woXruajKAnGSQEsFSggoo+OUEd8H55SRavWUxSmyYcMnMzPbvT91oo+4xnxYc36V0RpKSYAycCGI4/pGURnCmDc6WgwvuVgP9t/1q/dvufHYTo5uvuHzxQuP/bPbkyx72NX6x1zPg+d7GOfuOgAIjKxQZAkqXQWOIy/z1XDzbbftmjvx52tZZw9bsJ1Hxt+299gZtNfZvn3LYm27+UZdqa2VrGiaxBgOBijy1GitVrwwur+YkM+Fx9BM2BiQwUKwxXPoh2iN/ojSRvhZMC6CCIXnuKtH7cYNjgHAaI2qLDAcDAAjjef5knH+pKD4ZHO6+YULt3/q1BZrADD/GHLVBqMcjhhniRIAnI2TFaQcn2zkjBuAaFVVWVIsn+B/sjMM5sDlRZ5vUgZwg/FyGWMMZVFi0OthMByg3eqshtozGG2gitKUZZl6wvsGKv/REzk4Q27syn2P/sajjJi7tHJ/yXE9CmMglQSlBK7ngguHCCFILQw7pfQuuOKKNQGw+wSXnrsm7//aQCuRMsZgGJCmCfIsw3A4ACEUvjdu30LpOG6LUUFFyATQJUD3lG1pIGu7qVn4D7ePWEVg6L+tZHWVkhKEA4AAoYQQSqDLqlGWxVulUkN/3Uxinuh+m+zovuYBk/EsWzw3ImZ3VXpvbjRYzQ98UELGB0koget6MFENVSW5VmpTnCbb3VHxKIBTtrRvWWcqW7BZ1ik2M3NhUxXmLUarllIKWhtwzoxx3UpW1dNlnn5j/fq5wbFcazT38Q4h6gOGkvcIIfxGo4korAEwMFJDA5BKAhLjwgQEWRpjMOghzzLjh74kwJOGkE9kVP5Vp9099cUaAF35RghRMMpQFQUGRQEDAy7GBw3KqkSaJDBawRhUsqryK67VJzTD++STc27N4W9hDvEZCDhzwCgHYxyUKjDuoNFso9FqgVIyPslpYOLRQCej0UGXi9t3/Oj642ql8lL9atirwfkLDfMTeV5MSylRVRWEYGh1OmDCAQwIITRyGd22aQ1r4wQLCkJgnv2W6TMmEwDIiwyDfg9KjXu++b6PKKrDcdxx0cYYQEDHOVxPklP970ym/mlsFrpfW9aMqEr+7wbmKrLam40AUFKSIstolqUThLF3CM7iBV5l5q4bvkNufO0vi5tv+HwxvOcffs3zvYcY5z8KYyCrCsYAXAgQRuH6IWGOgizLCQp95ZYrLvjaiT5fyzqb2GgqyzqFbrttFytTuU1rcxVhzPW8ALV6AxOTU2bN9Lr+5Jq1D7qN6GlCXnsJxyz+fo0K8ssG5ueLLKvHwxFkWYJzBs8PwF0HZVlg2O8jiRO8WDgsLS3g6PwRo6F1EIbPCMf5VGmyv2q3/+UxFYknYqazVrearbQW1WGUxvLSEubmjmBpcQGDfg+j4QBZmiAejdBbXipX+svZnj3PHPfsjzEgDV/OOI6zs95skVq99r0cTS0VBBeYmlqDDes3IfBDDIZDLC8tIktTcMYz1/EeptJ5+GSi0q655tZKgT9oQB/UIDAg46a9jgPGBYwBlFIwxniE0i1pkq7FSXzWtmso/CDoiXEhCEIY/CBEq9VBZ2IKYa22unTogFEKagzVshLA9lNesAEAmerG2lVfrZT8XQLyCCiDIQRa6dWDD5owRhmjbAaV+kkCtmu49Ue3GNN9zWdACMwVa/1ZN6jtppwlxgAGZDUxoxj3n1stDglFALAdU7XmZnPbLvZ6jNWyziS2YLOsU2jXrusch7A3UWI2UkKIoAy+6xnfD3QY1p6Posb9Wjdf8/ScefbTbgzxk5Syf2wMptIkJmk8glYKnHFwNl4CK4sSSRxDVeNibXlpCYuLc6YscuW53rOO436W+Pp1LdZe5HmhdlwHXHAIMV4adTwXnu8jCEK0OxOYXDNlmu122ao1ssXFyRNYruuyWlC70g+CC3zHA+cccnX2Lk0TGKMQBAGCIICUEqNBH0kcQ2tjoiia7XRadwx4f/74X/f7aa+5XGvU/rzZavRbnTbanQ4azda4Q7+sUOXFOEuJkXWEsgv37+86J/pa7dpG5TnhMuMCvh9iYmIK7dYk6vUGHNdd7Qf3Yv1PIJViVVkK7Hm9/qWBqalu7Kr4q5w5v0so+05VVSjKAoQAXuAjCGvE930uHGcjY/zvEuBne8+b9RgnP7y6rZ8pC1ncmcXJg1mawmgNqRTi4RBZkkDJCnkaI08zboze7Lq44vkLLohev9Fa1pnBFmyWdYoYA5L3yRpDyPWE0DoMoI0CjAYFhsSoh5M4e2LHju6rno40d93A553eW/Is+xeVklsoY9T3AtRqDfhBCEoZZFmiygtopQEYFGWJ4XCI4WDZEFA9MTG1L/DD/xAWuK3Z/Fe913/0mwCKcX8uA9TrTayZnka91oAQAlFUQ7PVRqs9icmJqWJyYjrbtWv7ieyv8hzHebMrnDqlFDAGSZxgcWEBg14PyShGkecoyxLQBlFUQ6vdMX7gZ5SxRyLPuf+aa249uUgsANu3dytj6L1G42GjNMjqnkIY873TmwAoNZgQVFzsSad2wi9WMU0ZXRyfeA1Qa7QQRhG4EKCEgTM2Llylwmg4Qm9lhS4tLYm9tdnXZYbtRVM7Phubuv+1Kss+vrK0+Pz80TkMhwNUVQU9LrKIUlIog60G2MU856cXD/ze9GtdlxAY6i8eklreprUeSilRZBmqMl8NuidQShMYQzhnbcb5G2pMnFAAvWWdTeweNss6ZXbRo/PLl8CoyxljjDMO1xFwPV8Tyg4VefGN/MDTR/Eq2ZLGdGk8Ly4xcfWhpIivKMuKR/UagqgGstrXLEtSpEmCLEuQphlKKZEVJVzXQRjVTb3Res5xnc96Df9/1NZ2j7kX1ilBCbgjIOi4xUUSD5GmKWr1BlwvAOMcxKelccoU+JcG6B7zpY0BGcyzSUHNjwCaGWgYpVEWBZLhEKVwIKsKZV7CcR0IxuE2WmBCGK3NUp5ndy18d/GUtIAYB5c/N1cu1//CEFwXBlHoUw+EcsBoABqyqlDJKlJKXVyWoykASziBXNF9+X7dZhMrlHMjhCBccGitUBYlCAzY6sGOXq+Pxfk5FGXGQChfyJ5+XQs2AJic/O3RY1//J3+RjJZ5niW/PXLcCxu1BoIgWD0IA4RcuKDsUsr433cFSc3wI39B6v/6Vd+Xmzd/vjj0xG/e7vrewxTk78hKgjMBx/XAHRe+MSAEcDzPZUxc6tXNxXfd1d1/PLFYlnW2sTNslnWK3HdfLViYO/KW/c/tm3lu3z5y5PAhLC0tmkG/n8bD4SODOP32lhs//4rd/Y0ByY82NhrFP0QMebvRxoEeFyV69SRilmUY9PsYDQdQUoGvZoP6nmt8z1e1euO5Zqv92YmG/99nZrrL5MSDx08IZwzccWGMQZFlUFICxkBrA4AYo7SB0iWFyQghx3lvu2iZ60uklNtkWaLMc2ilEXg+Op0JNOoNuI4HQscB94RQUFAjK11VRfl4ofJ7t737MyeVX/pSmzd/vijL9E7GxePccQEAupLj/VZZjsWFORw+eEAcOXjggmF/cfPDt+w8oS/IRXHU5Hm5nMQjMxr2UeTZ9/bJjeIR0jRBWZTI0xRlVRJCKCUEIoo2ve4FGwBc8db/2B/Egz8HyMccx3mOcQ7KBDh3QBkjjFNQxnzK2OWckJ/v9c07Hn74fY1XuyYhMBui8Ihg7m4unJEQfLW9hxjHgI33CxICQo0x64nLr946Naj/MMZrWaeLnWGzrFOg2wX1mbe+KJJrARIQQkAJMWUp9dLy0lGt9H2iQQ/jVQqo4fATLWOK98dJ8jNFmgWO48LzPKiqwmgwRFHkULICpQxRrQbHccenRI0y2lBttH5eKfMfTMn+W23j/778w0+poADUuEGwNt9rOeF5IagQKPMceZKYPEtKMHPc3ekPH17vhNR9C2CaspKQchzPFIQhavX6ajC6AghAjEEaxwBgXN9fYZzuIYez507laAmBOfpocZA221+kBG/QsnIIBWAM8jzH7OwRkowG1A9rM+1257LWzgvuBfYe917C7Yvb9f6JeKlIYjOKB6g3WmjUW+N0A6VRQYIQiiAMwR0OQhklhAhS+6G1WTTvvPkrK1//4k/+eaczhaAW/StKyAVGKWhjwBgjMNooWQWyktcMeit5Or+Sfe73f+TOf/Db973y6c7NHy7owY991ZjqZkrpjVy8mJRRQcpx30ECRhhFQxl5VeQ7G7pd9G3OsXWusjNslnUKfPi9Xd4M/TdMdiYu2rhxE920aTOm165Hq90uPMd9XKbpw3/2Z599xaDq2dlukPbSnx/2h7+yeHSu0VtZIdpoEEJQFgUG/RUszs9h0O+DC4Gw1oBwHCilEI9iM+z3Xsiy9D/KdPDfZrb966XTEimmJUAIKGcQngu/Fo0zLglFliRYXpjD0bnDODx7uJw99MJxFWzGgIRszaQhuFYrzcqyRCUlzGq6g+O5oJSBrIbMy6pCVZVGaSU5598V3L97w/WnPsJozRUXZsTQv86y9JmyHLcao99rMcLh+gHpdKbqnYnp7VFjeuKEXuSGJ43HeV+4riSEYWV5CYePvIA4HoJQCqkkyrKE5/loNlqohTVaC+uC884PZYbtRW99z//qCb/9Px3H/TgImSWUwXFdMM5htCZGa2JgokrJa6H1L124rXPt/rt+1Xul6xFCTO/5YnaUjnaneZoaAhTluM/e+CDJuC7T2ji6Mtuk4Zdddtku94c5Zsv6YbIFm2WdAqONUb3RnHhLpzM1MTG5hnQmJlBr1k2tVl9stifv4xPt/a/0zd+YT7uuDn4SxnwgTuI1SZIQysa/mmmcIs9LOI6PeqOFWr0JzsS4cepgYFZWVlRvcenQ8sLiH8e9o/9t2xs+c0L7pE7WAdyN4XCIPCsAMBAyTh1QSo/32yXJuFeZ4+pGvVkEnn9cS5O7d++iKwsrFw56K5ctLi6QwWCAspQAGUdyKamRJQlGoxGKLIfWZnUJze1T0HsaLj+mkPfjRUhXLx969pnlxfmvDPorKsvycb+JoI7p6Q3YuOkiTE9v8Br11jbOJi7odk/kM3e7YUGQhGE0rNeboJQjTRKsrCxj0F/BaDREnmcwAAjj0DCskCWfmzv1fdhey/rLuj0h8z9TSn4KjB4ljK0mQRAAIIxSEoVhY2pyzVvXrdv0S6y94Y3miVc+Qbv5hm4RD4e3S6W+WRQVeivLODo/iySJQQkB44wQxihAZ6DY1Rd11rd/2GO2rB8WW7BZ1kkypkt55Wz1veCNvh+4jusChJqyKMuiqr4D0Aeuuqo5fPmfvY3FS+Z613N/w3WdbZ7n0c7EJCYmpyCEiyzPkOcZ/CDEmnUbsGZmHYRwkI5i01vp6SrPDxFDPpfm+vNv/rH/voDTUKwBAA4Aw8EA8WiIYX+AxYVF9JaWkaUZQCjCWgPtiWlMz6w3a9duKJqT61+z8/1L7br6rVwIeq1SalIqA2MoKikxGoyQjJLVGTUJWUmAMIAQk+W5TJP4mSyN70T71IXc/6DHZkdpkRdfklIdKcsKBBSu76PV6qDR6BDuOLQs5AZlcNmH//Gf+ifyGhQip4wnnheg05nC5OQMPMcDjBmHwQcBCCFIkxGSeETSuM9w+IcfsUkITLT1o0u0yv5USf3JOI7n4ziGkhJkvK+QeK5PW+1Op9Zo/qhB+UtPDhauvOWW94lXul5NB4cpc/4qz5J0NOhDSQXGKShnAKXQMChlUSuq/KpWh265bRdsTzbrnGQLNss6aZucqtJv0tpsNjAEBpBlZVaWV1YW5ue+0V9afOblGrV2u6D7Ht17SZbFv6mNugoGPIpqmJicgh+GKIoCcRyjkgrUceB4AVzPg+M4cFzPuJ436zje5yI3+uP37No9+8M+YPB9NgOOMw5CT4Y99JcWsTQ/j0FvBZRSBFEILwjgep7yPS9t1IPjOs03T1YaQa3+tma7wzpTU4jqNVRlgUFvGVmWwpjx0JVSUEqhyDPEw8FwNOjdv1RUT7yeS8Q337xbNRv+Y612Z08Y1TQXAoxxMDaO58rTlPQHK63BaHjFfP+FE2g/0TVEFAmltB8EPpqtJjoTE5iamsb09FpMrZlGvdkAFxxKSmilKCOcOxdc/kOfYQPGRdbUjj9ayPrxn8Sj4SezLJkvyxIwGDcYFoK4nk+F4FPGqHdJVf7SzvXJJa9UaH3uq58sODV/LZV83Pd8TE1No9HqgDCOqqwg8xJFloskTraVZX75DR/YdUJFsWWd6WzBZlknwRiQbGVhwih5bVXkjSLLSVWWRiulqDHPJcPsof/+xb39l/u5977n19YrXf56mmZvHw5HTpIk34vgKYsCS4sLGI1GoJRAVhWGgwHyJDVKKSW4OFKv1z/n17z/cuXf+U+zp2XP2vfZjHqjhSiIEIYRmo0GXG98WhTGQJYlBivL6K0sqzTLMiWdY84Rve22XYw63iWMscsc1wWlFFJKwGh4qwVsVSlU1bi9GqMwDndUFNZfcP3wzssv/9jr3jR4BAxqjdZf+n64PN4cj3FbC2OgtUae5d6wt7LtuRee37LrBGaAyv4oq4pioLUEgQFnDH4QIKrX4XkeKKFgnKPRbKHd7pBGu8OFqJ+Wgg0YF21b3vxHCzov/gQGnyjybL7ICxg9bvWhlSJKS0YImXaF8y4Ruj930ft/+UK8TGPdbhdatEcHfC/482a7nUW1Gl58HxitQYwhgnMCpTvxaPSGp5PU9mSzzkm2YLOsk9JluvIvMdpcKaVko9EIi/NHkSTx0Av8h9uB92S3+7czFEejj3emp9b841q98bOUkCCNxw1fCaWgjEEpDUIJoqiGwA8hixzxoIc0iU0ax7NZlv8xk/JzF135iTOgWBvTenxIIqzVMLFmDdZMz6AzMYUgjGAMEMcxhv2BklInvkOOeYbt6qvfymVR7FRlNZ3GMZYX5jHs9SC4QLPVhuO4yLMMUipEtTrCRgt+GKWNZvOBMPQfJeT1PzW4c+etUms8pLT8hqwqKClhzHiIggvi+x6lxKyHyre/853/W3A81yYEJu0lWb+/HI9GQyhVgXEG7ggwwQEyPmjBKIEX+HD9gDqc8TWv96CP4b633/D5eVqxP6lU+Ukpq4Uiz5DFMZIkQZIkRKqCeb6/sdWZ/JmpDRv/3spzH95gzN8u2mZuXZvX6tH/Ykw8rpWGkeN4KkoIyGoovO8HvhBiey1obDPGRlVZ5x7b1sOyTsL8fOiKPLvOaL3OaEOKLDMrK4uSCn6w3Wrfhw7+Vsi4MbcEWV/9fR66v+L4qlnkGbDaAJW7LrQBuHAwNbMWQjjgjEFLBUOIJoQezZLk83kcf27r2z57hJA/OCOKNRw4gH6jA0oZwnoDQRjAcT0YAEpp5HmBMKzBGKMc38satfYxF1G19GCjctwfkaUWWZYiiRMwJuD6AbwwAAxAGYUQDlzPBzFGx1lyRCtz5+btbzzhkPfjMW6k+90lM+x8gVB6Y+B5NUopGKNQhMIPfSJcp9NS5vLaZDmJ4wwrXxqMcr/mDAkFCAEoI1BaoiokjJQwWoOy8QEMRkAJJYyy6LTNsL30uRjzRwsHnvzAn1S6irJR8mtQqi6Eu9qiQxHfc0RQr10ghNillUmTA93/F+ge/b7rdLt69j3vO0Cizp9TRq80WrnxMEGaJKDg8IOABFFAhSs2EWKuPvCI/yCwu3+6x29Zp5KdYbOsE2QMyNzT+9YuLs5dlyRxyDhHVKvBD6JElnLv/Fzy6LZt39+o9Yknus7K0dG7tTH/lDA+TRkjru+h3mgijGpgXKCSCtoAfhghCEM4noegXtNhrT7vheEfc5ff+ud3ffbImTKz9iJZlijKAkmcIE1SGAMw7kIqPc471TCECymNTtDyj2mGzZguHSTF5sGgf81wOCTGGDSbbUxNT6PebEIIAcY5/DAcd9fX2owGw2ywsvzN0WjpIUJuPual15O1efPni0F/+e54NHgiTRNkeY6iLGAo4AUBmq2W15roXLp2srP5eGeA1jWnqsD3Y0opiqJElqYYDfpYWjiK+fmjWFpaxGg4RJlnxBhNKKcMZ8jCICEwm7d/dl6m8X8us+Q/OcIdBFENUb2OWr0Bx/UI59QxRl2cpsOfWx7FNw0P/6vOD17n1i+uzYngXxZCPCalxNLSAg6+sB9zc0eQZxmE65EoqjcdN7y6URPrzbHkllrWWcQWbJZ1gvbu3ckHo+Wr+r2VSytZMW0MhOPoyTVrjrQnJ+49PFw88tK/f9ddXb6uU7+OAR8yRl2otKRKKRBCQdh4X1ZVlDDagK3OlihjoKTUZVEcrVT1XwRjt67b+uEjZ1xz0M2bETSaEK6HeNTH0sI8sjRDVVWIhyMsHD2K2cOHMXf4kDpy+FB24MDzx1RIffnLy6KfD94yiodrlFbwgwiNZhP1RhOuO27hZbQe53iCQJalocCsw/jt939bHj2W1zhVCIGZEMlhz3e/WJZFNT8/i7mjR5DlOZgjwAXnQvCNnudevm/fdHg815ZFzwRBsCK4Y4oiRzIaocxzwBhUVYmiyKG1Wm2hARgFNjn5wxz9az+bLVdPHtJCfNb1wluFI4ZeECBsjA9LpPEIw96Kn8bDy7VRv1AU7k889/Bvf18aQrfb1dpnzxngf2og49yBH0QIggCO54MJAam1m8TxxQuLs5d9+dPvck70fi3rTGQLNss6QUePTkeqwvWM8qmqlKS3smJGw0HOuXhsstPeu2vX7vzFv2tMl15/5dRlruv+pnC9NxBCuVISRZ4hSWKMBiMM+32MBgOURQ6tNaqyQjwa6oX5owtLiwt/kieDW/7gM4tHjj/S6YdDVhJaSsAABBRKK2RJjDQeQUs5XtaVShEpkyR58pgKzo4ziIzGdY5w3ajWgON5oJSCjpMkQECgjYZSCmVZGK107rreo0EYPfD+9598yPvxWn/d9sJz3DuUls+naYIkHqJIY2gpiTEgSsp2XqRXk/L4NsbL9TUjPH8YhTXlBxEc14UfhGi1O+hMTKLd7iCMaiB0HECviSFLT6dn1AwTIV196dWThzhzP0sZv4VRNqSMIS8LLM4fJctL8zBaBZ7rX+0I/ouTU40bn7irG730GjO3IleGfIkx9myzNYH1GzZjZt0GRPU6pFRkNOjT5cWjM0uLs1dH62Lbk806p9g9bJZ1AkwX9AHe2qI5fZNW8Aa9PgCYZrs5H0a439Sqgy8uWY5Pkk6uI4z+cwbcYLR2pVHIsxwrK8tIRkMQQuC7HhgTyFIJqRQIpaYoy6VkNPw85fQ//fe//L8Pn3Ezay86ACRiCMoYwqgOx3UhpUSepFBVhXa7A8cRoJwrIli6+ZLBa86wdbugZZ5t5o53he/7xHVdqKqCJONiUBmDqiiQ5TmG/QGG/YHxA3+h2WrdpXx+6HQ8BkK6uv/Yv3xGBv6dk1NrLqQgvBZG42/GRqMqSy8dJZemsbrotl3Yf/NuHNNM49atO3Tv0PwKdXgljOZqNZYLhIIzAVACoySyNEFiDCgBdemZ932ckK42pnswP4rPGhAoLd9PDKkL4YISj9TqLeOHUUQpe7OSVV6fLLP7b9t1z/U3786A8V62xZ/7rf2ae3/pOuJS4kAwxmC0QRqPkMUxOBf1Zr19ZVE1Nu3C1xd249iesWWd6c6832jLOgvs+4UPik5n+o2NRutCLjjRWhkpq6Ioyu/EafFQu92NX/y7z+ztdtIi+8day58xRgcGBsZoEGOgK4nhYIDhcACl9HhPFmOQsjJFni1JWf2PNE9uueO+M7hYAwAcAKMcnhcg8EOUZYW5I4cxO3sYeVHAdT00mi202u2qFgYpsP01Zwnf+94un5rZuLPRam0KwvHskZISZVEgiWMsLy5ibvYI5mePYGlxziyvLFYry4tP9Hr9r2/Z0s2P5a5fD43LvVFYa35xzfTM0pp161FvteAIF8aAlEVJZVltCP2JK3/sY78dHes19+7dC3AxopwpJsS4aSyAF0+kKikxHA5w5MgLOHDgGRw48By++fCRM2qG7UWEdLU3XRwkKv8PkOoW13GGExOT6EyuQRDViHA9og0aSZq+tSqTX56+cP01T7wkDWHi4iChUv0VDJ4hjMOAIh3FGA0G4I6Lick1YmJielu9Xt9x05/+uHcy92pZZxJbsFnW8SPVgLUNNddywVt+EJJmq20831/J0+SBInn+uRdbSTx172/VvED9opH6V7TSjbwoSFlVoCBwXReNRgMTk2vQbk/C830wzuG4rgnDaCWMov9BfPc/PPjoXxw8s4u1sTAKTRiEBhjPdsSjEQwIhOuhrCRGg6GJR3E5HMQZ0H3Ngm2t7/mdqcm3dyYmHe6MG+FzIVAUOforyxgN+oiHQxR5Ac8JzERnshf60T3JkblTGvJ+vAjpqjD0HxGu9wAXAowyAOP4LBgQzw8bzVbnShNMrjvWa+7c+WOagS0TQipKCBjjAKUAAcqqRH9lBf2VHoqigpQaWVYQYN/pfAyv9Yy0NyMPZbL6rDH6/2JCJI7rgjKKqixJniW0youW0eZGxtkvtT11+YsHNcZNqKunoc1fcM4lYePn6/gBas028aMG5Y4z6Xve1ddcuOHE8lst6wxkl0Qt6zjddtsu6jnhNkBfDWOEEziGC6/y8/DZ4crSN45++T8NAODZZz/oBqT+bsLIPyGUzORpRsqyAucMjjvugh9EEdwgGO/FUgplUZqyLFag9f9wmPt/vvDCoefOhmJNrq+ZJq/FSmmkcQzGGNZMz8APQnh+gDzNEMcDqHhQGKOL1zrhakyXLr5QXsCJu1NTSrTScBwHzHGQJimKooDnBnDbHhijRnBPUsa+Wxbl3Re84z+f8pD342QW4mxFVMVfy6L6UcF5jVKGLCsArRCENYdRXASpL332Sx98btu7P/Oauaq7d+/GDddcnlBiJAgZB90TCqUUBv0+kiSG63qYntkASiiyPCdJvwXgwdP8KF7Zi8ujC0/o/2g81QDwC0Qzv8gyZElCKGfU84NJGP3jKsnTw49fUJrbdn2H3LxbTVwcJMv78VdG658ByGXCcUA5AxMC2mjIqgy0JlcEnn+hMThMiF0Wtc5+tmCzrOO0a9d1Tu+gvFbKcqPWhnAujOM6Ay8IHoiY/u6VXUhz2y72TOq/pWD5vxBCXDCIU5olCZRS4JxBCAHP8xDV6wj8CJRQaCON4PmylPK2LE8+vfe5Q8/dfPPus+I/mqLoGBG6GakqcEeg7jrjliSuBxAK13VAqDHDfi8bJMlr5og++eSTvMYuflMU8g2MMXiuB+H7YJQhiCQ0CBhl4JyBMwYunD4T7B6ft5463c8CAP7kT7rlz/74++7O0/i70HiTUgp5XkBwQer1Jo3qtbWeE1xdW9f5OoCF17rerl279QvfWrdU6qrK8xx+GKHRbENrjaIswbhAqzOJWr0OYoC8yFE4+oxcEn2pcdG268Dysxd/oio11br4+aosPUIIOBeEUsLKolpLQG9yPTcfXX/1Hxuz+1lCunrlud9+uqzoX6uyuqjICy5lhTRJoJUiRZ4zwGzyXPfqfQ9+8GHgM8OTv1vLOr1swWZZx8EYkP6BYk2p9HVFnteULA1nQmlEz3PK7nvh0qUlY7r0wJPl5SYvfzPNRlcagMuyRFkUqKQCIYDn+6CUfi/7knBqGHP6jkN2h17+6YeeWDhrijUA2J7PmsMjaC4EonoEwcf90Ywh4yLVdUyj1TGU8Vwa8pozSg2yPnJccR1l3HeEgBEEhlBIJeF6HsAohv0elpePGq219MNwX7tZv3PDpUV8LPf7eut2od/+9t7hCP5dUsprRnFCCQEc4aEqSpKO4oZ0qyviQbIRwBLw2rOopUZRFEU+ioeQSiOqNeD6AdqdSVBKUWu0IASHLEpQykkLrdP9GI4JIbuVuav77MGp7BNQkjLOf87zPU9rjaIoCAE4JXSD0eanSom4N/epPwX++cHWBc/Hc9/Z+hfxcPTT8Si+UGsDQgm00tBaEUZp2xHeG1rt+owxGJ1pfQst63jZPWyWdRz27r2FF0ZeMRr2t8/PH2GHZ49gubc8ytP04UG68sQXf+dW9cT9y1u01L/hCOdtjvAc1/FQqzfRmViDdnsC9WYbzVYbvh+gLEqM+n2TJXFfKX0bc+kfTWx5bN/ZVKwBwF4Ag8EKsmQEAgIuBOTqRvhBv4+yKCA8xzTazaw5MfmqM2zGGNKc3jIt/OCNRmuizbgF6rC/gtmDL2B5aWGcUUoI+oMeDh86MDpycP99/eXh4+P9TWeGG25A1mi3b29PTM2uWbMOGzZeiPUbN6PdmYTDXVHkxTZt5PYvfOGmY9oYH/mubLYnemum16LV7oBzDiEEao0GwiACAYGsFGQlAa3ROjvqNQAAubErNy74T4Pxj3uu/9+FEDkAUEpBKCNpHvPFhbktw37/7xFd/ezi4u/NALt1UWaPS1l9UQMK35t15fCDAH6t7jqed4nW9LIvf+aDtiebddazM2yWdRwuvDANZZ/9iIGeMRqEc0dx4R6C4Pdv3jY594H3dqcSIz/IuHiP6zo+4xyEEBAy/m4kKwlDAEYJqiJHEg9NpWRfVmq3Yd6nOu01+wg5u4o1ANgJ4DnPHS/tKgWtKpR5jizLQAAY7UGWlamqIndd+eozbHvfz4vpLW9SWm2sshK+MaBcYDgc4OjRw2CMo12WIAAEc7TL3f1ZWdxx+Vv+4+se8n48CNmtnn/8Xz8uHDwQhbX3ukEIAoN0GJMiK6gs1BoDunNzfdudANJXvxbM4lO+dHw6DKMajNGQlcRgZQVFUXzvfWW0htEKjnDBqX+6H8HxPa8bu9KY7tPxQXy8lAaU0L/vup5XliXiOEaaxq5wvItlnv88cmTPvPAv/vyinQd7Lzy26a/CevvvaqU2QWsUeQalNfH9gAoh1impr75oZ3UfjmHp2bLOZLZgs6xj1O2CHn76wEbf99/sCsefnllrKBcpYfxR5niPopd6zkT9V4XEz4OgToBxWoFSUFWFsiyRJSkMGYd0UxijtRlWVfVnBRl8auPMd549G4s1AMDOnWjsXwQIAeMMBgTMEYhq0bgbf1liZWnRxGmcS5W/akPbvUDgz869jXMWcCYguAA1gON6aDTaKMsS8SiGVpWhhKSN9sSDlUoe+WGEvB8vdWjUZzPh16jDfkIWZU3KClmSoixKaG0iQtnlVZFd0O3iNdMrKjIqWRH2NQfKPEeaJuj3e0iydJzZWlVI4j6kLFGrNdBp1k738I8bIV1pTPeZlX3y3wMglNCfY4R7oV8jgeebwPU9VcnLCMjP1/xa0j+w/YusjUci5d9pDH5VVxUhhGA0HCJPEyJL2WCMXkV8/5iXni3rTGULNss6Rm960weFVNUb0tRsq9fqtBZFhnHnqJTVQwuLK73e0ey9tXrjH3me16GUElUplFUFKSsoqTAcDbC0sACtDdqTE6bdag0JpburMv/k5YcGz5CdZ2mxtsqtBYA00DCQUgKEwAs8yLLCaDhAr79skiTO8iJ7xYKt2wUtlrMNFcmuEcKltVod8XAEpTWY62Bqei1ggDiJzXA41K7nHXY8984rDsql0z3+l7P1XZ8pD377X91bFfLpUTa8Jk1iZFkKowzhjsMYpxcUZXzl2yd37QV2v+r+u6oQqiJlkiwvYzQawHFccC5Qr9UhXBdFkUGbEmmioJU8a/drrRZtT6/sUx832oAz/vfbrQmXjE8LmyRJAqn01ZyzX8wKk1FS3VF4+J9a6fcoqSal1GBcANBQshKce9uIptu/cMtN37np/V9IT/oGLes0sQWbZR2jnZdMN5U212uYtsMdgJLcGPVMXpYHsmTw1jJP//loFG+q1SIahDVoqVCUJUAACgIYA84FlJamKstBmiV/5jreJ56bHzyz8yzbs/ZyHOqgohXiQR8rvR6E46DTmYDjOIiiGjQxulk0s5zKV9zD9t73dnmN6qvzPN2qygpVVWI46CPPMzhegE5nAlGtjiZvIQyjDJR8s2L8IXLt/3lGPj9CYJ56Sh1yY9yhpbq6KEompYaUEnEyJISQdhRFVzc3hjMAnn21a43osCI9vbzUW0JVlehMTKJeb8D1XHDBUJYFmo0apKwAEMOb/Owu2u7qPr2yHn8IAyIc9+eYI9wyK0hVlmZ5aTFUSr3JdbzSc4SsIjyTZ/ldBmYXI4x4fgA/8AihhJZlOT0a9K926vQ1l54t60xmDx1Y1jG47bZdTGpzIeXiatfxXMqoyfKiv7S0ODsa9K9otVr/PKrVL4YxTEkFxgW8MIQXBGBsvI+tVm9i8wUXmAu2XjTqdCb/vKrMJ7c9t/+Zs+2AwStJ0hGGoxHmZmex//l9mD18CP2VFRRpCsd1MDExpadm1uauy19xvNun4ES16Ib2xITXbLfhuv5q+gNHkSUY9FaQjkbglJpmoznXabZu7/cP/lBD3o/X3NxDGQi5yw2jo5Nr1mLdxs1Ys3YdXN9DVRaeMvoy5ZmLbrllp3i16zSGi6o/6sVKSTSbTbRabYShD8YAWeXgjKLZamDNmmlMr5kB0DndQz8p5MaubB/GM5zTf6+N/EKV51IrDc44kaoivd5KI83i67nj/LLnBW/mjvuA67hLUVRDrV5H1GjADQLkeRomyfDKVj3cYm4bN9+1rLORnWGzrGOwHnCee2bfm1xXbHI9D47j6jLP4uFwsDYM69eGYf2iZrMt6rX6uMdaFIIwBg2DMs8BGHi+b1zfSzglXyiK4lMXPf/80+QcKdb27t0LRwrIqsJwNIDrufB9H3E8QpYmqDXqiMKGNIRkL7wg5ctdo9sF3Tc72OS56o1cCKK1BgGB47gQXMCYcdIBpdSAkpwS8ggHuf+aa374Ie/H48Yb98j9T7zxUVY533Z8Z50XhijyDEZJ0qjXaRBG6x1fXP229dsfAPYuv9J1BoehtKt6YRCOZxrrEWRVIB4NoLRErdGAcB0wwkAINe3wNbunnPHIjV1pHn7fU4f55B9KlTuMOO/2g4BPdKYIAWAMmkqrH+GceZOTncNKqmUhnEnh+2AOh4xLwhlnjfbEligIrnyyN/oWgDOi9YtlHS9bsFnWayPP9nqTtTK93nO9RuD78FxXO8IJa2H9Ssf3J40yLncEgjAAJWTcWkEpMErhBz4oY4ZSmlRF9WV47FNrDlVPnSvFGgBg714sTG2C47ho1JuoN5twHRdpkqCqSmilkWepNIRkS0v9lx33ZZddxhePLu6s18vNUdRAJSVGwz6klKhFdfhBiKoskWWp0TALsqruTEjz8Oke+rGQbt5TsbydCf7jTDCHK46oXgfnDeI6XlMpeRVSvQ7ACvDy/cLyC1qmXdLY9zwVRgGjdJxORRmBcBz4oQ/hOCAAdKkMVk73qE8Ncs2t1RNPdB8R6fD3PE8QRtm7ms0Wp4SSXn+Jplk6Eeb5m7VWF5Zl0XFdHxAMCgpaafiBT+qu12Gu2Dky+q/xGkvPlnWmsgWbZb2GbvcGNj3V2e653o6oVheOECAAD4JgwncDqrXi2mgQQgAQFEWJoijgeA68wEMQ+oYAaSnllwf9wR/ODOcfJTfeKk/6xs4kO3ci6hcIohra7QkEYQhCAN/zICsJJpjRUldSIfeW8pc9qTcTXBVyId4qhAiZYDCUQDjjViGu54MyhiSNzXA4qAQXT/hR9PWr3vJvz4pppK1bO9XzcX4/5Wx/WeQXl0UOwghcz4dwHReFuZhL77L9d/3qM1tu/PzLBtfv3LnWzD2lRoSbQkoVUEYhHAeNRhOUUziOCwJAVRLxaGSeW1g8a/ew/aAdO7rlE090H3GK9CPDQY8Yo9/JGOWu6xGlFS2KpJXlJkpGIyqVgnAdCOGAgsBzHdQ591zqXjpZr1/88C07D1zz/r1n9KysZb0cW7BZ1mv4hXdeHlTcvc4NgrWcO0QWJYjR8L3AYYwhHgxJPBrB80O0O20QSsFdB5xzEBCjlUxhyF+D0Y/38vlHyBm+hHeioiiCcHwQQqClBBcCfhjCSAWlFSpTlbKq0ojHf6tgMwbk4Lc3zWiqr9PG0CxNUUkJzjgCL4DretBawxGuCbxwRWu1J51deP50j/lYEdLVh5789efSRD60vJRuM1rTMIwAGhADUEPIDBP8al1z7gYw93LX2LMHuGhGJ3mSFlKWQZM34QUuuGGoygJ5nACEoSgK9Horevnpp0/3sE+pHTu65bNf+uC3l7Hye8wQ43jeu6KoxplwCOcMsiqdMs/QW1nEKB6AUAbfC9DuTBA/Cmmggw2OJ95w8U/9+IN4/97F0z0eyzpetmCzrFfR7XZpe8ZZX2l6LRM8rIoKlVYghBLCGMqqRDwaYTDoQyqNVqcNPwpRlQWyODbDQS9llP616zh/MH3RzLcnZs7NYg0AirRAmuRI4iECP0Cj1UYUhVBGI89z5Hlejvr97NATC3+rYNu79318QyPcqZTcuLKyhOXl8UlI3w9QD+uQZQkuuAmjUNbr9adUXu7Z9ON/fLpD3o/LN548HG9pBbeXUv4U506DC47hENBGgRIaOa57JQ87m43B0ZeLUbph8UnzXHvdyGiTCcdtMeGAUA5VVcjzEkWeQymNoiyQjVJVr4lzZobtRdve/Zniidt2fatH9e85rkPCqPZOx/e51prA+HBdD4QSiGWGSiq4no8gjOC6PmGOaIDgqjKvr8e4J9s593ysc5s9JWpZr+LDH55hlTFXZVl6SW95kQ37K8jSBFmWoSgKEMoQ1uqYnJpGe6IDx/dBCEOeZmZxYT5bXpr/yqA/+MPp0ey3CXn/OVusAYCIAjDBUa4uCWupoLWBqhTyNDN5mpayrPK5tXv/1n+UnsfdNE+uy/M0VEqCUgpGKagBtFKoyhKqkqCGDFzPvScIW8+c7vEer127dleeI75ZrzcPMEYxv7CAffv24cD+59FbXhFaqm1Kkcu/sXvXy0dV7dpuGPeTKGoUjeYEhPCgJKAkAAgQ5oEwxwjm6VqzKdsXbzgnC5IdN+8uW5p+O/D9jzDOvlJkmczSFJRyRLUIM2vX48Ktl2DbRZdiywXbsGZ6LfwwgNHaTYejixZnFy7tdm9wT/c4LOt42Rk2y3oVc3OjhqnUdcNhf2I0HBFGKThlgCGoygKuH0AIB61ObRxKbgyyJDbxaJhVRf6Vssr+gDbEt87VZdAX7QRwsNEGagayrEBgQCmD1gDhDGI88yF9z8/wA93mjQGZe3xqfUnLNyoDWm+00Wi2oSoJxigc5oAQmKzIZZIlz5a6uv2CK3//rDvpRwjMsw/4RxRle0bD4eWD/oASQlGvR8R1HRBDJqoif4MX1r8C4IW/fYWuccPfHTrMzYTgkFWFLE1RZDmUVtCEQAgBvx5pV3DZdqfOyYINGBdtzz77wW/JrPq9JE4N5fydlDNOOYPQGjAGlDGw1ZYwxhhSFQXtr6zMrKwsXX3ZluhuAEdO9zgs63jYgs2yXoExXTo8ii0pY9f4nu9SMFDGIBiHLCsMhwP0eiuo15twxTQMV8iKxIyGg6ws069xwf6AtevfOtPbTpwqVAOOH0C7GslggOFwiLwoQBiDkhKU0VK4tOh2f3Ap6hZem8wur1R1cVVWoJSBwADagDEGRgikVCjKYpTG8dcHafXEheTsXM4a8CKLTHCvV4t+cZ3rd8IwRBj4cBgDhfFNIXdEHe8iY3YdfrmYMk/JpDCskEpCVxorK0OsLC1CKgnGGcLQR7PZ0AhCNeee2wso27Z9pnj2Sx/8FmnRf+c4LtFK/YQ0mmulvndK2wHAKIOWCmWeQ8kyEpxdtWWys/m223YdPVd6IFrnB1uwWdYr2LPngDPTaF/je7XNtVqDtjouKOfQlcSo18NwOAAjFI4QoARQVWXi4agYDAa3A+ZjUW/wrR037j4vijUAYJSDEopKlsiyFFJKyEpCGw2pKkMIqbhLxk3pXmp+6DDhv73SKszzHGWaQysJxjgCz4fjOlBS6jzL9iuZ3XH3N/7r8HSP9UTt3HmrXNj3b77d4Gv2cS46glMQrVFlGUnjmJVlsdHx/av6B9Z9E0D/pT9LCMz99w9jTxc5pwyMuVhZXsbC/AJADOr1OlwhkIxGRpalDJfP3qSDY7Xt3Z8pzBPdbx/Rxe8N+33CYH6ccsaN0WBg4yV5rWGUhJKKuF7AGXcuVKq8YjJOH4XtyWadRWzBZlkvj4gqbyfD+Hoh3FbNb8D1AhDCUSCF1gqe76HVbqPRaML1PJOlaS6r4muoqo8ejJNv3Xzz7vLkb+MssXMngoMjSGgQY1CLajDGgBAKYwClKxRlXmo9/31tOIwBOfxcMmNkdu1o0KeL87OoygqB58FzPVR+iCAMjZIyGcWDB0osPPpaIelnMkJgFhZm5gPgAULwpipPiSoKVGWJ0WhIpK5ajh9ctZSItQAG+IHitv/8U9KvRQOlAQKGoijhBT5c10UtDFCLAjiuq5ggpayvnPMFGwCQHd3y2Wc/+K1sIfl3ADGO7/4EZ5zzIAAXDKAEMBSEURAFAuhOVRVvaDfw17AFm3UWObfnzC3rBN11V5etX7f+kka7dWUQRY5wHFDCAKNBiEZYizA1PY3J6WnUmk3DOMul1rdrkI+dd8XaKkUBWVWgIAijCJ4fwBiAMWpqtYbudCazqcnJ75tx3HvrTv6dx5645tmnvnvh/ueexXAwABcCtUYTjVYbYRQa5ghNGDtMGbvj+m989YwMeT8ek5OLOTXqnjJNR0vz85ibPYI4TsC4QBDUPAJyad4fXPLpD251fvBnfSfSDKRfZim0kpianMDWC7dh44aN8DwHaZogzWJd5XmFA6d7pD8827Z9pjhczn3LqPJ3izz7ahwP1Wg0QJEXUEohyzIMBj30esvIijzwg2h7fXrzxXfd1bWTFtZZw75ZLetlbJ+El8O9llOsl1VJktHwxc3LMFrD8RxQxmG0MWmWlUqrO7TUH6NHjuw9H4s17N2LgbcGUAqe44BCwCiNoihBGYHnB9p3g5ywie8r2B53amG0vPL2vEjrnHOs27AF69ZvRK1WA6MEhFIQQjNVlQ/LMvkmOYtn1/7Gh5WpPvEEUeq7qqzePBwOYLRCuz1BHMdhWZauK/L0qovfOHUvsG/hpT9Z81u6Ekk/JBFqtQaazQ6EIwBjUJUJFheXUFWZimr1coYV58UM24ve/e4vF1/60rsemcD07zEjyWg0eEdRVJwJjjgeIolHCIKQTK1p0UaztdEL/Kva7fZDwLmSCWGd62zBZlk/wBiQfXuTNVKX1+V5USurEkYbEDrOjeacI6rVwQU3WZZVWpuvu77/BxVzH95xPhZrAPYCyA48B9dxEYY11BoNuK6PsBaBUArGuSKcZoLWvlewGQPy9b9ct3aUDt6S5wkFxqccXc8DdxwQAlDGDSGYo4x9bcCL+dM9zlOBEGLM4u/PDRV9qNVsXUMpYYwxCCFgjAZjvN5qtK5y2SUbgfsX8ZJl0VFU6mm/1TeaQCqFxcVZVGUJPwjgCoHAD01BqQr8erG16Z0Dxe3xWS3a9nacyd+l0uiiSH+iGBa8rCp4no/O5Bo0Wh3iuKJJGX8DVLweQA+2J5t1FrAFm2X9gL1738fXhNFVUpbbq6piMqmQFzlAAIBASomVlSXjekHJOXvAUP2Hh1eefujd7/7yeVmsrT40VDMXQGhn/KxGBCB09XlVoAaaQmdSzJV/8yPv4822+yZD9eaqquB5HqA1esuLKIocYRgZx0XOGX3EpfqMD3k/LhNZZp5376aM/UK7PdE2MMizFEWewnN9Ua+3Lgr8xo79d/3qEy+Nqlqf1UxtfXtQFpU5OjtLjhx6AWkWY6KzBuvWrcfk5DSMMdJ3gwJ9/7wsQlaLtm+t89f9OyEYgrD2E4RxHgYB/CAA4xzaGLcsqkvA3EuN+fTThPzaWRFxZp3fbMFmWT9g69arIp6pa5WppjzXI2FQQ1kW46RtQpAkI5OnmWScP8A4+Zh046+/+0e/fH5/4O/ciY2sDUo5DDSUVBgOhhgMB6jKHM1GS7XarWx50P+boquHoGLZ2yihQa1eQ61WHx80KHNksQGFMaoqFz3f/Vpt4wVnRcj7seuqI/O//qiqyufDMGwLR6AsCmiliVaSVlW1hnN+Va217mt4Sb+wre/aoReen+8bXZqqkoQzgVZzAu2JSUT1NgRnKIpMybIosLjzvCzYgHHR9uyXPvjtYCL4iHD9kHJxg6okkuEQo8GAeH7AGBMzGtnVh48kdwM4errv2bJeiy3YLOsljOnSZNFs4g5/EzPwSRiCCwFZSXCHgwvHVHkhkzz7RpFkH3OTxT1brt+dn/wrn9127tyJpf0DaKNgtEGWZlheXML83CyMUWCUaMdzs6OzSQUA3S5oUQw2CM+7JqxHtM4b8D0fnudDaQ3KqGGEVkbLx8uRuY+Qm8+p2UtCYB74r08tpG7tGz2+fI3rctSiOgI/QFVVKMsyDKLoijBwLjS34Si5Gav9wuaMLNAHiGm2OqhFdXieB9fzwQRHVRYY9QeqPxwUf/qd75y3BRswbvmxf3/3Ww03+KiqlFskxbUry0sky1O02hMIw1qtLMsrRiuDTd0uFs7m08fW+cGeErWsl9i3ry0YzBuMUlursqJKKVDG4HguHNc1DhdVUIseaNRbH7t4Mtzz0uWq89nevXuxtLSIxYUFLC8uIEmGoJQgimpwfR8gVBkg84WpAOCyyy7jju9fHdVqW6fXzmB6ZgbNVgthFKLeqCOMIgNCV7TUd8nhYP/pHt/rgScLWVakD/RWloZHZ+cQj4aQWoNwQbjrMs75FnBy5crVHwz/5qeeNHEyGlRSmiAI0JmYRBiGyJIEK4uLyNMMIFSCmrzb7Z7XBRsAbNnSzYdI783TohsPRw9WVQWlNLRUgCFCKbWtFgSXv+c97/NO/tUs6/VlCzbLeonpmmho6OuMVi2tDQwhEI4DPwgM51xprR4iwEdb69ftIVu6tlh7iUFvCaNBH/FoBAKCickpbN5yAaanZ+AHviaMZu1gfEr0ssntjhd6N9QadS+IQgT1GtwwABUMjDGTZ6ns9RaeWhou3bPh+k+eVSHvx2rn+/bKqswegcFBPwjgeD6E6yKqN1CrNwjnopOn5Rvm95drXvyZ3buBUhZZlo6y/soyFheOYnb2CF54YR9mDx9EmZem2ZqsNm6+pIDdSA8AWLu2mx5KZu/Ns/R3As9/cHJiGrVagwghiBDepB/Vdq71ZiZO931a1muxBZtlrbrttl3M5OlWlcurVFW5xACUEBDAEBDFKHuAwnxkpaj2EHLzOVlEnIzGRBudySl0JqfQbLXRarXQ7nQwM7PWdDoTyhEiW3GWVLcLmppgk5L6jWmWkdFoiLIsQCgB4xyEEihZ9fM0vTtdnnv6dI/r9UIIzLTjHJ6emfnWps0XYu269Wi0mvA8F4xS5FnmLy4vXTq7snBxt3uZAwC7dm03TJGcaIyGwwEOvnAAhw8fxGg0RCUrGBi4vp9PTW+xXyZe4pprbk2rjN0ThvVu6PvfMDBGKQXOeeC4YrtW1ba77rrBbhGyzmi2YLOsVRdc0HKHvZU3JaPh5mFvSEeDAZLhyCSDoSyL/CFO9EdXpNmzYcOHbLH2A3bu3IlWYwK1Wh3CcVGWFfKiBKUMfhihVm9IzxHF4mKi3/veLm91Jq4TvrdllIxw8OB+zB05jLLIwBkznDPpe8G+eli/40d++q/O6U70kRPl6zdu/sb09EwZNupwXAcGBsPhkBw+fJAe2L9vQ16Ornz7RVfWAQC/AxhaFWVZZWWRI89TKClRr7cwNTVtgjDUjLOUEc8WbD/gmptuTQMnuWeUDbtzs7MPLi4eJYBmwnE2E8qu7iSX1k73PVrWq7HfKCxrjCw/O9+pwvQ6P4galBFwx4EQQlFGv2mI/khCzJ4NG7q2WHs5ewEdVkiSBPOL88jTDM1WG1NrpuF6DkBQMurkuya36yNNpyFk8+1SqYAyhiROMBoM0Gw04QgPRsoRoeZeZ037CXKOL+tt37VdLjznPWYImZOV3FRVJXq9JczNzmL+6BzRsmi2JyavakxMrAOwhA93zcIX/16mcj0qyhKUUhAAfhCgVmtAG23ieJRSZ9G+T1/G2mtuTb9wy033Uif9Hc8Pu44QbzLGtI1Sb6A0/xLGPdks64xkZ9gsC8Btt+2iyKtLRqPRFaNkyLUxhjImCSHfLKX8qFzq37l2bTc93fd55tqLSmmkWYp4NEISx1heXMTC3Cx6i4smHgxLrVS+tzZLqsXehqoor3WES9vtKaxfvwnNZhtVKdFfWdEL8wv7l5Z7d2ze3D1rQ96PFSFdTfL8+TRLH0+TBMtLi5idPYJ+v4davY4Nm7a6k1PTl4Dkl37wg+9yAUBLVVHGc8/34HkeHFfAdV1QRjGKh/rI4YPpU/setO/VV3DT+7+Q6rJ/j9Hopkny4NLCnLvSW7kkFeySL33pg+7pvj/LeiV2hs2yAFx88Savmh9cVxXleu4KMNdTBvqblVYfiRS941zd+H4qUVCEYYR16zaiyHNkSYIiL8AIhVSqoKD5aFQyreavr5XNdY4fIvB9UEPQK5axOL9gqrJIjDH3a5TfvpScH20WiuFgoMNwr5TVO9M045RwTK2ZQbPZIfV6nRqj16ZJcvUvvXPNHkJw9KG7monnBj2igKosQAiB6wVwPBe+ljqNR+nB7y6c330BX8NN79+bfuGWtffU6qZLGfsd5ogLw1rr6o3NzfcCWDjpF7Cs14Et2KzznjEgR56K1snp8DpZFH6apros8odLYz46m5o7b7rpVlusvYa9AGpFClNVcLlA2PChogaUkkbKylRpWS4NF4wo8zZx3Bu1VJ4pNTTTqNIC/eUVLC8vacd1Dvuee+ftD+1ePt1j+mFZf/hw+chE+6GqKgeEsU6nMwnf9+F64xk0o3Sktbky6pjNpttd+DZ7QTnckX7owXFcEEJWe9cxCM51kWepS5fse/Y13PT+L6QPP/y+r4eF84eMu//G5ezqcrS4CcD3xYFZ1pnCLola5729e2/hRpHLYcylnHPje+43VZV/pPBXbr/pplvt0tIxWp47jMW5I1iYO4Le8hKKLIWuFGRRmWQ4qOYPz/J0lO9QUr9RFYaMegMMlpaRDhNQEFOv1bN2c+Kbk+3Ww+dVE9Ndu/XyyuJTZVE+67kegiBEWZTo9Xro93qkP+jz5aXFC2cPP3/F/4MvR2VcFlrJAQAwziE8D0EUwnEdCE5Vvd5IJtevsTNsx+Caa25NSSf4qsv5RyllpfDJjkP3f8L2ZLPOSHaGzTrvXdhJwzglbyuKrGWU+pYTiI/IWnL79dfvtrMUx2rvXvT9aQguIIRAL00hixKMOeMWFUVOYNCsR7U3Bl641nV85HGKlXxcD4dhZCYnp+eE498+m71wToS8HytCYO79y+bi5FTrPj/w3mwIIWmWYDAcgFIGRwiS5clEv7eyk9bz+zK/fKHDWFZVFZClCFkEwgV0KSG1rrzAH15xycXnVDLE6+mSS/5gtPjUb33ZEG/kBv6FsmYmABw63fdlWT/IFmzWec2YLh3MigsCGl6tlPpOPOx/NK7iO2yxdrx2olmL4XgeKGVYWV5CHI/AKEcU1lGr1d1Go7WDcXoDCBGcCzDuQCoFKSvjCLfww/ojjsvvv2b7ORTyfoy8dTyvR/UHi6LoJ3HcAiFwhAtCKfwoghv4Aadse16rXdbQIva80GgAXAgAgMxLqKoyRptCOLzfrsXqdI/pbDJ5yR+Mjj76h/dQnx3iGp4xuxghu+0ztM4otmCzzmt79hxwtkzoq4Mg6IdR8J9ZPPzajmvsnrXjthOIhg0YTQGj0Ki3EHghBBfwoxrxPL9NGXlLPBxelKQJkjxDuzOJiTXTkFIaxtgio7i9sXH9ORbyfoyPb+et8shT3UfTJHthNBi2lDEI/AB+EEIbjbLUvN3qbKSdiSsJYVmWpQXjQjuuRwkoqjKHrCpwRjJKWB9rIltsHKfpK38zMeaWp5ee7nu/8zuL5HTfj2X9IFuwWeczUvYG9YOj52pT0xs+W6feORuD9MOQpimIoXAcF832BDzPA4WBVJrlRTE5ikdtWVaBMQZ5liLLUjieZ5TWldLqUZXRr3fOsZD3Y0UIzFP3pnMG6gFG6VVVWYJQiqoscXTuMCmLFJMTU+16rXlFmsR0NBhsJ4yi0W6j0WiiKnLE8dBwzvNms94HhrZgOwGEvL8yBrLbPd13Yll/my3YrPNWtwtCCsV7o+U7B3PDfTe9/wv2gMGJ2rsXaeMCRLUGPD+AATAajVCVBVFKmqpSIYhBGISkzgVW+j30+30Mhn1TFsWKcPie9XV+Toa8H6v4yX5uNlb3aIJfrtWbgRf46C0tYmnxKIjRiMLI5czZlqSxk+bZZYIzMuyvIB71UGQ5kiQBpSQfrkSD9Vfy8+fQxilGiD0hap2ZbMFmnbc+/GGYL3+mWj7qLi6+//17z7t9U6eahiGMM3iehzTL0FtegqwqhLWIBLWIeK4H13FQFAWKqsBwsGKKspIw+ruUkj1v/ek7z+s4pZ3vu1U+8P/9/x4R9ebTUb1xtdEaMAbNVgeOEKRea7MgjNYKx/Vdx6v5fkiqKsOhQ88hHo0MpY6mxCRJfzgg5EFbsFnWOcYWbNZ5a/xN+su2/cEpQ4xWCpVU42aurovA99FodSB8D0Yr5HmOwaAHKSs4ngfBvQGMvmfkD5853Xd/uhEC8+yXpo6QRvAQI+TKOImp57jYvHkbGONwBIfrBhEoPF1WlHKK3vIilNRQUsIJQuU6bp8olsD2EbOsc44t2CzLOmmji2qGLpB+JSsTxwM4jot6owGjNIosQZwOQSmDIRSUcUxOrjFcCGUM9slC3fHOH/ujBLD7vLf6nfSxePn2PF/6ubLIGmFYQxTVwBhHVVaEMgbX8wRcBaUkXNdDZ2IGvl+D5/pVFIVLvuPbpX3LOgfZgs2yrJN3N+DvDDLXccEdF8L1wClFmg+xsryIOItRa7XRbq9Bs91BEPjgnA+1xr1plj1BCLEzQgBwQ1cd+ZN3fVtK810v8K+NojoAAkooOOeglBKt1DiSCgZhVMOGTReiyDIwxqQfuIs8hD04Y1nnIFuwWZZ18t5+AzZOEhDGYbRBkiRYWVpCMhqiKAqURYlRfwgYgka9Cd93tXDdA+D0q1MX/9YA6J7uEZwRCIH5y88NF8Jo5r5OZ/LN9WaLABRZmiHPc/ieB9dzUZUluODwfBfM9eC4HohRJRd8iRTGFmyWdQ6yBZtlWaeEH9SgtUKSJOgtLWLuyCHIsoIfBAiiCFoZ9JaXMOwtGamKdO36Td+ImjOP2tm17/dTF7wje8aN73O94FeUVBOjwQBFlmAUj+A4LjrtDjzfHy+TVhKj0RBJHBvOSEY5XX78yNHzsjWKZZ3rbMFmWdYpsAezRy5HVUiM4hjJaAQYQAgHfhCg0WjAgKA/6JlRv6f7K71DPg9un76kOm9C3o/ZDV1lHvo3j6hSPlUU+VvSJIZSEpRS5HmOfr+HutEoywKDQQ8LC7PIs9T4Xjj0vaAHwPZgs6xzkC3YLMs6eXcDRy6aRVWVSJIEruOg1e4gDEIEUQTH8SBlBcYoojDKHMa+yXT+TUK6tv3EDyAE5uEvLMzXm827GWXXG6WplAp+EEJpjTRNEMcjlFWBPEuhVAVKiOY+WDgAAAdHSURBVCmKom9AV3b94l/aZ2pZ5yBbsFmWdUoEQQCpBQij8BwXrU4HoR/CAMjSEdI8RRCGptGZmGOU3P58j51XIe/HY2fWK54M/a8rqd5XyWqSwIBzDodx5HmG3tIy8jxBrdHCzPoN8D3PZFnWrwZVzzZ+taxzky3YLMs6eW8HWnwCjALaEAghEHg+lNLIswxKKwghjO9HueeHj1Cf33fjVb8pT/dtn7Fu3q3zv/ql746S6hHK6DuazSaCIADjHEqWqMocDdbB1NQMpqbXGC6EzOJsuUfnBqf71i3Len3Ygs2yrFPgBtTqGpwJCEeAcQ4KoColOGcIwwAGMNqQRaX0V2c2JUdO9x2fyQhg7s/zRe549/ie/zbGmGuUgXA56lEdlDBwz0Wr3YHrh6jKsqiUXJw/GtsebJZ1jrIFm2VZp0QQBFBSI0tSEEIQBAFc14XjOtCVNEmaVnkaP1Gp4v4N5DP2JONrOAyUlzTb90DLhSSON6RpAllFoJRCSQmUFGVRQaqBGfRXktFgMJ/lPdvSw7LOUbZgsyzrFNiDUe8aJGmJ4XCAMAjB10yDEoKyLJGlqcmzfMUAtw+PHj2vQ96P1c0371YP3P4Pny6WR9/Mi3yD4zgoigxaG1BG4XghlFQoZQlZFamU1Xx00cgWwpZ1jrIFm2VZJ+9u4MhV80izFBQEzUYbRmv0V1aw0l8yw8FQOtz5blRr3X39zbvtLNAxWvnOkb4K9F0Od99FGfGzNEeaxnC9AE4pkeUpAGPanc6w0XSXrrxhj23pYVnnKFuwWZZ1SnDG4Hk+fC+AcFwsLy2j319GksSghPSpJnf3uHr2dN/n2eShlS9XP7HuF+8PotpBxvnFo9EIZVmgLAsorZDnseFCyKoolpdSrNgTopZ17rIFm2VZJ+/tN2BNWAHUgBKOQb+HQy/sR1UWpt1qq9bE5HOU0jt33Pjp5HTf6tmk24X+lbsmnqdB+BAV/OKgVgcXHHmegzOGsgyQpamKk9FCf6WwTYgt6xxmCzbLsk4J13UAQqGVRpZlyNIMnBII1xvVouY9jYnoccDOAB2v5Vqa1DJ8jRvnZ4Xwwk5nEoCBVhqjYQ+6UmVeFgv1UA1P971alvX6sQWbZVkn7+49OLLzQoBSKK1QFAXqzRaEEJoQfiAeje7Y/CY+Ot23eTb64hdvVe9+w698i3HxdCXVGzjniKIIMICS2gjHTWu1+tx6t2X3BlrWOcwWbJZlnRJpOkKWl4jjERzXxZo1M6YzMZUSg/uG/aOPEPJHNjLpBHS70O/8r/LQiup9vSiKK5jgvBbXUIvq8IMIfhQNPdebXXOdX53ue7Us6/VDT/cNWJZ1bmDMBaUMRhnIogIlTEdh7VCn07nj6oc22f1VJyHbdyQtquJr3HFX/KgOQyiUVsbxXR0EYb+U5RGgaws2yzqH2YLNsqxTQimAU4F6vYVOZ9K4rpflSf5NleXfJF0b8n4ybuzuUVGr88TkmpnHm/UmAi8A5wJlXsh+r7e0eLS/aE+IWta5zS6JWpZ1ikj4QYjJqIZaGBpHiNmiLG/v9KgNeT95ZiJau+SE5M5sNHprVZWOYAxa69Joc4j7ymaIWtY5zs6wWZZ1SjAAnuuh3eqYerORB1HwKOX+feTGrg15PwWeHryQQ6k9VZ4fGg2HWOmtIE3TjHP2wtYtkS3YLOscZws2y7JOCVUoZEmCUb9n8iRdkpW8Y1OobMj7KXLzzbtVb37wVBpn3zQGMKBGVnJYVMXBJxe/k5/u+7Ms6/VlCzbLsk7a3QBGaYpRr4fe0pIcDgaPJUl6D9nRtdmWp5DTe2RUFMWXXMeLpyamdKPRmqdGHL7BRlJZ1jnPFmyWZZ0SqkxRVaUuy2KliJM7Tc5tyPsptvN9eyUX5EHHcR9XWptKq0MaOGIPHFjWuc8eOrAs66S9HYCutWBAKinld9PBcM9Vf/dWu0x3ihEC8+hXoiN55t41Go62lrLcz0pv5XTfl2VZrz87w2ZZ1ikxNTVtWu1W33GcPUV81Ia8v06eHuR5qeWXy7J6pMrLZ7H5QHy678myrNefLdgsyzp5bwf8IJChHzzrud7tN/7TPTbk/XVy8827Vb44erwo0s+nsnzsxhv32FO4lnUesAWbZVknbXFx0qgiT7Qk9wve+A5syPvr6sfev3tYjgZfWgJ56nTfi2VZPxx2D5tlWSftO9/Zbdbv+NVFl/tfe8Oja4an+37OdQQw+PU9/dN9H5ZlWZZlnWXu/dxP1e76L3+3ebrvw7Isy7Isy3oFBiDGgJzu+7Asy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7Isy7KOzf8fB1TFc8518h8AAAAASUVORK5CYII=';
const style=document.createElement('style');
style.id='sun-client-offer-style';
style.textContent=`
#sunClientOfferModal{z-index:100050}
#sunClientOfferModal .dialog{width:min(1180px,calc(100vw - 28px));max-height:94vh;padding:0;overflow:hidden;border-radius:16px;background:#fffaf2}
.sun-offer-modal-head{display:flex;align-items:center;gap:10px;padding:13px 15px;background:#fffdf9;border-bottom:1px solid #e8d9bf;flex-wrap:wrap}
.sun-offer-modal-head h2{margin:0;color:#28533a;font-size:19px}.sun-offer-modal-head .hint{margin:0;color:#73808a;font-size:12px}
.sun-offer-modal-head .spacer{flex:1}.sun-offer-modal-actions{display:flex;gap:7px;flex-wrap:wrap}.sun-offer-modal-actions button{min-height:36px}
.sun-offer-editor{display:none;padding:12px 15px;background:#fff;border-bottom:1px solid #e8d9bf}.sun-offer-editor.on{display:block}.sun-offer-editor-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px}.sun-offer-editor label{display:grid;gap:4px;font-size:10px;font-weight:700;color:#607068}.sun-offer-editor label.wide{grid-column:span 2}.sun-offer-editor input,.sun-offer-editor textarea{width:100%;box-sizing:border-box;border:1px solid #d9d2c7;border-radius:9px;padding:8px 9px;background:#fff;font:12px/1.35 Arial;color:#1d2b24}.sun-offer-editor textarea{min-height:62px;resize:vertical}.sun-offer-editor-note{margin:8px 0 0;color:#778178;font-size:10px}.sun-offer-editor-gallery{display:grid;grid-template-columns:minmax(0,1fr) 190px;gap:12px;align-items:center;border:1px solid #e7dfd2;border-radius:10px;padding:9px;background:#fffdf9}.sun-offer-editor-gallery b{display:block;font-size:11px;color:#425248}.sun-offer-editor-gallery small{display:block;margin-top:4px;font-size:10px;line-height:1.4;color:#7b827d}.sun-offer-editor-gallery-images{display:grid;grid-template-columns:1fr 1fr;gap:5px}.sun-offer-editor-gallery-images img{width:100%;height:70px;object-fit:cover;border-radius:7px;border:1px solid #e5ded3}
.sun-offer-frame-wrap{height:min(78vh,900px);background:#eee9e0;padding:12px;overflow:auto}
#sunClientOfferPreview{display:grid;gap:14px;justify-items:center;min-height:100%}.sun-offer-page-canvas{display:block;width:min(100%,794px);height:auto;background:#fff;box-shadow:0 5px 24px #4a38272b}.sun-offer-rendering{align-self:center;justify-self:center;margin:70px 20px;padding:14px 18px;border-radius:12px;background:#fff;color:#526159;font-size:12px;box-shadow:0 4px 18px #00000012}
.sun-offer-stale{display:none;padding:7px 12px;background:#fff6df;color:#8b6213;border-radius:8px;font-size:12px}.sun-offer-stale.on{display:block}
#new .actions #sunClientOfferButton{background:#fff8e9;border-color:#c99b47;color:#28533a}
@media(max-width:900px){.sun-offer-editor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:700px){.sun-offer-editor-gallery{grid-template-columns:1fr}.sun-offer-editor-gallery-images img{height:95px}#sunClientOfferModal{padding:0}#sunClientOfferModal .dialog{width:100vw;height:100vh;max-height:none;border-radius:0}.sun-offer-frame-wrap{height:calc(100vh - 112px);padding:5px}.sun-offer-modal-head{padding:9px}.sun-offer-modal-head h2{font-size:16px}.sun-offer-modal-head .hint{display:none}.sun-offer-modal-actions{width:100%;display:grid;grid-template-columns:1.15fr 1fr 1fr 1fr}.sun-offer-modal-actions button{padding:8px 4px;font-size:10px}.sun-offer-editor{padding:9px}.sun-offer-editor-grid{grid-template-columns:1fr}.sun-offer-editor label.wide{grid-column:auto}}
`;
document.head.appendChild(style);
const getBoxes=()=>{try{if(typeof boxes!=='undefined'&&Array.isArray(boxes))return boxes}catch(_){}try{const v=JSON.parse(localStorage.getItem('sunBoxes')||'[]');return Array.isArray(v)?v:[]}catch(_){return[]}};
const getLegacyBoxes=()=>Array.isArray(window.SUN_LEGACY_CATALOG_V175)?window.SUN_LEGACY_CATALOG_V175:[];
const normalizedCatalogName=value=>String(value||'').toLowerCase().replace(/ё/g,'е').replace(/[^a-zа-я0-9]+/gi,' ').trim().replace(/\s+/g,' ');
function resolveCatalogLine(line,all=getBoxes()){
const id=String(line?.id??'');
let item=(all||[]).find(x=>String(x.id)===id);
if(item)return item;
const legacy=getLegacyBoxes();
item=legacy.find(x=>String(x.id)===id);
if(item)return item;
const name=normalizedCatalogName(line?.name||line?.catalogName||'');
if(name){
item=(all||[]).find(x=>normalizedCatalogName(x?.name)===name)||legacy.find(x=>normalizedCatalogName(x?.name)===name);
if(item)return item;
}
return null;
}
const getOrders=()=>{try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}try{const v=JSON.parse(localStorage.getItem('sunOrders')||'[]');return Array.isArray(v)?v:[]}catch(_){return[]}};
const getDraft=()=>{try{return typeof draft!=='undefined'?draft:null}catch(_){return null}};
const catName=id=>{try{return window.sunCatalogCategoryName?.(id)||({0:'Боксы',1:'Посуда и упаковка',2:'Дополнительно',3:'Напитки',4:'Доставка',5:'Премиум'}[Number(id)]||'Каталог')}catch(_){return 'Каталог'}};
function linePrice(line,item){const p=Number(line?.price);return Number.isFinite(p)&&p>=0?p:num(item?.price)}
function discountAmount(value,type,base){const n=num(value);return Math.min(base,type==='amount'?n:base*Math.min(100,n)/100)}
function priceCalc(order){
const all=getBoxes();
const base=(order?.lines||[]).reduce((sum,line)=>{const item=resolveCatalogLine(line,all);return sum+linePrice(line,item)*num(line.qty)},0);
const manual=discountAmount(order?.manualDiscountValue,order?.manualDiscountType,base);
const afterManual=Math.max(0,base-manual);
const promo=discountAmount(order?.promoDiscountValue,order?.promoDiscountType,afterManual);
const itemsTotal=Math.max(0,afterManual-promo);
const delivery=num(order?.deliveryCost);
return {base,manual,promo,itemsTotal,delivery,total:itemsTotal+delivery,discount:manual+promo};
}
function parseMeasure(text){
const s=String(text||'').toLowerCase().replace(/,/g,'.');
const m=s.match(/(\d+(?:\.\d+)?)\s*(кг|kg|мл|ml|л|l|г|g)(?:\s|$|[^a-zа-яё])/iu)||s.match(/(\d+(?:\.\d+)?)\s*(кг|kg|мл|ml|л|l|г|g)$/iu);
if(!m)return null;const value=Number(m[1]),u=m[2];if(!Number.isFinite(value))return null;
if(u==='кг'||u==='kg')return {kind:'food',value:value*1000,unit:'г'};
if(u==='г'||u==='g')return {kind:'food',value,unit:'г'};
if(u==='л'||u==='l')return {kind:'drink',value:value*1000,unit:'мл'};
if(u==='мл'||u==='ml')return {kind:'drink',value,unit:'мл'};
return null;
}
function ingredientWeight(row){if(!Array.isArray(row))return 0;const direct=Math.max(0,num(row[3]));if(direct)return direct;const m=String(row[0]||'').match(/[—–-]\s*(\d+(?:[.,]\d+)?)\s*г(?:\s*\/\s*шт\.?)?\s*$/i);return m?Math.max(0,num(String(m[1]).replace(',','.'))):0}
function cleanIngredientName(name){return String(name||'').replace(/\s*[—–-]\s*\d+(?:[.,]\d+)?\s*г(?:\s*\/\s*шт\.?)?\s*$/i,'').trim()}
function ingredientLine(row){if(!Array.isArray(row)||!String(row[0]||'').trim())return'';const name=cleanIngredientName(row[0]),qty=Math.max(0,num(row[1])),unit=String(row[2]||'').trim(),w=ingredientWeight(row);if(/^поз\.?$/i.test(unit)&&qty<=1)return `${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`;const shown=Number.isInteger(qty)?String(qty):qty.toLocaleString('ru-RU',{maximumFractionDigits:2});return `${shown} ${unit} ${name}${w?`${w.toLocaleString('ru-RU')} г/шт.`:''}`.replace(/\s+/g,' ').trim()}
function composition(item){
// Client-facing composition is independent from kitchen TTK ingredients.
if(Array.isArray(item?.composition)&&item.composition.length)return item.composition.map(x=>String(x||'').trim()).filter(Boolean);
if(Array.isArray(item?.ingredients)&&item.ingredients.some(x=>Array.isArray(x)&&x[0]))return item.ingredients.map(ingredientLine).filter(Boolean);
return [];
}
function orderSignature(order){
const clean={id:String(order?.id??''),event:order?.event||'',date:order?.date||'',time:order?.time||'',contact:order?.contact||'',guestsCount:num(order?.guestsCount),deliveryCost:num(order?.deliveryCost),manualDiscountValue:num(order?.manualDiscountValue),manualDiscountType:order?.manualDiscountType||'percent',promoId:order?.promoId||'',promoCode:order?.promoCode||'',promoDiscountValue:num(order?.promoDiscountValue),promoDiscountType:order?.promoDiscountType||'percent',lines:(order?.lines||[]).map(l=>({id:String(l.id),qty:num(l.qty),price:Number.isFinite(Number(l.price))?Number(l.price):null}))};
return JSON.stringify(clean);
}
function currentSavedOrder(id){return getOrders().find(o=>String(o.id)===String(id))||null}
function currentOfferSourceOrder(id,preferDraft=false){
const saved=currentSavedOrder(id),d=getDraft();
if(preferDraft&&d&&String(d.id)===String(id))return d;
return saved;
}
function snapshotIsStale(order,snapshot){
if(!snapshot||Number(snapshot.version||0)<7||snapshot.sourceSignature!==orderSignature(order))return true;
const all=getBoxes();
return (snapshot.items||[]).some(item=>{
const missingName=!String(item?.name||'').trim()||String(item?.name||'').trim()==='Позиция';
const missingPhoto=!String(item?.photo||'').trim();
return (missingName||missingPhoto)&&Boolean(resolveCatalogLine(item,all));
});
}
function sectionTitle(item){const s=String(item?.catalogSection||'').trim();return s||String(item?.categoryName||'Меню')}
function canapePieces(item){
// v17.5.1: any food box may contain countable ready-to-serve pieces, not only sections named "canape".
if(![0,5].includes(Number(item?.category??item?.categoryId??0)))return 0;
const explicit=Math.max(0,num(item?.pieces));
if(explicit>0)return explicit;
let total=0;
if(Array.isArray(item?.ingredients)){
for(const row of item.ingredients){
if(!Array.isArray(row))continue;
const value=num(row[1]),unit=String(row[2]||'').toLowerCase().replace(/\s+/g,'');
if(/^(шт\.?|пор\.?|кус\.?|шарик(?:а|ов|и)?|креманк(?:а|и|е)?)$/i.test(unit))total+=value;
}
}
if(total>0)return total;
for(const text of Array.isArray(item?.composition)?item.composition:[]){
const re=/(\d+(?:[.,]\d+)?)\s*(?:шт\.?|пор\.?|кус\.?)/ig;let m;while((m=re.exec(String(text||''))))total+=num(String(m[1]).replace(',','.'));
}
return total;
}
function multiplyLeadingPieces(text,qty){
const raw=String(text||'').trim(),q=Math.max(0,num(qty));if(!raw||q<=0)return raw;
return raw.replace(/^(\d+(?:[.,]\d+)?)\s*(шт\.?|пор\.?|кус\.?)\s+/i,(m,n,u)=>{
const total=Number(String(n).replace(',','.'))*q;
const shown=Number.isInteger(total)?String(total):total.toLocaleString('ru-RU',{maximumFractionDigits:2});
return `${shown} ${u} `;
});
}
function totalComposition(item,qty,categoryId){
const list=composition(item);return [0,5].includes(Number(categoryId))?list.map(x=>multiplyLeadingPieces(x,qty)):list;
}
function offerComposition(item){
const list=Array.isArray(item?.compositionTotal)&&item.compositionTotal.length?item.compositionTotal:(Array.isArray(item?.composition)?item.composition:[]);
return list.map(x=>String(x||'').trim()).filter(Boolean);
}
function roundUp(value,step=1){const n=Math.max(0,Number(value||0)),s=Math.max(1,Number(step||1));return Math.ceil((n-1e-9)/s)*s}
function guestRangeLabel(raw){
const value=String(raw||'').trim().replace(/(\d)\s*[-–—]\s*(\d)/g,'$1$2');
if(!value)return'';return /(?:гост|чел)/i.test(value)?value:`${value} гостей`;
}
function boxQuantityLabel(qty){const n=Math.max(0,Math.round(num(qty)));return n?`${n} ${plural(n,'бокс','бокса','боксов')}`:''}
function totalMeasureLabel(item){
const raw=String(item?.weight||'').trim(),qty=Math.max(0,num(item?.qty));if(!raw||!qty)return'';
const measure=parseMeasure(raw);if(!measure)return qty===1?`Вес бокса: ${raw}`:`Вес бокса: ${raw} × ${qty}`;
const total=measure.value*qty,shown=(Number.isInteger(total)?total:Math.round(total*100)/100).toLocaleString('ru-RU',{maximumFractionDigits:2});
return `Общий ${measure.kind==='drink'?'объём':'вес'}: ${shown} ${measure.unit}`;
}
function weightLabel(item){
const raw=String(item?.weight||'').trim(),isBox=[0,5].includes(Number(item?.categoryId??item?.category??0)),pieces=isBox?Math.max(0,num(item?.pieces)):0,qty=Math.max(0,num(item?.qty));
if(isBox){
const parts=[];
const totalWeight=totalMeasureLabel(item);if(totalWeight)parts.push(totalWeight);else if(raw)parts.push(`Общий вес: ${raw}`);
if(pieces&&qty)parts.push(`Всего: ${(pieces*qty).toLocaleString('ru-RU',{maximumFractionDigits:2})} шт.`);
return parts.join(' · ')
}
return raw?`Вес / объём: ${raw}`:'';
}
async function createSnapshot(order){
const all=getBoxes(),p=priceCalc(order),guests=num(order?.guestsCount)||num($('eventGuests')?.value),selected=new Set((order?.lines||[]).map(l=>String(l.id)));
let foodGrams=0,drinkMl=0,foodPieces=0;
const items=(order?.lines||[]).map(line=>{
const item=resolveCatalogLine(line,all)||{};const qty=num(line.qty),price=linePrice(line,item),measure=parseMeasure(item.weight),rawItemCategory=item.category,rawLineCategory=line.category,itemCategory=rawItemCategory!==undefined&&rawItemCategory!==null&&rawItemCategory!==''?Number(rawItemCategory):NaN,lineCategory=rawLineCategory!==undefined&&rawLineCategory!==null&&rawLineCategory!==''?Number(rawLineCategory):NaN,categoryId=Number.isFinite(itemCategory)?itemCategory:(Number.isFinite(lineCategory)?lineCategory:-1),pieces=canapePieces(item),comp=composition(item),compositionTotal=[0,5].includes(Number(categoryId))?comp.map(x=>multiplyLeadingPieces(x,qty)):comp;
if(measure){if(measure.kind==='drink'||categoryId===3)drinkMl+=measure.value*qty;else if([0,5].includes(categoryId))foodGrams+=measure.value*qty;}
if([0,5].includes(categoryId)&&pieces>0)foodPieces+=pieces*qty;
return {id:String(item.id??line.id??''),name:String(item.name||line.name||'Позиция'),categoryId,categoryName:catName(categoryId),catalogSection:String(item.catalogSection||''),qty,unitPrice:price,sum:price*qty,weight:String(item.weight||''),boxGuests:String(item.guests||''),photo:String(item.photo||''),composition:comp,compositionTotal,pieces};
}).filter(x=>x.qty>0);
const extras=all.filter(item=>[1,2,3].includes(Number(item.category))&&!selected.has(String(item.id))&&!item.hidden).slice(0,6).map(item=>({name:String(item.name||''),categoryName:catName(item.category)})).filter(x=>x.name);
return {version:8,createdAt:new Date().toISOString(),orderId:order?.id||'',sourceSignature:orderSignature(order),event:String(order?.event||''),date:String(order?.date||''),time:String(order?.time||''),client:String(order?.contact||''),guests,foodGrams,foodPieces,drinkMl,pricing:p,promoCode:String(order?.promoCode||''),items,extras,offerOverrides:structuredClone(order?.clientOfferSnapshot?.offerOverrides||{})};
}
function persistSnapshot(orderId,snapshot){
let list=getOrders(),i=list.findIndex(o=>String(o.id)===String(orderId));if(i<0)return false;
list[i].clientOfferSnapshot=snapshot;list[i].clientOfferUpdatedAt=snapshot.createdAt;
try{if(typeof orders!=='undefined'&&Array.isArray(orders)){const j=orders.findIndex(o=>String(o.id)===String(orderId));if(j>=0){orders[j].clientOfferSnapshot=snapshot;orders[j].clientOfferUpdatedAt=snapshot.createdAt;}}}catch(_){}
const d=getDraft();if(d&&String(d.id)===String(orderId)){d.clientOfferSnapshot=snapshot;d.clientOfferUpdatedAt=snapshot.createdAt;}
try{if(typeof persist==='function')persist();else localStorage.setItem('sunOrders',JSON.stringify(list));return true}catch(err){console.error(err);toast('Не удалось сохранить предложение: возможно, переполнено локальное хранилище.','error');return false}
}
function ensureModal(){
if($('sunClientOfferModal'))return;
const modal=document.createElement('div');modal.id='sunClientOfferModal';modal.className='modal';modal.innerHTML=`<div class="dialog"><div class="sun-offer-modal-head"><div><h2>Предложение клиенту</h2><p class="hint" id="sunOfferMeta"></p></div><div class="spacer"></div><div id="sunOfferStale" class="sun-offer-stale">Заказ изменён — предложение будет автоматически обновлено из текущих данных.</div><div class="sun-offer-modal-actions"><button class="primary" id="sunOfferPdf" type="button">Скачать PDF</button><button class="outline" id="sunOfferEdit" type="button">Редактировать</button><button class="outline" id="sunOfferRefresh" type="button">Обновить</button><button class="outline" id="sunOfferClose" type="button">Закрыть</button></div></div><section class="sun-offer-editor" id="sunOfferEditor"><div class="sun-offer-editor-grid"><label>Название мероприятия<input data-offer-data="event" type="text"></label><label>Имя клиента<input data-offer-data="client" type="text"></label><label>Дата<input data-offer-data="date" type="date"></label><label>Гостей<input data-offer-data="guests" type="number" min="0" step="1"></label><label>Метка предложения<input data-offer-text="proposalLabel" type="text"></label><label>Заголовок меню<input data-offer-text="menuTitle" type="text"></label><label class="wide">Короткий текст первого блока<textarea data-offer-text="heroNote"></textarea></label><label>Заголовок продающего блока<input data-offer-text="salesTitle" type="text"></label><label class="wide">Продающий текст<textarea data-offer-text="salesNote"></textarea></label><label>Заголовок итогов<input data-offer-text="summaryTitle" type="text"></label><label>Заголовок стоимости<input data-offer-text="totalPriceTitle" type="text"></label><label>Заголовок блока «Всё под контролем»<input data-offer-text="controlTitle" type="text"></label><label>Блок рекомендаций<input data-offer-text="extrasTitle" type="text"></label><label class="wide">Пункты блока «Всё под контролем» — каждый с новой строки<textarea data-offer-list="controlLines" rows="7"></textarea></label><label>Заголовок фото на столе<input data-offer-text="finalGalleryTitle" type="text"></label><label class="wide">Примечание внизу<textarea data-offer-text="footerNote"></textarea></label><div class="sun-offer-editor-gallery wide"><div><b>Фото «Как это выглядит на вашем столе»</b><small>В PDF автоматически попадёт 1 или 2 фото только если на последней странице есть свободное место.</small></div><div class="sun-offer-editor-gallery-images"><img src="offer-gallery/001.jpg" alt=""><img src="offer-gallery/002.jpg" alt=""></div></div></div><p class="sun-offer-editor-note">Изменения относятся только к этому предложению. Просмотр ниже — те же страницы, которые попадут в скачанный PDF.</p></section><div class="sun-offer-frame-wrap"><div id="sunClientOfferPreview" aria-label="Предпросмотр предложения"></div></div></div>`;document.body.appendChild(modal);
$('sunOfferClose').onclick=()=>window.closeModal?.('sunClientOfferModal');
$('sunOfferRefresh').onclick=refreshCurrentOffer;$('sunOfferPdf').onclick=downloadCurrentOfferPdf;$('sunOfferEdit').onclick=toggleOfferEditor;bindOfferEditor();
}
function dateText(raw){if(!raw)return'Дата уточняется';const d=new Date(`${raw}T00:00:00`);return Number.isNaN(d.getTime())?raw:d.toLocaleDateString('ru-RU',{day:'numeric',month:'long'})}
function perGuest(value,guests){return guests>0?value/guests:0}
function metricValue(value,unit,guests){if(!guests||!value)return'—';const per=value/guests,rounded=unit==='г'?roundUp(per,5):roundUp(per,1);return `${rounded.toLocaleString('ru-RU')} ${unit}`}
function itemPerGuest(item,guests){const measure=parseMeasure(item?.weight);if(!guests||!measure)return'—';const per=measure.value*num(item.qty)/guests,rounded=measure.kind==='drink'?roundUp(per,1):roundUp(per,5);return `${rounded.toLocaleString('ru-RU')} ${measure.kind==='drink'?'мл':'г'}`}
const EXTRA_SERVICES=[
['Напитки',''],
['Десерты',''],
['Фрукты',''],
['Одноразовая посуда','']
];
const DELIVERY_CONTROL=[
'Фирменная упаковка',
'Уточняем детали доставки',
'Стоимость заказа не изменится',
'Готовим закуски в день мероприятия',
'Контролируем заказ до момента передачи',
'Привозим заказ вовремя и передаём вам лично',
'Всё организованно! Вы просто встречаете гостей!'
];
const FINAL_GALLERY_PATHS=[...(Array.isArray(window.SUN_OFFER_GALLERY_DATA)?window.SUN_OFFER_GALLERY_DATA.filter(Boolean).slice(0,2):[])];if(FINAL_GALLERY_PATHS.length<2)FINAL_GALLERY_PATHS.push(...Array.from({length:2-FINAL_GALLERY_PATHS.length},(_,i)=>`offer-gallery/${String(FINAL_GALLERY_PATHS.length+i+1).padStart(3,'0')}.jpg`));
function plural(n,one,few,many){const x=Math.abs(Number(n||0))%100,y=x%10;return x>10&&x<20?many:y>1&&y<5?few:y===1?one:many}
const SAFE_PIXEL='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9Zwv8AAAAASUVORK5CYII=';
const isSafeRasterDataUrl=src=>/^data:image\/(?!svg\+xml)[a-z0-9.+-]+;/i.test(String(src||''));
async function asDataUrlOne(src){
if(!src)return'';
const raw=String(src).trim();
if(isSafeRasterDataUrl(raw))return raw;
if(/^data:/i.test(raw))return'';
try{
const url=new URL(raw,document.baseURI||location.href);
if(!['http:','https:','blob:'].includes(url.protocol))return'';
const sameOrigin=url.protocol==='blob:'||url.origin===location.origin;
const res=await fetch(url,{mode:'cors',credentials:sameOrigin?'same-origin':'omit',cache:'force-cache'});
if(!res.ok)throw new Error(`HTTP ${res.status}`);
const blob=await res.blob();
if(!/^image\/(?!svg\+xml)[a-z0-9.+-]+$/i.test(String(blob.type||'')))throw new Error('Неподдерживаемый формат изображения');
const data=await new Promise((resolve,reject)=>{const r=new FileReader();r.onload=()=>resolve(String(r.result||''));r.onerror=()=>reject(r.error||new Error('FileReader'));r.readAsDataURL(blob)});
return isSafeRasterDataUrl(data)?data:'';
}catch(err){return''}
}
async function asDataUrl(src){
const list=window.SunCatalogPhoto?.candidates?.(src)||[src];
for(const candidate of list){const data=await asDataUrlOne(candidate);if(data)return data;}
console.warn('PDF image unavailable after all fallbacks:',src);
return'';
}
async function preparedSnapshot(snapshot){
const copy=structuredClone(snapshot),liveCatalog=getBoxes(),legacyCatalog=getLegacyBoxes();
// Preview uses normal image URLs. Expensive Base64 conversion is deferred until PDF download.
let customLogo='',customBrandName='';try{customLogo=localStorage.getItem('sunPdfBrandLogoV1')||'';customBrandName=localStorage.getItem('sunPdfBrandNameV1')||'';}catch(_){}
copy.logo=customLogo||EMBEDDED_LOGO||'sun-logo.png';
copy.brandName=customBrandName||'Солнце Кейтеринг';
copy.finalPhoto='';
copy.finalGallery=FINAL_GALLERY_PATHS.slice(0,2);
const dataOverrides=copy.offerOverrides?.data&&typeof copy.offerOverrides.data==='object'?copy.offerOverrides.data:{};
if(Object.prototype.hasOwnProperty.call(dataOverrides,'event'))copy.event=String(dataOverrides.event??'');
if(Object.prototype.hasOwnProperty.call(dataOverrides,'client'))copy.client=String(dataOverrides.client??'');
if(Object.prototype.hasOwnProperty.call(dataOverrides,'date'))copy.date=String(dataOverrides.date??'');
if(Object.prototype.hasOwnProperty.call(dataOverrides,'guests'))copy.guests=Math.max(0,num(dataOverrides.guests));
for(const item of copy.items){
// Quantity and price come from the order snapshot; catalog presentation data is always current.
const live=liveCatalog.find(x=>String(x.id)===String(item.id))||legacyCatalog.find(x=>String(x.id)===String(item.id));
if(live){
item.name=String(live.name||item.name||'Позиция');
if(Number.isFinite(Number(live.category)))item.categoryId=Number(live.category);
item.categoryName=catName(item.categoryId);
item.photo=String(live.photo||'');item.weight=String(live.weight||'');item.boxGuests=String(live.guests||'');item.catalogSection=String(live.catalogSection||item.catalogSection||'');
const comp=composition(live);item.composition=comp;item.compositionTotal=[0,5].includes(Number(item.categoryId))?comp.map(x=>multiplyLeadingPieces(x,item.qty)):comp;item.pieces=canapePieces(live);
}else item.compositionTotal=[0,5].includes(Number(item.categoryId))?(item.composition||[]).map(x=>multiplyLeadingPieces(x,item.qty)):(item.composition||[]);
item.photoData=item.photo||copy.logo;
}
copy.foodGrams=0;copy.drinkMl=0;copy.foodPieces=0;
for(const item of copy.items){const measure=parseMeasure(item.weight);if(measure){if(measure.kind==='drink'||Number(item.categoryId)===3)copy.drinkMl+=measure.value*num(item.qty);else if([0,5].includes(Number(item.categoryId)))copy.foodGrams+=measure.value*num(item.qty);}if([0,5].includes(Number(item.categoryId))&&num(item.pieces)>0)copy.foodPieces+=num(item.pieces)*num(item.qty)}
try{const cfg=offerSettingsForSnapshot(copy);copy.controlLines=Array.isArray(cfg.controlLines)&&cfg.controlLines.length?cfg.controlLines:DELIVERY_CONTROL;copy.extraServices=Array.isArray(cfg.extraServices)&&cfg.extraServices.length?cfg.extraServices:EXTRA_SERVICES.map(x=>x[0]);copy.controlTitle=cfg.texts?.controlTitle||'';copy.extrasTitle=cfg.texts?.extrasTitle||'';}catch(_){}
return copy;
}
async function preparedPdfSnapshot(prepared){
const copy=structuredClone(prepared),cache=new Map();
const resolve=src=>{const key=String(src||'');if(!key)return Promise.resolve('');if(!cache.has(key))cache.set(key,asDataUrl(key));return cache.get(key);};
copy.logo=(await resolve(copy.logo||'sun-logo.png'))||SAFE_PIXEL;
copy.finalGallery=(await Promise.all((copy.finalGallery||[]).map(resolve))).filter(Boolean);
await Promise.all((copy.items||[]).map(async item=>{item.photoData=(await resolve(item.photoData||item.photo||''))||copy.logo;}));
return copy;
}
const OFFER_TEMPLATE_KEY='sunOfferTemplateV1';
const OFFER_TEMPLATE_IDS=new Set(['cream-elegance','neon-menu','emerald-circles','midnight-checklist','gourmet-hero','diamond-gold','light','midnight-glass','editorial-grid','warm-sun','bento-cards','event-story','black-gold','personal-letter','event-ticket','solar-experience','midnight-compact','emerald-gold','neon-emerald']);
function offerTemplateId(){
try{
const api=window.SunOfferTemplate?.get?.();
if(api?.id&&OFFER_TEMPLATE_IDS.has(api.id))return api.id;
const raw=JSON.parse(localStorage.getItem(OFFER_TEMPLATE_KEY)||'{}')||{};
if(raw.id&&OFFER_TEMPLATE_IDS.has(raw.id))return raw.id;
}catch(_){}
return 'light';
}
const CLIENT_OFFER_SETTINGS_KEY='sunClientOfferSettingsV1';
const CLIENT_OFFER_DEFAULTS={metricMode:'weight',showAmountPerGuest:true,showBoxCount:true,showPriceBreakdown:true,showDiscountBadge:true,showGallery:true,showControl:true,showExtras:true,showFinalPhoto:true,showClientName:true,showDate:true,showGuests:true,showFooterNote:true,texts:{proposalLabel:'Индивидуальное предложение',menuTitle:'Меню',summaryTitle:'Итого',totalPriceTitle:'Итоговая стоимость',heroNote:'Фирменные боксы с закусками — готово к подаче на стол.',salesTitle:'Почему с нами удобно',salesNote:'Меню составлено специально под ваше событие. Закуски приезжают в фирменных боксах, их удобно поставить на стол и подать без сложной сервировки. Мы готовим заказ в день доставки, используем свежие ингредиенты и контролируем детали до передачи заказа.',finalGalleryTitle:'Как это выглядит на вашем столе',weightMetricLabel:'Вес еды на гостя',piecesMetricLabel:'Канапе на гостя',amountPerGuestLabel:'Сумма на гостя',boxCountLabel:'Количество боксов',pricingTitle:'Расчёт стоимости',priceItemsLabel:'Стоимость позиций',discountLabel:'Скидка',promoLabel:'Промокод',menuAfterDiscountLabel:'Меню после скидок',deliveryLabel:'Доставка',totalLabel:'Итого',controlTitle:'Всё под контролем команды Солнца',extrasTitle:'Дополнительно к заказу рекомендуем',footerNote:'Доставка считается отдельно от меню и не участвует в скидках.'},controlLines:[...DELIVERY_CONTROL],extraServices:EXTRA_SERVICES.map(x=>x[0])};
function offerSettings(){
try{const api=window.SunClientOfferSettings?.get?.();if(api)return api}catch(_){}
try{
const raw=JSON.parse(localStorage.getItem(CLIENT_OFFER_SETTINGS_KEY)||'{}')||{},d=structuredClone(CLIENT_OFFER_DEFAULTS);
Object.keys(d).forEach(k=>{if(k!=='texts'&&k!=='controlLines'&&k!=='extraServices'&&raw[k]!=null)d[k]=raw[k]});
if(raw.texts&&typeof raw.texts==='object')d.texts={...d.texts,...raw.texts};
if(Array.isArray(raw.controlLines)&&raw.controlLines.length)d.controlLines=raw.controlLines;
if(Array.isArray(raw.extraServices)&&raw.extraServices.length)d.extraServices=raw.extraServices;
return d;
}catch(_){return structuredClone(CLIENT_OFFER_DEFAULTS)}
}
function offerSettingsForSnapshot(s){
const cfg=offerSettings(),textOverrides=s?.offerOverrides?.texts;
if(textOverrides&&typeof textOverrides==='object')cfg.texts={...cfg.texts,...textOverrides};
if(Array.isArray(s?.offerOverrides?.controlLines))cfg.controlLines=s.offerOverrides.controlLines.map(x=>String(x||'').trim()).filter(Boolean).slice(0,20);
// Table photos are automatic: they are shown only when the final page has free space.
cfg.showFinalPhoto=true;
return cfg;
}
function offerBoxCount(items){return (items||[]).filter(item=>[0,5].includes(Number(item.categoryId??0))).reduce((n,item)=>n+num(item.qty),0)}
function offerPrimaryMetric(s,cfg,g){
if(cfg.metricMode==='pieces')return {value:g&&num(s.foodPieces)?`${roundUp(num(s.foodPieces)/g,1).toLocaleString('ru-RU')} шт.`:'—',label:cfg.texts?.piecesMetricLabel||'Канапе на гостя'};
return {value:metricValue(s.foodGrams,'г',g),label:cfg.texts?.weightMetricLabel||'Вес еды на гостя'};
}
function offerIconSvg(title=''){
const t=String(title).toLowerCase();
if(/рыб|мор|лосос|кревет/.test(t))return '<svg viewBox="0 0 24 24"><path d="M4 12c3.4-4 7.2-5.6 11.5-3.5L20 6v4.3L22 12l-2 1.7V18l-4.5-2.5C11.2 17.6 7.4 16 4 12Z"/><circle cx="10" cy="11" r=".8"/></svg>';
if(/мяс|ростбиф|бургер|ветчин|салям/.test(t))return '<svg viewBox="0 0 24 24"><path d="M7 7c3-4 9-4 11 1 2 5-2 10-7 10-5 0-8-5-5-9l1-2Z"/><path d="M9 10c2-2 5-2 6 1"/></svg>';
if(/вег|овощ|салат/.test(t))return '<svg viewBox="0 0 24 24"><path d="M6 18c0-7 5-11 12-12-1 7-5 12-12 12Z"/><path d="M7 17c3-3 5-5 9-8"/></svg>';
if(/десерт|слад|макарон|торт/.test(t))return '<svg viewBox="0 0 24 24"><path d="M6 9h12l-1 10H7L6 9Z"/><path d="M8 9c0-3 2-5 4-5s4 2 4 5"/><path d="M10 5c1 1 3 1 4 0"/></svg>';
return '<svg viewBox="0 0 24 24"><path d="M4 8 12 4l8 4-8 4-8-4Z"/><path d="M4 8v8l8 4 8-4V8M12 12v8"/></svg>';
}
function offerGroups(items){
const map=new Map();
(items||[]).forEach(item=>{const k=sectionTitle(item)||'Меню';if(!map.has(k))map.set(k,[]);map.get(k).push(item)});
return [...map.entries()].map(([name,list])=>({name,list}));
}
function editorialGroups(items){
return offerGroups(items).map(group=>{
const weights=group.list.map(item=>parseMeasure(item.weight)).filter(m=>m&&m.kind!=='drink').map(m=>Math.round(m.value)).filter(v=>v>0);
const minW=weights.length?Math.min(...weights):0,maxW=weights.length?Math.max(...weights):0;
const qty=group.list.reduce((n,item)=>n+Math.max(0,num(item.qty)),0);
const weightText=minW?(minW===maxW?`${minW} г`:`${minW}-${maxW} г`):'';
const positions=`${group.list.length} ${plural(group.list.length,'блюдо','блюда','блюд')}`;
return {...group,qty,weightText,positions,meta:[weightText,positions].filter(Boolean).join(' · ')};
});
}
function offerHtml(s){
const templateId=OFFER_TEMPLATE_IDS.has(s.offerTemplateId)?s.offerTemplateId:offerTemplateId();
const themeClass=`theme-${templateId}`;
const cfg=offerSettingsForSnapshot(s),p=s.pricing||{},g=num(s.guests),menuPer=perGuest(p.itemsTotal,g);
const items=s.items||[],boxCount=offerBoxCount(items),positionCount=items.length,groups=offerGroups(items),heroPhoto=items.find(item=>item.photoData)?.photoData||s.finalGallery?.[0]||s.logo||'';
const primaryMetric=offerPrimaryMetric(s,cfg,g),eventName=String(s.event||'Персональное предложение').trim(),clientName=cfg.showClientName?String(s.client||'').trim():'';
const visibleDate=cfg.showDate?dateText(s.date):'',visibleGuests=cfg.showGuests&&g?`${g} ${plural(g,'гость','гостя','гостей')}`:'';
const discountPct=p.base>0?Math.round((p.discount/p.base)*100):0;
const compositionText=(item,sep=' · ')=>offerComposition(item).join(sep);
const itemMeta=item=>[weightLabel(item),compositionText(item)].filter(Boolean).join(' · ');
const photo=(item,i,cls='')=>`<figure class="offer-photo ${cls}"><span>${i+1}</span><img src="${esc(item.photoData||s.logo||'')}" alt="${esc(item.name)}" loading="lazy" decoding="async"><figcaption>${esc(item.name)}</figcaption></figure>`;
const row=item=>`<div class="offer-row"><span class="row-icon">${offerIconSvg(sectionTitle(item))}</span><div class="row-main"><b>${esc(item.name)}</b>${itemMeta(item)?`<small>${esc(itemMeta(item))}</small>`:''}</div><span class="row-qty">× ${Math.round(num(item.qty))}</span></div>`;
const groupTable=group=>`<section class="table-group"><div class="table-head"><h3>${esc(group.name)}</h3><span>Итого по позиции</span><span>Количество</span></div>${group.list.map(item=>{const comp=compositionText(item);return `<div class="table-line"><div><b>${esc(item.name)}</b>${comp?`<small>${esc(comp)}</small>`:''}</div><span>${esc(weightLabel(item)||item.weight||'—')}</span><strong>× ${Math.round(num(item.qty))}</strong></div>`}).join('')}</section>`;
const gallery=items.map((item,i)=>photo(item,i)).join('');
const controlLines=Array.isArray(cfg.controlLines)&&cfg.controlLines.length?cfg.controlLines:DELIVERY_CONTROL;
const extrasList=Array.isArray(cfg.extraServices)&&cfg.extraServices.length?cfg.extraServices:EXTRA_SERVICES.map(x=>x[0]);
const deliveryControl=controlLines.map(item=>`<li><span>✓</span>${esc(item)}</li>`).join('');
const deliveryControlShort=controlLines.slice(0,5).map(item=>`<li><span>✓</span>${esc(item)}</li>`).join('');
const extraServices=extrasList.map(name=>`<div class="extra-service"><span>+</span><b>${esc(name)}</b></div>`).join('');
const manual=p.manual?`<div><span>${esc(cfg.texts.discountLabel||'Скидка')}</span><b>- ${money(p.manual)}</b></div>`:'';
const promo=p.promo?`<div><span>${esc(cfg.texts.promoLabel||'Промокод')}${s.promoCode?` ${esc(s.promoCode)}`:''}</span><b>- ${money(p.promo)}</b></div>`:'';
const breakdown=cfg.showPriceBreakdown?`<div class="price-breakdown"><div><span>${esc(cfg.texts.priceItemsLabel||'Стоимость позиций')}</span><b>${money(p.base)}</b></div>${manual}${promo}<div><span>${esc(cfg.texts.menuAfterDiscountLabel||'Меню после скидок')}</span><b>${money(p.itemsTotal)}</b></div><div><span>${esc(cfg.texts.deliveryLabel||'Доставка')}</span><b>${money(p.delivery)}</b></div><div class="grand"><span>${esc(cfg.texts.totalLabel||'Итого')}</span><b>${money(p.total)}</b></div></div>`:'';
const metricCards=[`<div class="metric"><b>${esc(primaryMetric.value)}</b><small>${esc(primaryMetric.label)}</small></div>`,cfg.showAmountPerGuest?`<div class="metric"><b>${g?money(menuPer):'—'}</b><small>${esc(cfg.texts.amountPerGuestLabel||'Сумма на гостя')}</small></div>`:'',cfg.showBoxCount?`<div class="metric"><b>${Math.round(boxCount)}</b><small>${esc(cfg.texts.boxCountLabel||'Количество боксов')}</small></div>`:''].filter(Boolean);
const metrics=`<div class="metrics" style="--metric-count:${metricCards.length}">${metricCards.join('')}</div>`;
const infoParts=[];
if(cfg.showControl)infoParts.push(`<article class="info-card"><h2>${esc(cfg.texts.controlTitle)}</h2><ul class="control-list">${deliveryControl}</ul></article>`);
if(cfg.showExtras)infoParts.push(`<article class="info-card"><h2>${esc(cfg.texts.extrasTitle)}</h2><div class="extra-grid">${extraServices}</div></article>`);
const info=infoParts.length?`<section class="info-grid ${infoParts.length===1?'single':''}">${infoParts.join('')}</section>`:'';
const actions='';
const brand=`<header class="brandbar"><img class="brand-logo" src="${esc(s.logo||'')}" alt="Солнце Кейтеринг">${visibleDate?`<div class="brand-meta"><span>${esc(visibleDate)}</span></div>`:''}</header>`;
const heroStats=[visibleDate?`<span class="hero-stat"><i>◷</i>${esc(visibleDate)}</span>`:'',visibleGuests?`<span class="hero-stat"><i>○</i>${esc(visibleGuests)}</span>`:''].filter(Boolean).join('');
const standardHero=`<section class="hero"><div class="hero-copy"><div class="kicker">${esc(cfg.texts.proposalLabel)}</div><h1>${esc(eventName)}</h1>${clientName?`<div class="client-line">${esc(clientName)}</div>`:''}${heroStats?`<div class="hero-stats">${heroStats}</div>`:''}<div class="hero-rule"></div><p class="hero-note">${esc(cfg.texts.heroNote)}</p></div><div class="hero-photo"><img src="${esc(heroPhoto)}" alt="${esc(eventName)}"></div></section>`;
const sellingBlock=(String(cfg.texts.salesTitle||'').trim()||String(cfg.texts.salesNote||'').trim())?`<section class="sales-block"><div class="sales-accent"></div><div>${String(cfg.texts.salesTitle||'').trim()?`<h2>${esc(cfg.texts.salesTitle)}</h2>`:''}${String(cfg.texts.salesNote||'').trim()?`<p>${esc(cfg.texts.salesNote)}</p>`:''}</div></section>`:'';
const identityLine=[clientName,visibleDate,visibleGuests].filter(Boolean).join(' · ');
const blackControl=cfg.showControl?`<article class="delivery-key"><h2>${esc(cfg.texts.controlTitle)}</h2><ul class="control-list">${deliveryControlShort}</ul></article>`:'';
const blackExtras=cfg.showExtras?`<div class="addons-strip"><h2>${esc(cfg.texts.extrasTitle)}</h2><div class="extra-grid">${extraServices}</div></div>`:'';
const egSideParts=[cfg.showControl?`<div class="eg-delivery"><h2>${esc(cfg.texts.controlTitle)}</h2><ul class="control-list">${deliveryControlShort}</ul></div>`:'',cfg.showExtras?`<div class="eg-addons"><h2>${esc(cfg.texts.extrasTitle)}</h2><div class="extra-grid">${extraServices}</div></div>`:''].filter(Boolean);
const egSide=egSideParts.length?`<aside class="eg-side">${egSideParts.join('')}</aside>`:'';
const egFacts=[visibleDate,clientName,visibleGuests].filter(Boolean);
const footerNote=cfg.showFooterNote?`<div class="offer-note">${esc(cfg.texts.footerNote)}</div>`:'';
let body='';
if(templateId==='editorial-grid'){
const editorial=editorialGroups(items);
const editorialRows=editorial.map((group,gi)=>`<div class="ed-group-row"><span class="ed-group-icon">${String(gi+1).padStart(2,'0')}</span><div><b>${esc(group.name)}</b><small>${esc(group.meta)}</small></div><span class="ed-group-qty">× ${Math.round(group.qty)}</span></div>`).join('');
const editorialGallery=items.slice(0,8).map((item,i)=>photo(item,i,'ed-photo')).join('');
body=`${brand}<section class="ed-hero"><div class="ed-hero-copy"><div class="kicker">${esc(cfg.texts.proposalLabel)}</div><h1>${esc(eventName)}</h1>${clientName?`<div class="client-line">${esc(clientName)}</div>`:''}<div class="ed-hero-facts">${visibleDate?`<span>◷ ${esc(visibleDate)}</span>`:''}${visibleGuests?`<span>○ ${esc(visibleGuests)}</span>`:''}</div><div class="ed-rule"></div><p>${esc(cfg.texts.heroNote)}</p></div><div class="ed-hero-photo"><img src="${esc(heroPhoto)}" alt="${esc(eventName)}"></div></section>${sellingBlock}<div class="ed-section-title"><span>${esc(cfg.texts.menuTitle)}</span></div><section class="ed-group-grid">${editorialRows}</section>${cfg.showGallery?`<section class="ed-photo-grid">${editorialGallery}</section>`:''}<section class="summary ed-summary"><div class="summary-left"><h2 class="summary-title">${esc(cfg.texts.summaryTitle)}</h2>${metrics}</div><div class="summary-price"><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b><em>${g?`на гостя: ${money(menuPer)}`:'Финальная сумма'}</em>${cfg.showDiscountBadge&&discountPct?`<span class="discount-pill">-${discountPct}% учтено</span>`:''}</div></section>${breakdown}${info}${actions}`;
}else if(templateId==='light'){
body=`${brand}${standardHero}${sellingBlock}<div class="section-heading"><span>${esc(cfg.texts.menuTitle)}</span></div><section class="menu-grid light-menu">${items.map(row).join('')}</section>${cfg.showGallery?`<section class="gallery light-gallery">${gallery}</section>`:''}<section class="summary"><div class="summary-left"><h2 class="summary-title">${esc(cfg.texts.summaryTitle)}</h2>${metrics}</div><div class="summary-price"><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b><em>${g?`на гостя: ${money(menuPer)}`:'Финальная сумма'}</em>${cfg.showDiscountBadge&&discountPct?`<span class="discount-pill">-${discountPct}% учтено</span>`:''}</div>${breakdown}</section>${info}${actions}`;
}else if(templateId==='midnight-glass'){
body=`${brand}<section class="mg-hero"><div><span class="eyebrow">${esc(cfg.texts.proposalLabel)}</span><h1>${esc(cfg.texts.menuTitle)} на ваше событие</h1><p>${esc(eventName)}${identityLine?` · ${esc(identityLine)}`:''}</p><p class="mg-hero-note">${esc(cfg.texts.heroNote)}</p><div class="quality-icons"><span>Разнообразие блюд</span><span>Свежие ингредиенты</span><span>Премиальное качество</span></div></div><img src="${esc(heroPhoto)}" alt=""></section>${sellingBlock}<section class="mg-split"><div class="mg-list"><h2>${esc(cfg.texts.menuTitle)}</h2>${items.map(row).join('')}</div>${cfg.showGallery?`<div class="mg-gallery">${items.slice(0,8).map((item,i)=>photo(item,i)).join('')}</div>`:''}</section><section class="summary mg-summary"><div class="summary-left"><h2 class="summary-title">${esc(cfg.texts.summaryTitle)}</h2>${metrics}</div><div class="summary-price"><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b><em>${g?`на гостя: ${money(menuPer)}`:'Финальная сумма'}</em>${cfg.showDiscountBadge&&discountPct?`<span class="discount-pill">-${discountPct}% учтено</span>`:''}</div>${breakdown}</section>${info}${actions}`;
}else if(templateId==='midnight-compact'){
body=`${brand}<section class="mc-intro"><div><span class="eyebrow">${esc(cfg.texts.proposalLabel)}</span><h1>${esc(eventName)}</h1>${identityLine?`<p>${esc(identityLine)}</p>`:''}</div><div class="mc-note">${esc(cfg.texts.heroNote)}</div></section><section class="mc-menu"><div class="mc-tables">${groups.map(groupTable).join('')}</div>${cfg.showGallery?`<aside class="mc-photo-column"><h2>Боксы в заказе</h2>${items.filter(item=>[0,5].includes(Number(item.categoryId))).map((item,i)=>photo(item,i)).join('')}</aside>`:''}</section><section class="mc-total"><h2>${esc(cfg.texts.summaryTitle)}</h2>${metrics}<div class="mc-price"><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b>${cfg.showDiscountBadge&&discountPct?`<span>-${discountPct}%</span>`:''}</div></section>${info}${breakdown}${actions}`;
}else if(templateId==='black-gold'){
body=`${brand}<section class="bg-hero"><img src="${esc(heroPhoto)}" alt=""><div class="bg-overlay"><span>${esc(cfg.texts.proposalLabel)}</span><h1>Кейтеринг<br>для вашего события</h1><p>${esc(eventName)}${identityLine?`<br>${esc(identityLine)}`:''}</p></div></section><div class="section-heading editorial"><span>${esc(cfg.texts.menuTitle)} для вашего мероприятия</span></div><section class="product-grid">${items.map((item,i)=>`<article class="product-card"><div class="product-photo"><img src="${esc(item.photoData||s.logo||'')}" alt="${esc(item.name)}"><span>${String(i+1).padStart(2,'0')}</span><b>×${Math.round(num(item.qty))}</b></div><h3>${esc(item.name)}</h3><p>${esc([weightLabel(item)].filter(Boolean).join(' · ')||sectionTitle(item))}</p>${compositionText(item,', ')?`<small>${esc(compositionText(item,', '))}</small>`:''}</article>`).join('')}</section><section class="bg-bottom ${blackControl?'':'single'}"><article><h2>${esc(cfg.texts.summaryTitle)}</h2>${metrics}<div class="bg-price">${money(p.total)}${g?`<small>на гостя: ${money(menuPer)}</small>`:''}</div></article>${blackControl}</section>${blackExtras}${breakdown}${actions}`;
}else if(templateId==='emerald-gold'){
body=`${brand}<section class="eg-hero"><div class="eg-copy"><span class="eyebrow">${esc(cfg.texts.proposalLabel)}</span><h1>${esc(eventName)}</h1><p>${esc(cfg.texts.heroNote)}</p>${egFacts.length?`<div class="eg-facts" style="--fact-count:${egFacts.length}">${egFacts.map(x=>`<span>${esc(x)}</span>`).join('')}</div>`:''}<div class="eg-gold-summary">${metrics}<div><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b></div></div></div><div class="eg-photo-stack"><img class="main" src="${esc(heroPhoto)}" alt="">${items[1]?`<img class="secondary" src="${esc(items[1].photoData||s.logo||'')}" alt="">`:''}</div></section>${sellingBlock}<section class="eg-main ${egSide?'':'single'}"><div class="eg-menu"><h2>${esc(cfg.texts.menuTitle)}</h2>${items.map(row).join('')}</div>${egSide}</section>${breakdown}${actions}`;
}else{
body=`${brand}<section class="neon-hero"><div>${visibleDate?`<span>${esc(visibleDate)}</span>`:''}<h1>${esc(eventName)}</h1><p>${esc(cfg.texts.heroNote)}</p></div><img src="${esc(heroPhoto)}" alt="">${visibleGuests?`<b>${g}<small>гостей</small></b>`:''}</section><section class="neon-quality"><p>${esc(cfg.texts.proposalLabel)}</p><span>Свежие ингредиенты</span><span>Авторские рецепты</span><span>Контроль качества</span></section><section class="neon-main"><div class="neon-tables">${groups.map(groupTable).join('')}</div><aside class="neon-gallery"><h2>Наши боксы</h2><div>${items.filter(item=>[0,5].includes(Number(item.categoryId))).map((item,i)=>photo(item,i)).join('')}</div></aside></section><section class="neon-total"><div><h2>${esc(cfg.texts.summaryTitle)}</h2>${metrics}</div><div><small>${esc(cfg.texts.totalPriceTitle)}</small><b>${money(p.total)}</b>${cfg.showDiscountBadge&&discountPct?`<span>-${discountPct}%</span>`:''}</div></section>${info}${breakdown}${actions}`;
}
const finalGalleryItems=cfg.showFinalPhoto&&Array.isArray(s.finalGallery)?s.finalGallery.filter(Boolean).slice(0,2):[];
const finalPhoto=finalGalleryItems.length?`<section class="final-gallery-card"><h2>${esc(cfg.texts.finalGalleryTitle||'Как это выглядит на вашем столе')}</h2><div class="final-gallery-grid">${finalGalleryItems.map((src,i)=>`<figure><img src="${esc(src)}" alt="Сервировка Солнце Кейтеринг ${i+1}" loading="lazy" decoding="async"></figure>`).join('')}</div></section>`:'';
return `<!doctype html><html lang="ru" class="${themeClass}"><head><meta charset="utf-8"><base href="${esc(document.baseURI||location.href)}"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><title>Предложение клиенту</title><style>
:root{--cream:#ffffff;--paper:#ffffff;--paper2:#f7f8f6;--ink:#171917;--green:#476f4d;--green-deep:#28533a;--green-soft:#6d8f6a;--gold:#c99b47;--gold2:#e2c28b;--line:#e7d8bd;--muted:#7b756b;--shadow:0 12px 36px rgba(59,65,55,.07)}*{box-sizing:border-box}html{background:var(--cream)}body{margin:0;background:var(--cream);color:var(--ink);font-family:Arial,Helvetica,sans-serif;-webkit-font-smoothing:antialiased}.page{width:min(1100px,100%);margin:0 auto;padding:24px 26px 48px}.brandbar{display:flex;align-items:center;justify-content:space-between;gap:18px;margin:0 0 22px}.brand-logo{width:210px;max-height:76px;object-fit:contain;object-position:left center}.brand-meta{display:flex;gap:12px;flex-wrap:wrap;justify-content:flex-end;color:var(--muted);font-size:12px}.brand-meta span{border:1px solid var(--line);border-radius:999px;padding:8px 12px;background:color-mix(in srgb,var(--paper) 88%,transparent)}.hero{display:grid;grid-template-columns:44% 56%;border:1px solid var(--line);border-radius:27px;overflow:hidden;background:var(--paper);box-shadow:var(--shadow);min-height:390px}.hero-copy{padding:38px 36px 32px;display:flex;flex-direction:column;justify-content:center}.kicker,.eyebrow{font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);font-weight:800}.hero h1{font-family:Georgia,'Times New Roman',serif;font-weight:500;font-size:48px;line-height:1.03;margin:12px 0 0;color:var(--ink)}.client-line{margin:11px 0 0;color:var(--green-deep);font-family:Georgia,'Times New Roman',serif;font-size:22px}.hero-stats{display:flex;gap:20px;flex-wrap:wrap;margin:25px 0 22px}.hero-stat{display:flex;align-items:center;gap:8px;font-size:15px}.hero-stat i{width:25px;height:25px;border:1px solid var(--green);border-radius:8px;display:grid;place-items:center;color:var(--green);font-style:normal}.hero-rule{height:1px;background:linear-gradient(90deg,var(--gold2),transparent);margin-bottom:20px}.hero-note{font-size:16px;line-height:1.55;color:color-mix(in srgb,var(--ink) 82%,var(--muted));margin:0}.sales-block{display:grid;grid-template-columns:6px minmax(0,1fr);gap:24px;margin-top:18px;padding:26px 30px;border:1px solid var(--line);border-radius:21px;background:var(--paper);box-shadow:var(--shadow)}.sales-accent{border-radius:999px;background:linear-gradient(180deg,var(--gold),var(--green))}.sales-block h2{margin:0 0 10px;font-family:Georgia,'Times New Roman',serif;font-size:25px;font-weight:500;color:var(--green-deep)}.sales-block p{margin:0;color:color-mix(in srgb,var(--ink) 84%,var(--muted));font-size:15px;line-height:1.65;white-space:pre-line;overflow-wrap:anywhere}.mg-hero-note{margin:13px 0 0!important;color:var(--muted)!important;font-size:14px!important;line-height:1.5!important}.hero-photo{min-height:390px}.hero-photo img{display:block;width:100%;height:100%;min-height:390px;object-fit:cover}.section-heading{display:flex;align-items:center;gap:14px;margin:31px 0 20px;color:var(--green-deep);font-family:Georgia,'Times New Roman',serif;font-size:28px;text-align:center}.section-heading:before,.section-heading:after{content:'';height:1px;flex:1;background:var(--gold2)}.menu-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.offer-row{display:grid;grid-template-columns:52px minmax(0,1fr) auto;align-items:center;gap:13px;min-height:80px;padding:12px 15px;border:1px solid var(--line);border-radius:17px;background:var(--paper)}.row-icon{width:45px;height:45px;border-radius:50%;display:grid;place-items:center;background:var(--green-deep);color:#fff}.row-icon svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.row-main{min-width:0}.row-main b{display:block;font-family:Georgia,'Times New Roman',serif;font-size:18px;font-weight:500;line-height:1.22}.row-main small{display:block;color:var(--muted);font-size:11px;line-height:1.4;margin-top:5px;overflow-wrap:anywhere;white-space:normal}.row-qty{min-width:52px;text-align:center;padding:8px 10px;border:1px solid var(--line);border-radius:999px;font-family:Georgia,serif}.gallery{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:9px;margin:18px 0 22px}.offer-photo{margin:0;position:relative;min-width:0}.offer-photo>span{position:absolute;left:7px;top:7px;z-index:2;min-width:23px;height:23px;padding:0 5px;border-radius:50%;display:grid;place-items:center;background:color-mix(in srgb,var(--green-deep) 90%,transparent);color:#fff;font-size:10px;font-weight:800}.offer-photo img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:13px;border:1px solid var(--line);background:var(--paper2)}.offer-photo figcaption{display:none}.summary{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(250px,.7fr);border:1px solid var(--line);border-radius:22px;background:var(--paper);overflow:hidden;box-shadow:var(--shadow);margin-top:18px}.summary-left{padding:25px 28px}.summary-title{font-family:Georgia,serif;color:var(--green-deep);font-size:26px;text-align:center;margin:0 0 18px}.metrics{display:grid;grid-template-columns:repeat(var(--metric-count,3),1fr);gap:8px}.metric{text-align:center;padding:5px 10px}.metric b{display:block;font-family:Georgia,serif;font-size:25px;font-weight:500}.metric small{display:block;color:var(--muted);font-size:10px;margin-top:6px;line-height:1.3}.summary-price{border-left:1px solid var(--line);padding:25px;display:flex;flex-direction:column;justify-content:center;text-align:center}.summary-price small,.mc-price small,.neon-total small,.eg-gold-summary small{color:var(--gold);text-transform:uppercase;letter-spacing:.06em;font-size:10px}.summary-price b,.mc-price b,.neon-total>div>b,.eg-gold-summary>div>b{font-family:Georgia,serif;color:var(--green-deep);font-size:34px;font-weight:500;margin:7px 0}.summary-price em{font-style:normal;color:var(--muted);font-size:12px}.discount-pill{align-self:center;margin-top:8px;padding:5px 11px;border-radius:8px;background:color-mix(in srgb,var(--green) 12%,var(--paper));color:var(--green);font-size:11px}.price-breakdown{grid-column:1/-1;border-top:1px solid var(--line);padding:12px 24px;background:color-mix(in srgb,var(--paper) 90%,var(--paper2))}.price-breakdown>div{display:flex;justify-content:space-between;gap:20px;padding:5px 0;color:var(--muted);font-size:11px}.price-breakdown .grand{font-weight:800;color:var(--green-deep);font-size:13px;border-top:1px dashed var(--line);padding-top:8px;margin-top:3px}.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:16px}.info-card{border:1px solid var(--line);border-radius:20px;background:var(--paper);padding:22px 24px}.info-card h2{font-family:Georgia,serif;color:var(--green-deep);font-size:19px;font-weight:500;text-align:center;margin:0 0 13px}.control-list{display:grid;gap:7px;margin:0;padding:0;list-style:none}.control-list li{display:flex;align-items:flex-start;gap:8px;font-size:11px;line-height:1.4}.control-list li span{flex:0 0 16px;width:16px;height:16px;border-radius:50%;display:grid;place-items:center;background:color-mix(in srgb,var(--green) 16%,var(--paper));color:var(--green-deep);font-size:9px}.extra-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}.extra-service{display:flex;align-items:center;gap:8px;font-size:11px}.extra-service span{width:17px;height:17px;border-radius:50%;display:grid;place-items:center;background:color-mix(in srgb,var(--gold) 16%,var(--paper));color:var(--gold)}.footer-actions{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:18px}.footer-action{border-radius:15px;min-height:56px;display:flex;align-items:center;justify-content:center;border:1px solid var(--line);font-family:Georgia,serif;font-size:17px;text-decoration:none;cursor:pointer}.footer-action.download{background:transparent;color:var(--green-deep);border-color:var(--gold2)}.footer-action.order{background:var(--green-deep);color:#fff}.final-photo-card{margin-top:22px;border-radius:22px;overflow:hidden;border:1px solid var(--line)}.final-photo-card img{display:block;width:100%;height:auto}.final-gallery-card{margin-top:22px;padding:24px;border:1px solid var(--line);border-radius:22px;background:var(--paper)}.final-gallery-card h2{margin:0 0 18px;text-align:center;font-family:Georgia,'Times New Roman',serif;font-size:27px;font-weight:500;color:var(--green-deep)}.final-gallery-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.final-gallery-grid figure{margin:0;overflow:hidden;border-radius:14px;border:1px solid var(--line);background:var(--paper2);aspect-ratio:4/3}.final-gallery-grid img{display:block;width:100%;height:100%;object-fit:cover}.footer{display:flex;justify-content:space-between;gap:12px;padding:22px 3px 0;color:var(--muted);font-size:10px}
.table-group{border:1px solid var(--line);border-radius:16px;overflow:hidden;background:var(--paper);margin-bottom:12px}.table-head,.table-line{display:grid;grid-template-columns:minmax(0,1fr) 190px 72px;gap:10px;align-items:center}.table-head{padding:13px 15px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px}.table-head h3{margin:0;color:var(--green-deep);font-size:16px}.table-line{padding:12px 15px;border-bottom:1px solid color-mix(in srgb,var(--line) 65%,transparent);font-size:12px}.table-line:last-child{border-bottom:0}.table-line b{display:block}.table-line small{display:block;color:var(--muted);font-size:9px;line-height:1.4;margin-top:5px;overflow-wrap:anywhere;white-space:normal}.table-line>span,.table-line>strong{text-align:center}.table-line>span{white-space:normal;line-height:1.35;font-size:10px}.table-line>strong{white-space:nowrap}
html.theme-editorial-grid,body.theme-editorial-grid{--cream:#fbfaf6;--paper:#fffefb;--paper2:#f7f4ed;--ink:#163127;--green:#245f46;--green-deep:#1f5a42;--green-soft:#e9f1eb;--gold:#b99355;--gold2:#d8c4a2;--line:#e8dfcf;--muted:#776f62;background:#fbfaf6;color:var(--ink)}
.theme-editorial-grid .page{max-width:980px;padding:20px 22px 42px}.theme-editorial-grid .brandbar{margin-bottom:16px}.theme-editorial-grid .brand-logo{width:205px}.ed-hero{display:grid;grid-template-columns:43% 57%;min-height:360px;border:1px solid var(--line);border-radius:18px;overflow:hidden;background:var(--paper)}.ed-hero-copy{padding:30px 28px;display:flex;flex-direction:column;justify-content:center}.ed-hero h1{font-family:Georgia,'Times New Roman',serif;font-size:43px;line-height:1.04;font-weight:500;margin:10px 0 4px;color:var(--ink)}.ed-hero-facts{display:flex;gap:18px;flex-wrap:wrap;margin:22px 0 17px;font-size:13px;color:var(--ink)}.ed-rule{height:1px;background:linear-gradient(90deg,var(--gold2),transparent);margin:0 0 17px}.ed-hero-copy p{margin:0;color:var(--muted);font-size:13px;line-height:1.55}.ed-hero-photo{min-height:360px}.ed-hero-photo img{width:100%;height:100%;min-height:360px;display:block;object-fit:cover}.ed-section-title{display:flex;align-items:center;gap:18px;margin:22px 0 14px;font-family:Georgia,'Times New Roman',serif;font-size:25px;color:var(--green-deep);text-align:center}.ed-section-title:before,.ed-section-title:after{content:'';height:1px;flex:1;background:var(--gold2)}.ed-group-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}.ed-group-row{display:grid;grid-template-columns:44px minmax(0,1fr) auto;align-items:center;gap:10px;min-height:65px;padding:8px 12px;border:1px solid var(--line);border-radius:13px;background:var(--paper)}.ed-group-icon{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;background:var(--green-deep);color:#fff}.ed-group-icon{font-size:10px;font-weight:800;letter-spacing:.02em}.ed-group-icon svg{display:none}.ed-group-row b{display:block;font-family:Georgia,'Times New Roman',serif;font-size:15px;font-weight:500;color:var(--ink);line-height:1.2}.ed-group-row small{display:block;margin-top:4px;color:var(--gold);font-size:10px}.ed-group-qty{min-width:48px;text-align:center;padding:6px 9px;border:1px solid var(--line);border-radius:999px;font-size:11px;color:var(--ink)}.ed-photo-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;margin:11px 0 14px}.ed-photo-grid .offer-photo{overflow:hidden;border-radius:10px}.ed-photo-grid .offer-photo img{aspect-ratio:1.28/1;border-radius:10px}.ed-photo-grid .offer-photo>span{display:none}.ed-summary{margin-top:12px}.theme-editorial-grid .summary{border-radius:16px;box-shadow:none}.theme-editorial-grid .info-card{border-radius:15px}.theme-editorial-grid .sales-block{box-shadow:none;border-radius:15px;padding:20px 24px;margin-top:12px}.theme-editorial-grid .final-gallery-card{border-radius:16px}.theme-editorial-grid>.price-breakdown,.theme-editorial-grid .price-breakdown{background:#fbfaf6;border:1px solid var(--line);border-radius:14px;margin-top:10px}@media(max-width:760px){.ed-hero{grid-template-columns:1fr}.ed-hero-photo{min-height:260px}.ed-hero-photo img{min-height:260px}.ed-group-grid{grid-template-columns:1fr}.ed-photo-grid{grid-template-columns:1fr 1fr}}
html.theme-midnight-glass,body.theme-midnight-glass{--cream:#071521;--paper:#0b2230;--paper2:#0b1b27;--ink:#f2f5f6;--green:#1bb69b;--green-deep:#e4bd6b;--gold:#d5aa56;--gold2:#5f5131;--line:#294a5b;--muted:#9caeb7;background:radial-gradient(circle at 40% 0,#103b3f 0,transparent 34%),#06131d;color:var(--ink)}.theme-midnight-glass .brand-logo{filter:none}.mg-hero{display:grid;grid-template-columns:55% 45%;border:1px solid var(--line);border-radius:24px;overflow:hidden;background:linear-gradient(145deg,#0b2830,#071620);min-height:370px}.mg-hero>div{padding:48px 44px}.mg-hero h1{font-family:Georgia,serif;font-size:48px;font-weight:500;line-height:1.06;margin:17px 0 13px}.mg-hero p{color:var(--muted);font-size:17px}.mg-hero>img{width:100%;height:100%;object-fit:cover;min-height:370px}.quality-icons{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:32px}.quality-icons span{padding:13px 9px;border:1px solid var(--line);border-radius:13px;text-align:center;font-size:10px}.mg-split{display:grid;grid-template-columns:1.08fr .92fr;gap:15px;margin-top:18px}.mg-list,.mg-gallery{border:1px solid var(--line);border-radius:22px;background:color-mix(in srgb,var(--paper) 92%,transparent);padding:20px}.mg-list h2{font-family:Georgia,serif;margin:0 0 14px;font-weight:500}.mg-list .offer-row{border:0;border-bottom:1px solid var(--line);border-radius:0;background:transparent;min-height:68px}.mg-gallery{display:grid;grid-template-columns:1fr 1fr;gap:9px;align-content:start}.mg-gallery .offer-photo img{aspect-ratio:1.2/1}.theme-midnight-glass .row-main b,.theme-midnight-glass .metric b{color:#fff}.theme-midnight-glass .summary,.theme-midnight-glass .info-card{background:#0a202b}.theme-midnight-glass .footer-action.order{background:linear-gradient(135deg,#11876f,#0d695a)}
html.theme-midnight-compact,body.theme-midnight-compact{--cream:#03131c;--paper:#0a1b25;--paper2:#071720;--ink:#f4f3ef;--green:#67a76e;--green-deep:#f0cb75;--gold:#dfb957;--gold2:#5c4f31;--line:#344b58;--muted:#9ca9af;background:#03131c;color:var(--ink)}.mc-intro{display:grid;grid-template-columns:1.2fr .8fr;gap:20px;border:1px solid var(--line);border-radius:18px;padding:26px 28px;background:#081c26}.mc-intro h1{font-size:36px;margin:9px 0;font-family:Arial,sans-serif}.mc-intro p,.mc-note{color:var(--muted);line-height:1.5}.mc-note{display:flex;align-items:center;border-left:1px solid var(--line);padding-left:25px}.mc-menu{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(270px,.5fr);gap:14px;margin-top:16px}.mc-photo-column{border:1px solid var(--line);border-radius:18px;background:var(--paper);padding:15px;display:grid;grid-template-columns:1fr 1fr;gap:8px;align-content:start}.mc-photo-column h2{grid-column:1/-1;margin:2px 0 8px;color:var(--green-deep);font-size:17px}.mc-photo-column .offer-photo img{aspect-ratio:1/1}.mc-total{display:grid;grid-template-columns:auto minmax(0,1fr) 230px;gap:18px;align-items:center;border:1px solid var(--line);border-radius:18px;padding:17px 20px;margin-top:14px;background:#071820}.mc-total h2{margin:0;font-family:Georgia,serif;color:var(--green-deep)}.mc-total .metrics{min-width:0}.mc-price{text-align:right}.mc-price span{display:inline-block;margin-left:7px;padding:4px 8px;border-radius:999px;background:#284b32;color:#d9f3dc}.theme-midnight-compact .table-head h3,.theme-midnight-compact .row-main b,.theme-midnight-compact .metric b{color:#fff}.theme-midnight-compact .info-card{background:#081b24}.theme-midnight-compact .footer-action.order{background:#0d6e3e}
html.theme-black-gold,body.theme-black-gold{--cream:#0d0d0c;--paper:#151513;--paper2:#10100e;--ink:#f2eee6;--green:#c19035;--green-deep:#d5ab5d;--gold:#c99b47;--gold2:#55451f;--line:#3b362c;--muted:#aaa49b;background:#0d0d0c;color:var(--ink)}.bg-hero{height:540px;position:relative;border-radius:24px;overflow:hidden;border:1px solid var(--line)}.bg-hero>img{width:100%;height:100%;object-fit:cover}.bg-hero:after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(9,9,8,.96) 0%,rgba(10,10,9,.72) 42%,rgba(0,0,0,.08) 75%)}.bg-overlay{position:absolute;z-index:2;left:42px;top:60px;width:47%}.bg-overlay>span{color:var(--gold);text-transform:uppercase;letter-spacing:.12em;font-size:11px}.bg-overlay h1{font-family:Georgia,serif;font-size:58px;font-weight:400;line-height:1.02;margin:18px 0;color:#fff;text-transform:uppercase}.bg-overlay p{color:#c8c3ba;line-height:1.55}.section-heading.editorial{color:#e8dfcf;text-transform:uppercase}.product-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:13px}.product-card{border:1px solid var(--line);border-radius:16px;overflow:hidden;background:#151513;padding-bottom:16px}.product-photo{height:230px;position:relative}.product-photo img{width:100%;height:100%;object-fit:cover}.product-photo span,.product-photo b{position:absolute;top:10px;background:#090908;color:#fff;border-radius:999px;padding:5px 8px;font-size:10px}.product-photo span{left:10px;color:var(--gold)}.product-photo b{right:10px}.product-card h3{font-family:Georgia,serif;font-weight:500;font-size:20px;margin:14px 15px 6px;color:#fff}.product-card p,.product-card small{display:block;margin:0 15px;color:var(--muted);font-size:10px;line-height:1.45;overflow-wrap:anywhere;white-space:normal}.product-card small{margin-top:7px}.bg-bottom{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:15px}.bg-bottom>article,.addons-strip{border:1px solid var(--line);border-radius:18px;padding:20px;background:#151513}.bg-bottom h2,.addons-strip h2{margin:0 0 14px;color:var(--gold);font-family:Georgia,serif;font-weight:500}.bg-price{font-family:Georgia,serif;font-size:34px;color:#fff;margin-top:16px}.bg-price small{display:block;color:var(--gold);font:11px Arial;margin-top:4px}.delivery-key .control-list{grid-template-columns:1fr 1fr}.addons-strip{margin-top:14px}.addons-strip .extra-grid{grid-template-columns:repeat(4,1fr)}.theme-black-gold .metric b,.theme-black-gold .row-main b{color:#fff}.theme-black-gold .footer-action.order{background:#c99b47;color:#181208}
html.theme-emerald-gold,body.theme-emerald-gold{--cream:#061318;--paper:#0b2023;--paper2:#08171a;--ink:#f2efe7;--green:#1d795f;--green-deep:#dfbc72;--gold:#d7ac55;--gold2:#67572f;--line:#314a4c;--muted:#abb3b0;background:radial-gradient(circle at 100% 0,#0e3934 0,transparent 30%),#061318;color:var(--ink)}.eg-hero{display:grid;grid-template-columns:58% 42%;gap:0;border:1px solid var(--line);border-radius:24px;overflow:hidden;background:#081b1e;min-height:560px}.eg-copy{padding:45px 38px}.eg-copy h1{font-family:Georgia,serif;font-size:55px;font-weight:400;line-height:1.03;margin:18px 0 16px;color:#fff}.eg-copy p{font-size:16px;line-height:1.5;color:var(--muted)}.eg-copy p b{color:var(--gold)}.eg-facts{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:22px}.eg-facts span{padding:11px;border:1px solid var(--line);border-radius:13px;text-align:center;font-size:11px}.eg-gold-summary{margin-top:24px;padding:19px;border-radius:17px;background:linear-gradient(145deg,#b88731,#e6bf67);color:#1c1a13}.eg-gold-summary .metrics{margin-bottom:15px}.eg-gold-summary .metric b,.eg-gold-summary .metric small{color:#1c1a13}.eg-gold-summary>div:last-child{display:flex;justify-content:space-between;align-items:end;border-top:1px solid rgba(0,0,0,.18);padding-top:12px}.eg-gold-summary>div:last-child b{color:#173e32;margin:0}.eg-photo-stack{position:relative;overflow:hidden}.eg-photo-stack .main{width:100%;height:100%;object-fit:cover}.eg-photo-stack .secondary{position:absolute;right:18px;bottom:22px;width:62%;height:38%;object-fit:cover;border:2px solid var(--gold);border-radius:16px;transform:rotate(-5deg);box-shadow:0 16px 30px rgba(0,0,0,.35)}.eg-main{display:grid;grid-template-columns:1.15fr .85fr;gap:14px;margin-top:15px}.eg-menu,.eg-delivery,.eg-addons{border:1px solid var(--line);border-radius:20px;background:#0a1e21;padding:18px}.eg-menu h2,.eg-delivery h2,.eg-addons h2{margin:0 0 13px;color:var(--gold);font-family:Georgia,serif;font-weight:500}.eg-menu .offer-row{background:#071719;margin-bottom:8px}.eg-side{display:grid;gap:14px;align-content:start}.eg-addons .extra-grid{grid-template-columns:1fr}.theme-emerald-gold .row-main b,.theme-emerald-gold .metric b{color:#fff}.theme-emerald-gold .footer-action.order{background:#13734d}
html.theme-neon-emerald,body.theme-neon-emerald{--cream:#020d11;--paper:#06191d;--paper2:#041417;--ink:#effff5;--green:#45f27d;--green-deep:#69ff94;--gold:#d2aa45;--gold2:#5d4d22;--line:#23643f;--muted:#9bb4a9;background:radial-gradient(circle at 45% 0,#063229 0,transparent 31%),#020d11;color:var(--ink)}.neon-hero{display:grid;grid-template-columns:1fr 44%;min-height:300px;border:1px solid #2f9b59;border-radius:20px;overflow:hidden;position:relative;background:#05191b}.neon-hero>div{padding:36px 30px}.neon-hero>div>span{color:var(--green);font-weight:800;font-size:11px}.neon-hero h1{font-size:38px;line-height:1.1;margin:14px 0 12px}.neon-hero p{color:var(--muted);font-size:15px}.neon-hero>img{width:100%;height:100%;object-fit:cover}.neon-hero>b{position:absolute;right:24px;bottom:22px;width:88px;height:88px;border-radius:50%;display:grid;place-items:center;background:#05221d;border:1px solid var(--green);font-size:27px;box-shadow:0 0 22px rgba(69,242,125,.13)}.neon-hero>b small{display:block;font:9px Arial;text-transform:uppercase;color:var(--green)}.neon-quality{display:grid;grid-template-columns:2fr repeat(3,1fr);gap:10px;align-items:center;border:1px solid var(--line);border-radius:18px;padding:15px 18px;margin-top:14px;background:#05171a}.neon-quality p{font-size:11px;line-height:1.4;color:var(--muted)}.neon-quality span{text-align:center;color:var(--green);font-size:10px}.neon-main{display:grid;grid-template-columns:1.25fr .75fr;gap:14px;margin-top:14px}.neon-gallery{border:1px solid var(--line);border-radius:18px;background:#05171a;padding:13px}.neon-gallery h2{text-transform:uppercase;color:var(--green);text-align:center;font-size:15px}.neon-gallery>div{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.neon-gallery .offer-photo img{border-color:#355d45}.neon-tables .table-group{background:#05171a}.neon-tables .table-head h3{color:var(--green);text-transform:uppercase}.neon-total{display:grid;grid-template-columns:1.35fr .65fr;gap:14px;border:1px solid var(--line);border-radius:18px;padding:18px;margin-top:14px;background:#052019}.neon-total h2{margin:0 0 12px;color:var(--green);font-size:17px}.neon-total>div:last-child{text-align:right}.neon-total>div>b{display:block;color:var(--green);font-size:30px}.neon-total>div>span{display:inline-block;padding:5px 9px;border-radius:999px;background:#0b4a29;color:#8affaa}.theme-neon-emerald .metric b,.theme-neon-emerald .row-main b{color:#fff}.theme-neon-emerald .info-card{background:#05171a}.theme-neon-emerald .footer-action.order{background:linear-gradient(135deg,#14a94e,#087035);box-shadow:0 0 22px rgba(69,242,125,.17)}
.info-grid.single,.bg-bottom.single,.eg-main.single{grid-template-columns:1fr}.eg-facts{grid-template-columns:repeat(var(--fact-count,3),1fr)}
.offer-note{font-size:9px;color:var(--muted);margin:12px 0 0;text-align:center;line-height:1.45}@media(max-width:820px){.page{padding:14px 13px 34px}.brand-logo{width:165px}.brand-meta{display:none}.hero,.mg-hero,.eg-hero,.neon-hero{grid-template-columns:1fr}.hero-copy,.mg-hero>div,.eg-copy{padding:28px 24px}.hero h1,.mg-hero h1,.eg-copy h1{font-size:37px}.hero-photo,.hero-photo img,.mg-hero>img{min-height:260px}.menu-grid,.info-grid,.mg-split,.mc-menu,.bg-bottom,.eg-main,.neon-main,.neon-total{grid-template-columns:1fr}.gallery{grid-template-columns:repeat(3,1fr)}.product-grid{grid-template-columns:1fr 1fr}.bg-hero{height:470px}.bg-overlay{width:78%;left:25px;top:38px}.bg-overlay h1{font-size:43px}.mc-intro{grid-template-columns:1fr}.mc-note{border-left:0;border-top:1px solid var(--line);padding:15px 0 0}.mc-total{grid-template-columns:1fr}.mc-price{text-align:left}.eg-photo-stack{min-height:330px}.neon-quality{grid-template-columns:1fr 1fr}.summary{grid-template-columns:1fr}.summary-price{border-left:0;border-top:1px solid var(--line)}}
@media(max-width:520px){.page{padding:11px 9px 28px}.hero h1,.mg-hero h1,.eg-copy h1{font-size:32px}.quality-icons{grid-template-columns:1fr}.product-grid{grid-template-columns:1fr}.gallery{grid-template-columns:repeat(2,1fr)}.table-head,.table-line{grid-template-columns:minmax(0,1fr) 108px 52px}.table-head{font-size:8px}.mc-photo-column{grid-template-columns:1fr 1fr}.neon-gallery>div{grid-template-columns:1fr 1fr}.metrics{grid-template-columns:1fr}.metric{padding:8px;border-bottom:1px solid var(--line)}.extra-grid,.addons-strip .extra-grid{grid-template-columns:1fr}.footer-actions{grid-template-columns:1fr}.final-gallery-grid{grid-template-columns:1fr}.bg-overlay h1{font-size:34px}}
@media print{@page{size:A4;margin:8mm}html,body{background:var(--cream)!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}.page{width:100%!important;max-width:none!important;padding:0!important}.brandbar,.hero,.mg-hero,.mc-intro,.bg-hero,.eg-hero,.neon-hero,.offer-row,.table-group,.product-card,.summary,.info-card,.bg-bottom>article,.addons-strip,.eg-menu,.eg-delivery,.eg-addons,.neon-quality,.neon-gallery,.neon-total{break-inside:avoid;page-break-inside:avoid}.footer-actions{display:none!important}.row-main small,.table-line small,.control-list li{overflow-wrap:anywhere}.final-photo-card{break-before:page;page-break-before:always}.final-gallery-card{break-inside:avoid;page-break-inside:avoid}.final-gallery-grid figure{break-inside:avoid;page-break-inside:avoid}}
</style></head><body><main class="page">${body}${footerNote}${finalPhoto}<footer class="footer"><span>Солнце Кейтеринг · Санкт-Петербург</span><span>+7 (921) 954-04-40 · solnce-keytering.ru</span></footer></main></body></html>`;
}
let offerRenderSeq=0;
let currentOffer={orderId:null,snapshot:null,prepared:null,pdfPrepared:null,pages:null,pdfBlob:null,pdfPromise:null,renderToken:0,preferDraft:false,transient:false,editTimer:null};
function renderOfferPages(pages){const host=$('sunClientOfferPreview');if(!host)return;host.innerHTML='';for(const page of (pages||[])){page.classList.add('sun-offer-page-canvas');page.setAttribute('aria-label','Страница предложения');host.appendChild(page)}}
function offerRenderMessage(text='Обновляю предложение…'){const host=$('sunClientOfferPreview');if(host)host.innerHTML=`<div class="sun-offer-rendering">${esc(text)}</div>`}
async function renderOffer(snapshot){
const renderToken=++offerRenderSeq;currentOffer.snapshot=snapshot;currentOffer.pdfBlob=null;currentOffer.pdfPromise=null;currentOffer.pages=null;offerRenderMessage();const pdfBtn=$('sunOfferPdf');if(pdfBtn){pdfBtn.disabled=true;pdfBtn.textContent='Готовлю просмотр…'}
const prepared=await preparedSnapshot(snapshot);const explicitTemplate=String(snapshot?.offerTemplateId||prepared?.offerTemplateId||'').trim();prepared.offerTemplateId=OFFER_TEMPLATE_IDS.has(explicitTemplate)?explicitTemplate:offerTemplateId();
const pdfPrepared=await preparedPdfSnapshot(prepared),pages=await renderOfferPdfPages(pdfPrepared);
if(renderToken!==offerRenderSeq)return null;
currentOffer.snapshot=snapshot;currentOffer.prepared=prepared;currentOffer.pdfPrepared=pdfPrepared;currentOffer.pages=pages;currentOffer.renderToken=renderToken;
renderOfferPages(pages);syncOfferEditor();
if(pdfBtn){pdfBtn.disabled=false;pdfBtn.textContent='Скачать PDF'}
return pages;
}
function toggleOfferEditor(){const panel=$('sunOfferEditor'),btn=$('sunOfferEdit');if(!panel)return;const on=!panel.classList.contains('on');panel.classList.toggle('on',on);if(btn)btn.textContent=on?'Скрыть редактор':'Редактировать';if(on)syncOfferEditor()}
function offerOverrideTextValue(key){const over=currentOffer.snapshot?.offerOverrides?.texts||{},cfg=offerSettings();return Object.prototype.hasOwnProperty.call(over,key)?String(over[key]??''):String(cfg.texts?.[key]??'')}
function offerOverrideDataValue(key){const over=currentOffer.snapshot?.offerOverrides?.data||{},snap=currentOffer.snapshot||{};if(Object.prototype.hasOwnProperty.call(over,key))return over[key]??'';return key==='guests'?(snap.guests??''):(snap[key]??'')}
function syncOfferEditor(){const panel=$('sunOfferEditor');if(!panel||!currentOffer.snapshot)return;panel.querySelectorAll('[data-offer-text]').forEach(el=>{if(document.activeElement!==el)el.value=offerOverrideTextValue(el.dataset.offerText)});panel.querySelectorAll('[data-offer-data]').forEach(el=>{if(document.activeElement!==el)el.value=offerOverrideDataValue(el.dataset.offerData)});panel.querySelectorAll('[data-offer-list="controlLines"]').forEach(el=>{if(document.activeElement!==el){const over=currentOffer.snapshot?.offerOverrides?.controlLines;const lines=Array.isArray(over)?over:offerSettings().controlLines;el.value=(lines||[]).join('\n')}})}
function saveOfferOverrides(){if(!currentOffer.snapshot)return;currentOffer.snapshot.createdAt=new Date().toISOString();if(!currentOffer.transient&&currentOffer.orderId)persistSnapshot(currentOffer.orderId,currentOffer.snapshot);const meta=$('sunOfferMeta');if(meta)meta.textContent=`Изменено ${new Date(currentOffer.snapshot.createdAt).toLocaleString('ru-RU')}`}
function scheduleOfferEditRender(){clearTimeout(currentOffer.editTimer);currentOffer.editTimer=setTimeout(()=>{if(currentOffer.snapshot)renderOffer(currentOffer.snapshot).catch(err=>{console.error(err);toast('Не удалось обновить предпросмотр.','error')})},500)}
function bindOfferEditor(){const panel=$('sunOfferEditor');if(!panel||panel.dataset.bound)return;panel.dataset.bound='1';panel.addEventListener('input',e=>{const el=e.target.closest('[data-offer-text],[data-offer-data],[data-offer-list]');if(!el||!currentOffer.snapshot)return;currentOffer.snapshot.offerOverrides=currentOffer.snapshot.offerOverrides||{};if(el.dataset.offerText){currentOffer.snapshot.offerOverrides.texts=currentOffer.snapshot.offerOverrides.texts||{};currentOffer.snapshot.offerOverrides.texts[el.dataset.offerText]=el.value}else if(el.dataset.offerList==='controlLines'){currentOffer.snapshot.offerOverrides.controlLines=String(el.value||'').split(/\r?\n/).map(x=>x.trim()).filter(Boolean).slice(0,20)}else{currentOffer.snapshot.offerOverrides.data=currentOffer.snapshot.offerOverrides.data||{};currentOffer.snapshot.offerOverrides.data[el.dataset.offerData]=el.dataset.offerData==='guests'?Math.max(0,Number(el.value||0)):el.value}currentOffer.pdfBlob=null;currentOffer.pages=null;scheduleOfferEditRender()});panel.addEventListener('change',saveOfferOverrides);panel.addEventListener('focusout',e=>{if(e.target.matches?.('[data-offer-text],[data-offer-data],[data-offer-list]'))saveOfferOverrides()})}
function setMeta(order,snapshot,{transient=false,autoRefreshed=false}={}){
const stale=snapshotIsStale(order,snapshot),notice=$('sunOfferStale');
if(notice){
notice.classList.toggle('on',Boolean(stale||transient));
notice.textContent=transient
?'В предложении учтены текущие изменения заказа. Сохраните заказ, чтобы закрепить их.'
:(stale?'Заказ изменён. Предложение обновляется по актуальным данным…':'');
}
if($('sunOfferMeta'))$('sunOfferMeta').textContent=`Версия от ${new Date(snapshot.createdAt).toLocaleString('ru-RU')}${autoRefreshed?' · обновлено автоматически':''}`;
}
async function openOfferForOrder(orderId,{forceRefresh=false,preferDraft=false}={}){
ensureModal();
const saved=currentSavedOrder(orderId);if(!saved)return toast('Заказ не найден.','error');
const order=currentOfferSourceOrder(orderId,preferDraft)||saved;
const draftDiff=order!==saved&&orderSignature(order)!==orderSignature(saved);
let snapshot=saved.clientOfferSnapshot,autoRefreshed=false;
const stale=snapshotIsStale(order,snapshot);
if(stale||forceRefresh){
if(forceRefresh)toast('Обновляю предложение…');
snapshot=await createSnapshot(order);autoRefreshed=stale&&!forceRefresh;
// Never freeze an unsaved draft into the saved order. Saved orders, however,
// always receive the fresh snapshot so every template/PDF opens consistently.
if(!draftDiff&&!persistSnapshot(saved.id,snapshot))return;
}
currentOffer={orderId:saved.id,snapshot,prepared:null,pdfPrepared:null,pages:null,pdfBlob:null,pdfPromise:null,renderToken:0,preferDraft:Boolean(preferDraft),transient:draftDiff,editTimer:null};
setMeta(order,snapshot,{transient:draftDiff,autoRefreshed});
window.modal?.('sunClientOfferModal');await renderOffer(snapshot);
if(autoRefreshed)toast('Предложение автоматически обновлено по текущему заказу.');
}
async function openCurrentDraftOffer(){
const d=getDraft();if(!d?.id)return alert('Сначала сохраните заказ. После сохранения появится предложение клиенту.');
const saved=currentSavedOrder(d.id);if(!saved)return alert('Сначала сохраните заказ.');
return openOfferForOrder(saved.id,{preferDraft:true});
}
async function refreshCurrentOffer(){
if(!currentOffer.orderId)return;
await openOfferForOrder(currentOffer.orderId,{forceRefresh:true,preferDraft:currentOffer.preferDraft});
toast('Предложение обновлено.');
}
async function ensureCurrentOfferFresh(){
if(!currentOffer.orderId)return false;
const saved=currentSavedOrder(currentOffer.orderId);if(!saved)return false;
const order=currentOfferSourceOrder(currentOffer.orderId,currentOffer.preferDraft)||saved;
if(!snapshotIsStale(order,currentOffer.snapshot))return true;
await openOfferForOrder(currentOffer.orderId,{forceRefresh:true,preferDraft:currentOffer.preferDraft});
return true;
}
function filename(ext){const d=currentOffer.prepared?.date||currentOffer.snapshot?.date||'';return `predlozhenie-klientu${d?'-'+d:''}.${ext}`}
function pdfRoundRect(ctx,x,y,w,h,r,fill,stroke){
const rr=Math.min(r,w/2,h/2);ctx.beginPath();ctx.moveTo(x+rr,y);ctx.arcTo(x+w,y,x+w,y+h,rr);ctx.arcTo(x+w,y+h,x,y+h,rr);ctx.arcTo(x,y+h,x,y,rr);ctx.arcTo(x,y,x+w,y,rr);ctx.closePath();if(fill){ctx.fillStyle=fill;ctx.fill()}if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=2;ctx.stroke()}
}
function pdfWrap(ctx,text,maxWidth){
const input=String(text||'').replace(/\s+/g,' ').trim();if(!input)return[];
const words=input.split(' '),lines=[];let line='';
const pushLong=word=>{let chunk='';for(const ch of word){const next=chunk+ch;if(ctx.measureText(next).width>maxWidth&&chunk){lines.push(chunk);chunk=ch}else chunk=next}return chunk};
for(const word of words){
if(ctx.measureText(word).width>maxWidth){if(line){lines.push(line);line=''}line=pushLong(word);continue}
const test=line?`${line} ${word}`:word;
if(ctx.measureText(test).width<=maxWidth)line=test;else{if(line)lines.push(line);line=word}
}
if(line)lines.push(line);return lines;
}
function pdfText(ctx,text,x,y,maxWidth,lineHeight,opts={}){
ctx.save();ctx.font=opts.font||'28px Arial';ctx.fillStyle=opts.color||'#1a1a1a';ctx.textAlign=opts.align||'left';ctx.textBaseline='top';
let lines=pdfWrap(ctx,text,maxWidth);const maxLines=Math.max(0,Number(opts.maxLines||0));
if(maxLines&&lines.length>maxLines){lines=lines.slice(0,maxLines);let last=lines[lines.length-1]||'';while(last&&ctx.measureText(last+'...').width>maxWidth)last=last.slice(0,-1);lines[lines.length-1]=last+'...'}
const lh=Math.max(lineHeight||0,Math.ceil(parseFloat(String(ctx.font).match(/([0-9.]+)px/)?.[1]||16)*1.18));
lines.forEach((line,i)=>ctx.fillText(line,x,y+i*lh));ctx.restore();return lines.length*lh;
}
function pdfDrawCheck(ctx,x,y,r,fill,stroke){ctx.save();ctx.fillStyle=fill;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill();ctx.strokeStyle=stroke;ctx.lineWidth=Math.max(2,r*.22);ctx.lineCap='round';ctx.lineJoin='round';ctx.beginPath();ctx.moveTo(x-r*.45,y);ctx.lineTo(x-r*.1,y+r*.34);ctx.lineTo(x+r*.52,y-r*.38);ctx.stroke();ctx.restore()}
function pdfLoadImage(src){return new Promise(resolve=>{const safe=isSafeRasterDataUrl(src)?String(src):'';if(!safe)return resolve(null);const img=new Image();let done=false;const finish=v=>{if(done)return;done=true;clearTimeout(timer);resolve(v)};const timer=setTimeout(()=>finish(null),4500);img.onload=()=>finish(img);img.onerror=()=>finish(null);img.src=safe})}
function pdfDrawCover(ctx,img,x,y,w,h,r=24){
if(!img)return;const scale=Math.max(w/img.naturalWidth,h/img.naturalHeight),sw=w/scale,sh=h/scale,sx=(img.naturalWidth-sw)/2,sy=(img.naturalHeight-sh)/2;
ctx.save();pdfRoundRect(ctx,x,y,w,h,r);ctx.clip();ctx.drawImage(img,sx,sy,sw,sh,x,y,w,h);ctx.restore();
}
function pdfDrawContain(ctx,img,x,y,w,h){
if(!img)return;const scale=Math.min(w/img.naturalWidth,h/img.naturalHeight),dw=img.naturalWidth*scale,dh=img.naturalHeight*scale;ctx.drawImage(img,x+(w-dw)/2,y+(h-dh)/2,dw,dh);
}
const pdfFromJpegs=images=>window.SunPdfEngine.fromJpegs(images);
function pdfCanvasJpeg(canvas){
return new Promise((resolve,reject)=>{
let done=false;const finish=(err,value)=>{if(done)return;done=true;clearTimeout(timer);err?reject(err):resolve(value)};
const fromDataUrl=()=>{try{const data=canvas.toDataURL('image/jpeg',0.94),base64=String(data).split(',')[1]||'';if(!base64)throw new Error('Пустое изображение страницы');const raw=atob(base64),bytes=new Uint8Array(raw.length);for(let i=0;i<raw.length;i++)bytes[i]=raw.charCodeAt(i);finish(null,{width:canvas.width,height:canvas.height,bytes})}catch(err){finish(err)}};
const timer=setTimeout(fromDataUrl,1800);
try{
if(typeof canvas.toBlob!=='function')return fromDataUrl();
canvas.toBlob(async blob=>{if(done)return;if(!blob)return fromDataUrl();try{finish(null,{width:canvas.width,height:canvas.height,bytes:new Uint8Array(await blob.arrayBuffer())})}catch(err){fromDataUrl()}},'image/jpeg',0.94);
}catch(_){fromDataUrl()}
})
}
function pdfThemeFor(id){
const themes={
light:{BG:'#ffffff',PANEL:'#ffffff',LINE:'#e7d8bd',TEXT:'#1d211d',MUTED:'#6f746e',GOLD:'#c99b47',GOLD2:'#e2c28b',GREEN:'#476f4d',GREEN_DEEP:'#28533a',GREEN_SOFT:'#f1f5ef',FOOT:'#ffffff'},
'editorial-grid':{BG:'#fbfaf6',PANEL:'#fffefb',LINE:'#e8dfcf',TEXT:'#163127',MUTED:'#776f62',GOLD:'#b99355',GOLD2:'#d8c4a2',GREEN:'#245f46',GREEN_DEEP:'#1f5a42',GREEN_SOFT:'#e9f1eb',FOOT:'#fbfaf6'},
'midnight-glass':{BG:'#061521',PANEL:'#0b2230',LINE:'#315263',TEXT:'#f4f1e8',MUTED:'#9aaeb8',GOLD:'#d4a64b',GOLD2:'#765f35',GREEN:'#24a98f',GREEN_DEEP:'#d7ad58',GREEN_SOFT:'#123d38',FOOT:'#04111b'},
'midnight-compact':{BG:'#03131c',PANEL:'#0a1b25',LINE:'#3a4d57',TEXT:'#f4f3ef',MUTED:'#9daab1',GOLD:'#e0b751',GOLD2:'#5e5231',GREEN:'#62a868',GREEN_DEEP:'#f1c96c',GREEN_SOFT:'#264b37',FOOT:'#06141c'},
'black-gold':{BG:'#0c0c0b',PANEL:'#151513',LINE:'#453d2e',TEXT:'#f2eee6',MUTED:'#aaa49b',GOLD:'#c99a3c',GOLD2:'#57451f',GREEN:'#b98a31',GREEN_DEEP:'#d2a650',GREEN_SOFT:'#342c1d',FOOT:'#090908'},
'emerald-gold':{BG:'#061318',PANEL:'#0b2023',LINE:'#3b4f50',TEXT:'#f2efe7',MUTED:'#a7b0ae',GOLD:'#d7ac55',GOLD2:'#6a5930',GREEN:'#1c765d',GREEN_DEEP:'#d9b466',GREEN_SOFT:'#123b35',FOOT:'#07171a'},
'neon-emerald':{BG:'#020e12',PANEL:'#06191d',LINE:'#277b4d',TEXT:'#effff5',MUTED:'#9cb3aa',GOLD:'#d0a841',GOLD2:'#665322',GREEN:'#39f07c',GREEN_DEEP:'#67ff91',GREEN_SOFT:'#0c3f27',FOOT:'#020b0e'}
};
return themes[id]||themes.light;
}
async function renderOfferPdfPagesBase(s){
const id=s.offerTemplateId||offerTemplateId(),W=1000,H=1414,SCALE=1.6,M=52,T=pdfThemeFor(id),{BG,PANEL,LINE,TEXT,MUTED,GOLD,GOLD2,GREEN,GREEN_DEEP,GREEN_SOFT,FOOT}=T;
const cfg=offerSettingsForSnapshot(s),items=s.items||[],imageMap=new Map();
for(const item of items)imageMap.set(item.id,await pdfLoadImage(item.photoData||s.logo||''));
const logo=await pdfLoadImage(s.logo||'');
const finalGallery=cfg.showFinalPhoto?(await Promise.all((Array.isArray(s.finalGallery)?s.finalGallery:[]).map(pdfLoadImage))).filter(Boolean):[];
const heroImg=items.map(i=>imageMap.get(i.id)).find(Boolean)||finalGallery[0]||logo,p=s.pricing||{},g=num(s.guests),menuPer=perGuest(p.itemsTotal,g),boxCount=offerBoxCount(items),discountPct=p.base>0?Math.round((p.discount/p.base)*100):0;
const primaryMetric=offerPrimaryMetric(s,cfg,g),visibleDate=cfg.showDate?dateText(s.date):'',visibleClient=cfg.showClientName?String(s.client||'').trim():'',visibleGuests=cfg.showGuests&&g?`${g} ${plural(g,'гость','гостя','гостей')}`:'';
const identity=[visibleClient,visibleDate,visibleGuests].filter(Boolean).join(' · '),control=Array.isArray(cfg.controlLines)&&cfg.controlLines.length?cfg.controlLines:DELIVERY_CONTROL,extras=Array.isArray(cfg.extraServices)&&cfg.extraServices.length?cfg.extraServices:EXTRA_SERVICES.map(x=>x[0]);
const canvases=[];let canvas,ctx,y;const bottom=H-78;
function newPage(){canvas=document.createElement('canvas');canvas.width=Math.round(W*SCALE);canvas.height=Math.round(H*SCALE);ctx=canvas.getContext('2d',{alpha:false});ctx.setTransform(SCALE,0,0,SCALE,0,0);ctx.fillStyle=BG;ctx.fillRect(0,0,W,H);y=M;canvases.push(canvas);return ctx}
function space(h){if(y+h>bottom)newPage()}
function fitBlockHeight(natural,minHeight=natural,gap=0,softRatio=.84){
const h=Math.max(1,Number(natural||0)),minH=Math.min(h,Math.max(1,Number(minHeight||h))),available=Math.max(0,bottom-y-gap);
if(h<=available)return h;
if(available>=minH&&available>=h*softRatio)return available;
newPage();return h;
}
function card(x,yy,w,h,r=20,fill=PANEL,stroke=LINE){pdfRoundRect(ctx,x,yy,w,h,r,fill,stroke)}
function line(x1,yy1,x2,yy2,color=LINE,width=1){const snap=v=>width===1?Math.round(v)+.5:v;ctx.save();ctx.strokeStyle=color;ctx.lineWidth=width;ctx.beginPath();ctx.moveTo(snap(x1),snap(yy1));ctx.lineTo(snap(x2),snap(yy2));ctx.stroke();ctx.restore()}
function wrapCount(text,width,font){ctx.save();ctx.font=font;const n=Math.max(1,pdfWrap(ctx,String(text||''),width).length);ctx.restore();return n}
function wrappedLines(text,width,font){ctx.save();ctx.font=font;const out=pdfWrap(ctx,String(text||''),width);ctx.restore();return out}
function drawWrappedLines(lines,x,yy,lineHeight,{font='12px Arial',color=TEXT,align='left'}={}){ctx.save();ctx.font=font;ctx.fillStyle=color;ctx.textAlign=align;ctx.textBaseline='top';lines.forEach((line,i)=>ctx.fillText(line,x,yy+i*lineHeight));ctx.restore()}
function compositionTextPdf(item,sep=' · '){return offerComposition(item).join(sep)}
function brandHeader(){space(82);if(logo)pdfDrawContain(ctx,logo,M,y,195,62);if(visibleDate)pdfText(ctx,visibleDate,W-M,y+24,300,24,{font:'14px Arial',color:MUTED,align:'right',maxLines:1});y+=78}
function hero(){
const event=s.event||'Персональное предложение';
if(id==='black-gold'){
space(425);const h=405;card(M,y,W-2*M,h,24,'#0c0c0b',LINE);if(heroImg)pdfDrawCover(ctx,heroImg,M,y,W-2*M,h,24);const grad=ctx.createLinearGradient(M,y,M+650,y);grad.addColorStop(0,'rgba(0,0,0,.94)');grad.addColorStop(.68,'rgba(0,0,0,.48)');grad.addColorStop(1,'rgba(0,0,0,0)');ctx.save();pdfRoundRect(ctx,M,y,W-2*M,h,24);ctx.clip();ctx.fillStyle=grad;ctx.fillRect(M,y,W-2*M,h);ctx.restore();pdfText(ctx,cfg.texts.proposalLabel,M+34,y+36,430,22,{font:'700 12px Arial',color:GOLD,maxLines:1});pdfText(ctx,'Кейтеринг для вашего события',M+34,y+78,500,56,{font:'500 43px Georgia',color:'#ffffff',maxLines:3});pdfText(ctx,[event,identity].filter(Boolean).join(' · '),M+34,y+273,510,28,{font:'16px Arial',color:'#ded8cd',maxLines:3});y+=h+18;return;
}
if(id==='editorial-grid'){
const leftW=386,rightX=M+leftW,rightW=W-M-rightX,leftTextW=leftW-56,eventLines=wrappedLines(event,leftTextW,'500 34px Georgia'),noteLines=wrappedLines(cfg.texts.heroNote,leftTextW,'12px Arial');
const h=Math.max(350,94+eventLines.length*43+(visibleClient?31:0)+([visibleDate,visibleGuests].filter(Boolean).length?42:0)+Math.max(52,noteLines.length*19+18));
space(h+18);card(M,y,W-2*M,h,18,PANEL,LINE);if(heroImg)pdfDrawCover(ctx,heroImg,rightX+1,y+1,rightW-1,h-2,17);
pdfText(ctx,cfg.texts.proposalLabel,M+26,y+26,leftTextW,20,{font:'700 11px Arial',color:GOLD,maxLines:2});
drawWrappedLines(eventLines,M+26,y+62,43,{font:'500 34px Georgia',color:TEXT});let ty=y+62+eventLines.length*43+12;
if(visibleClient){pdfText(ctx,visibleClient,M+26,ty,leftTextW,25,{font:'500 17px Georgia',color:GREEN_DEEP,maxLines:1});ty+=31}
const facts=[visibleDate,visibleGuests].filter(Boolean).join(' · ');if(facts){pdfText(ctx,facts,M+26,ty,leftTextW,22,{font:'12px Arial',color:TEXT,maxLines:2});ty+=38}
line(M+26,ty,M+26+leftTextW,ty,GOLD2,1);ty+=15;if(noteLines.length)drawWrappedLines(noteLines,M+26,ty,19,{font:'12px Arial',color:MUTED});
y+=h+18;return;
}
if(id==='midnight-compact'){
const innerX=M+28,innerW=W-2*M-56,leftW=510,gap=28,rightX=innerX+leftW+gap,rightW=innerW-leftW-gap;
const eventLines=wrapCount(event,leftW,'700 31px Arial'),noteLines=wrapCount(cfg.texts.heroNote,rightW,'15px Arial'),h=Math.max(228,138+Math.max(eventLines*42,noteLines*21));space(h+20);card(M,y,W-2*M,h,20,PANEL,LINE);
pdfText(ctx,cfg.texts.proposalLabel,innerX,y+24,leftW,22,{font:'700 12px Arial',color:GOLD,maxLines:1});
pdfText(ctx,event,innerX,y+57,leftW,42,{font:'700 31px Arial',color:TEXT,maxLines:3});
line(rightX-gap/2,y+28,rightX-gap/2,y+h-28,LINE,1);
pdfText(ctx,cfg.texts.heroNote,rightX,y+55,rightW,21,{font:'15px Arial',color:GREEN_DEEP,maxLines:7});
if(identity)pdfText(ctx,identity,innerX,y+h-39,leftW,23,{font:'13px Arial',color:MUTED,maxLines:1});
y+=h+18;return;
}
const left=id==='neon-emerald'?500:425,rightX=M+left,rightW=W-M-rightX,leftTextW=left-56;
const eventFont=id==='neon-emerald'?'700 34px Arial':'500 38px Georgia',eventLH=48,noteFont='12px Arial',noteLH=20;
const eventLines=wrappedLines(event,leftTextW,eventFont),noteLines=wrappedLines(cfg.texts.heroNote,leftTextW,noteFont);
const identityRows=(visibleClient?31:0)+([visibleDate,visibleGuests].filter(Boolean).length?52:0);
const textNeed=68+Math.max(1,eventLines.length)*eventLH+24+identityRows+(noteLines.length?Math.max(54,noteLines.length*noteLH+14):0)+22;
const h=Math.max(id==='emerald-gold'?380:335,textNeed);space(h+20);card(M,y,W-2*M,h,24,PANEL,LINE);if(heroImg)pdfDrawCover(ctx,heroImg,rightX+1,y+1,rightW-1,h-2,22);
pdfText(ctx,cfg.texts.proposalLabel,M+28,y+29,leftTextW,22,{font:'700 12px Arial',color:id==='neon-emerald'?GREEN:GOLD,maxLines:2});
drawWrappedLines(eventLines,M+28,y+68,eventLH,{font:eventFont,color:TEXT});
let ty=y+68+Math.max(1,eventLines.length)*eventLH+20;if(visibleClient){pdfText(ctx,visibleClient,M+28,ty,leftTextW,27,{font:'500 18px Georgia',color:GREEN_DEEP,maxLines:1});ty+=31}
const facts=[visibleDate,visibleGuests].filter(Boolean).join(' · ');if(facts){pdfText(ctx,facts,M+28,ty,leftTextW,26,{font:'14px Arial',color:TEXT,maxLines:2});ty+=48}
if(noteLines.length){line(M+28,ty-8,M+28+leftTextW,ty-8,GOLD2,1);drawWrappedLines(noteLines,M+28,ty+6,noteLH,{font:noteFont,color:MUTED})}
y+=h+18;
}
function sellingBlock(){
const title=String(cfg.texts.salesTitle||'').trim(),body=String(cfg.texts.salesNote||'').trim();if(!title&&!body)return;
const x=M,w=W-2*M,padX=30,titleFont='500 22px Georgia',bodyFont='14px Arial',titleLH=30,bodyLH=23,innerW=w-padX*2-12;
let bodyLines=wrappedLines(body,innerW,bodyFont),first=true;
do{
const titleLines=first&&title?wrappedLines(title,innerW,titleFont):[];
const headingH=titleLines.length?titleLines.length*titleLH+12:0;
const minRows=Math.min(3,Math.max(1,bodyLines.length||1));
let minH=30+headingH+(bodyLines.length?minRows*bodyLH:0)+24;
if(bottom-y<minH)newPage();
const available=Math.max(120,bottom-y),maxRows=Math.max(1,Math.floor((available-54-headingH)/bodyLH));
const chunk=bodyLines.splice(0,maxRows),h=Math.min(available,30+headingH+Math.max(0,chunk.length*bodyLH)+24);
card(x,y,w,h,18,PANEL,LINE);ctx.save();ctx.fillStyle=GOLD;pdfRoundRect(ctx,x+18,y+22,6,Math.max(34,h-44),3,GOLD);ctx.restore();
let yy=y+22;if(titleLines.length){drawWrappedLines(titleLines,x+38,yy,titleLH,{font:titleFont,color:GREEN_DEEP});yy+=headingH}
if(chunk.length)drawWrappedLines(chunk,x+38,yy,bodyLH,{font:bodyFont,color:TEXT});
y+=h+18;first=false;if(bodyLines.length)newPage();
}while(bodyLines.length);
}
function sectionTitlePdf(text=cfg.texts.menuTitle){const titleFont=id==='black-gold'?'500 27px Georgia':id==='editorial-grid'?'500 24px Georgia':'700 24px Arial',lines=wrapCount(text,W-2*M,titleFont),h=Math.max(56,24+lines*30);space(h);pdfText(ctx,text,M,y+4,W-2*M,30,{font:titleFont,color:GREEN_DEEP,align:id==='black-gold'?'left':'center',maxLines:2});line(M,y+h-10,W-M,y+h-10,GOLD2,1);y+=h}
function itemCardLight(item,x,yy,w,forcedHeight=0){
const nameLines=Math.max(1,wrapCount(item.name,w-224,'500 18px Georgia'));
const meta=[weightLabel(item),compositionTextPdf(item)].filter(Boolean).join(' · ');
const metaLines=meta?Math.max(1,wrapCount(meta,w-230,'11px Arial')):0;
const textH=nameLines*27+(metaLines?8+metaLines*20:0),minH=36+textH,naturalH=Math.max(150,minH),h=Math.max(minH,Number(forcedHeight||naturalH));
card(x,yy,w,h,17,PANEL,LINE);
const img=imageMap.get(item.id)||logo;if(img)pdfDrawCover(ctx,img,x+10,yy+10,120,h-20,13);
pdfText(ctx,item.name,x+146,yy+18,w-224,27,{font:'500 18px Georgia',color:TEXT,maxLines:nameLines});
if(meta)pdfText(ctx,meta,x+146,yy+18+nameLines*27+8,w-230,20,{font:'11px Arial',color:MUTED,maxLines:metaLines});
card(x+w-70,yy+16,50,30,15,PANEL,LINE);
pdfText(ctx,`x${Math.round(num(item.qty))}`,x+w-45,yy+22,42,19,{font:'14px Arial',color:TEXT,align:'center',maxLines:1});
return h
}
function drawItems(){
sectionTitlePdf(id==='black-gold'?`${cfg.texts.menuTitle} для вашего мероприятия`:cfg.texts.menuTitle);
if(id==='black-gold'){
const gap=12,cw=(W-2*M-gap*2)/3;
for(let i=0;i<items.length;i+=3){
const row=items.slice(i,i+3);
const measured=row.map(item=>{
const nameLines=Math.max(1,wrapCount(item.name,cw-32,'500 17px Georgia'));
const meta=[weightLabel(item),compositionTextPdf(item,', ')].filter(Boolean).join(' · ');
const metaLines=meta?Math.max(1,wrapCount(meta,cw-32,'10px Arial')):0;
const minH=252+nameLines*24+(metaLines?8+metaLines*19:0)+18;
return {item,nameLines,meta,metaLines,minH,h:Math.max(340,minH)};
});
const naturalRh=Math.max(...measured.map(v=>v.h),340),minRh=Math.max(...measured.map(v=>v.minH)),rh=fitBlockHeight(naturalRh,minRh,14,.88);
measured.forEach((v,j)=>{
const {item,nameLines,meta,metaLines}=v,x=M+j*(cw+gap);
card(x,y,cw,rh,16,PANEL,LINE);const img=imageMap.get(item.id)||logo;if(img)pdfDrawCover(ctx,img,x+1,y+1,cw-2,205,15);
pdfText(ctx,String(i+j+1).padStart(2,'0'),x+12,y+12,38,20,{font:'700 11px Arial',color:GOLD,maxLines:1});
pdfText(ctx,`x${Math.round(num(item.qty))}`,x+cw-13,y+12,40,20,{font:'700 11px Arial',color:'#ffffff',align:'right',maxLines:1});
pdfText(ctx,item.name,x+16,y+222,cw-32,24,{font:'500 17px Georgia',color:TEXT,maxLines:nameLines});
if(meta)pdfText(ctx,meta,x+16,y+222+nameLines*24+8,cw-32,19,{font:'10px Arial',color:MUTED,maxLines:metaLines});
});
y+=rh+14
}
return;
}
if(id==='editorial-grid'){
const groups=editorialGroups(items),gap=10,cw=(W-2*M-gap)/2;
for(let i=0;i<groups.length;i+=2){
const pair=groups.slice(i,i+2),rh=78;space(rh+10);
pair.forEach((group,j)=>{
const x=M+j*(cw+gap);card(x,y,cw,rh,13,PANEL,LINE);
ctx.save();ctx.fillStyle=GREEN_DEEP;ctx.beginPath();ctx.arc(x+31,y+39,20,0,Math.PI*2);ctx.fill();ctx.restore();
pdfText(ctx,String(i+j+1).padStart(2,'0'),x+31,y+30,30,18,{font:'700 10px Arial',color:'#ffffff',align:'center',maxLines:1});
pdfText(ctx,group.name,x+62,y+15,cw-150,24,{font:'500 16px Georgia',color:TEXT,maxLines:2});
if(group.meta)pdfText(ctx,group.meta,x+62,y+48,cw-150,18,{font:'10px Arial',color:GOLD,maxLines:1});
card(x+cw-68,y+24,48,28,14,PANEL,LINE);pdfText(ctx,`x${Math.round(group.qty)}`,x+cw-44,y+29,40,18,{font:'11px Arial',color:TEXT,align:'center',maxLines:1});
});
y+=rh+10;
}
if(cfg.showGallery)drawGallery(4,126,false);return;
}
if(id==='midnight-compact'){
const groups=offerGroups(items);
for(const group of groups){
const titleLines=wrapCount(group.name,600,'700 16px Arial'),headerH=Math.max(45,18+titleLines*24);space(headerH+20);
card(M,y,W-2*M,headerH,13,PANEL,LINE);pdfText(ctx,group.name,M+18,y+12,650,24,{font:'700 16px Arial',color:GREEN_DEEP,maxLines:titleLines});y+=headerH;
for(const item of group.list){
const nameLines=Math.max(1,wrapCount(item.name,535,'14px Arial'));
const comp=compositionTextPdf(item);
const compLines=comp?Math.max(1,wrapCount(comp,535,'10px Arial')):0;
const minH=28+nameLines*21+(compLines?5+compLines*17:0)+10,naturalH=Math.max(64,minH),h=fitBlockHeight(naturalH,minH,0,.88);
line(M,y,W-M,y,LINE,1);
pdfText(ctx,item.name,M+18,y+14,535,21,{font:'14px Arial',color:TEXT,maxLines:nameLines});
if(comp)pdfText(ctx,comp,M+18,y+14+nameLines*21+5,535,17,{font:'10px Arial',color:MUTED,maxLines:compLines});
pdfText(ctx,weightLabel(item)||item.weight||'—',M+588,y+14,188,36,{font:'11px Arial',color:MUTED,align:'center',maxLines:2});
pdfText(ctx,`x${Math.round(num(item.qty))}`,W-M-25,y+14,80,21,{font:'700 13px Arial',color:TEXT,align:'right',maxLines:1});
y+=h
}
y+=10
}
if(cfg.showGallery)drawGallery(5,118,true);return;
}
if(id==='neon-emerald'){
const gap=12,cw=(W-2*M-gap)/2;
for(let i=0;i<items.length;i+=2){
const row=items.slice(i,i+2);
const measured=row.map(item=>{
const nameLines=Math.max(1,wrapCount(item.name,cw-202,'700 15px Arial'));
const meta=[weightLabel(item),compositionTextPdf(item)].filter(Boolean).join(' · ');
const metaLines=meta?Math.max(1,wrapCount(meta,cw-202,'10px Arial')):0;
const minH=38+nameLines*22+(metaLines?6+metaLines*18:0)+20;
return {item,nameLines,meta,metaLines,minH,h:Math.max(124,minH)};
});
const naturalRh=Math.max(...measured.map(v=>v.h)),minRh=Math.max(...measured.map(v=>v.minH)),rh=fitBlockHeight(naturalRh,minRh,12,.88);
measured.forEach((v,j)=>{
const {item,nameLines,meta,metaLines}=v,x=M+j*(cw+gap);card(x,y,cw,rh,16,PANEL,LINE);
const img=imageMap.get(item.id)||logo;if(img)pdfDrawCover(ctx,img,x+8,y+8,102,rh-16,12);
pdfText(ctx,item.name,x+122,y+17,cw-202,22,{font:'700 15px Arial',color:TEXT,maxLines:nameLines});
if(meta)pdfText(ctx,meta,x+122,y+17+nameLines*22+6,cw-202,18,{font:'10px Arial',color:GREEN,maxLines:metaLines});
pdfText(ctx,`x${Math.round(num(item.qty))}`,x+cw-18,y+18,55,20,{font:'700 12px Arial',color:GREEN,align:'right',maxLines:1});
});
y+=rh+12
}
return;
}
if(id==='emerald-gold'){
for(const item of items){
const nameLines=Math.max(1,wrapCount(item.name,W-2*M-220,'500 18px Georgia'));
const meta=[weightLabel(item),compositionTextPdf(item)].filter(Boolean).join(' · ');
const metaLines=meta?Math.max(1,wrapCount(meta,W-2*M-240,'10px Arial')):0;
const minH=38+nameLines*27+(metaLines?8+metaLines*19:0)+20,naturalH=Math.max(130,minH),h=fitBlockHeight(naturalH,minH,10,.88);
card(M,y,W-2*M,h,18,PANEL,LINE);const img=imageMap.get(item.id)||logo;if(img)pdfDrawCover(ctx,img,W-M-160,y+8,150,h-16,12);
pdfText(ctx,item.name,M+22,y+18,W-2*M-220,27,{font:'500 18px Georgia',color:TEXT,maxLines:nameLines});
if(meta)pdfText(ctx,meta,M+22,y+18+nameLines*27+8,W-2*M-240,19,{font:'10px Arial',color:MUTED,maxLines:metaLines});
pdfText(ctx,`x${Math.round(num(item.qty))}`,W-M-176,y+18,58,22,{font:'700 12px Arial',color:GOLD,align:'right',maxLines:1});y+=h+10
}
return;
}
const gap=14,cw=(W-2*M-gap)/2;
for(let i=0;i<items.length;i+=2){
const row=items.slice(i,i+2),measured=row.map(item=>{
const meta=[weightLabel(item),compositionTextPdf(item)].filter(Boolean).join(' · ');
const nameLines=Math.max(1,wrapCount(item.name,cw-224,'500 18px Georgia'));
const metaLines=meta?Math.max(1,wrapCount(meta,cw-230,'11px Arial')):0,minH=36+nameLines*27+(metaLines?8+metaLines*20:0);
return {minH,h:Math.max(150,minH)};
}),naturalRh=Math.max(...measured.map(v=>v.h)),minRh=Math.max(...measured.map(v=>v.minH)),rh=fitBlockHeight(naturalRh,minRh,14,.88);
row.forEach((item,j)=>itemCardLight(item,M+j*(cw+gap),y,cw,rh));y+=rh+14
}
// PDF cards already contain the position photo; a second gallery duplicated the same images and created extra sparse pages.
}
function drawGallery(cols=6,h=120,boxesOnly=false){const galleryItems=boxesOnly?items.filter(i=>[0,5].includes(Number(i.categoryId))):items;if(!galleryItems.length)return;const gap=8,shown=Math.min(galleryItems.length,cols*2),rows=Math.ceil(shown/cols),natural=rows*h+(rows-1)*gap,minCell=Math.min(h,96),minTotal=rows*minCell+(rows-1)*gap,total=fitBlockHeight(natural,minTotal,20,.86),cellH=Math.max(minCell,(total-(rows-1)*gap)/rows),w=(W-2*M-gap*(cols-1))/cols;for(let i=0;i<shown;i++){const img=imageMap.get(galleryItems[i].id)||logo;if(img)pdfDrawCover(ctx,img,M+(i%cols)*(w+gap),y+Math.floor(i/cols)*(cellH+gap),w,cellH,10)}y+=total+18}
function summary(){
const metricList=[[primaryMetric.value,primaryMetric.label],...(cfg.showAmountPerGuest?[[g?money(menuPer):'—',cfg.texts.amountPerGuestLabel||'Сумма на гостя']]:[]),...(cfg.showBoxCount?[[String(Math.round(boxCount)),cfg.texts.boxCountLabel||'Количество боксов']]:[])];
const titleLines=wrapCount(cfg.texts.summaryTitle,470,'500 22px Georgia'),naturalH=Math.max(246,170+titleLines*31),minH=Math.max(218,170+titleLines*31),h=fitBlockHeight(naturalH,minH,18,.9);card(M,y,W-2*M,h,20,PANEL,LINE);const leftW=520,rightX=M+leftW;pdfText(ctx,cfg.texts.summaryTitle,M+24,y+20,leftW-48,31,{font:'500 22px Georgia',color:GREEN_DEEP,maxLines:3});const metricsY=y+30+titleLines*31,mw=(leftW-44)/Math.max(1,metricList.length);metricList.forEach((it,i)=>{const x=M+22+i*mw;pdfText(ctx,it[0],x+mw/2,metricsY,mw-20,34,{font:'500 23px Georgia',color:TEXT,align:'center',maxLines:1});pdfText(ctx,it[1],x+mw/2,metricsY+43,mw-20,38,{font:'10px Arial',color:MUTED,align:'center',maxLines:2});if(i<metricList.length-1)line(x+mw,metricsY-2,x+mw,metricsY+88,LINE,1)});line(rightX,y+24,rightX,y+h-24,LINE,1);pdfText(ctx,cfg.texts.totalPriceTitle.toUpperCase(),rightX+32,y+38,W-M-rightX-54,44,{font:'700 11px Arial',color:GOLD,maxLines:2});pdfText(ctx,money(p.total),rightX+32,y+88,W-M-rightX-54,45,{font:'500 34px Georgia',color:GREEN_DEEP,maxLines:1});if(g&&cfg.showAmountPerGuest)pdfText(ctx,`на гостя: ${money(menuPer)}`,rightX+32,y+140,W-M-rightX-54,23,{font:'13px Arial',color:MUTED,maxLines:1});if(cfg.showDiscountBadge&&discountPct){card(rightX+32,y+h-58,142,34,10,GREEN_SOFT,GREEN_SOFT);pdfText(ctx,`-${discountPct}% учтено`,rightX+103,y+h-50,120,19,{font:'12px Arial',color:GREEN,align:'center',maxLines:1})}y+=h+18;
}
function measureListHeight(list,width,font='12px Arial',lineH=20,minRow=31){return list.reduce((sum,t)=>sum+Math.max(minRow,wrapCount(t,width,font)*lineH+9),0)}
function drawControlCard(x,yy,w,h){card(x,yy,w,h,18,PANEL,LINE);const titleLines=wrapCount(cfg.texts.controlTitle,w-40,'500 18px Georgia');pdfText(ctx,cfg.texts.controlTitle,x+w/2,yy+20,w-40,27,{font:'500 18px Georgia',color:GREEN_DEEP,align:'center',maxLines:3});let cy=yy+34+titleLines*27;for(const t of control){const lines=wrapCount(t,w-78,'12px Arial'),rowH=Math.max(31,lines*20+9);pdfDrawCheck(ctx,x+29,cy+9,8,GREEN_SOFT,GREEN_DEEP);pdfText(ctx,t,x+48,cy,w-70,rowH,{font:'12px Arial',color:TEXT,maxLines:5});cy+=rowH}}
function drawExtrasCard(x,yy,w,h){card(x,yy,w,h,18,PANEL,LINE);const titleLines=wrapCount(cfg.texts.extrasTitle,w-40,'500 18px Georgia');pdfText(ctx,cfg.texts.extrasTitle,x+w/2,yy+20,w-40,27,{font:'500 18px Georgia',color:GREEN_DEEP,align:'center',maxLines:3});let cy=yy+36+titleLines*27;for(const name of extras){const lines=wrapCount(name,w-70,'12px Arial'),rowH=Math.max(37,lines*20+9);ctx.save();ctx.fillStyle=GREEN_SOFT;ctx.beginPath();ctx.arc(x+31,cy+8,8,0,Math.PI*2);ctx.fill();ctx.restore();pdfText(ctx,'+',x+31,cy+1,16,16,{font:'700 11px Arial',color:GOLD,align:'center',maxLines:1});pdfText(ctx,name,x+50,cy,w-70,rowH,{font:'12px Arial',color:TEXT,maxLines:4});cy+=rowH}}
function infoCards(){
if(!cfg.showControl&&!cfg.showExtras)return;const gap=14,count=(cfg.showControl?1:0)+(cfg.showExtras?1:0),cw=count===1?W-2*M:(W-2*M-gap)/2;
const heights=[];if(cfg.showControl){const title=wrapCount(cfg.texts.controlTitle,cw-40,'500 18px Georgia');heights.push(50+title*27+measureListHeight(control,cw-78))}if(cfg.showExtras){const title=wrapCount(cfg.texts.extrasTitle,cw-40,'500 18px Georgia');heights.push(58+title*27+measureListHeight(extras,cw-70,'12px Arial',20,37))}const minH=Math.max(1,...heights),naturalH=Math.max(230,minH),h=fitBlockHeight(naturalH,minH,18,.9);let x=M;if(cfg.showControl){drawControlCard(x,y,cw,h);x+=cw+gap}if(cfg.showExtras)drawExtrasCard(x,y,cw,h);y+=h+18;
}
function footerNoteNaturalHeight(){if(!cfg.showFooterNote||!String(cfg.texts.footerNote||'').trim())return 0;const lines=wrapCount(cfg.texts.footerNote,W-2*M-34,'11px Arial');return Math.max(44,22+lines*18)+12}
function pricing(){if(!cfg.showPriceBreakdown)return;const rows=[[cfg.texts.priceItemsLabel||'Стоимость позиций',p.base],...(p.manual?[[cfg.texts.discountLabel||'Скидка',-p.manual]]:[]),...(p.promo?[[`${cfg.texts.promoLabel||'Промокод'}${s.promoCode?' '+s.promoCode:''}`,-p.promo]]:[]),[cfg.texts.menuAfterDiscountLabel||'Меню после скидок',p.itemsTotal],[cfg.texts.deliveryLabel||'Доставка',p.delivery],[cfg.texts.totalLabel||'Итого',p.total]],title=cfg.texts.pricingTitle||'Расчёт стоимости',titleLines=wrapCount(title,420,'500 18px Georgia'),naturalH=48+titleLines*27+rows.length*31,minH=44+titleLines*27+rows.length*26,footerReserve=footerNoteNaturalHeight();
if(y+naturalH+16<=bottom&&footerReserve&&y+naturalH+16+footerReserve>bottom)newPage();
const h=fitBlockHeight(naturalH,minH,16,.88),rowStep=Math.max(26,Math.min(31,(h-44-titleLines*27)/Math.max(1,rows.length)));card(M,y,W-2*M,h,16,PANEL,LINE);pdfText(ctx,title,M+20,y+16,420,27,{font:'500 18px Georgia',color:GREEN_DEEP,maxLines:2});let yy=y+24+titleLines*27;for(const [label,val] of rows){const grand=label===(cfg.texts.totalLabel||'Итого');pdfText(ctx,label,M+20,yy,490,21,{font:grand?'700 13px Arial':'12px Arial',color:grand?GREEN_DEEP:MUTED,maxLines:1});pdfText(ctx,(val<0?'- ':'')+money(Math.abs(val)),W-M-20,yy,250,21,{font:grand?'700 14px Arial':'12px Arial',color:grand?GREEN_DEEP:TEXT,align:'right',maxLines:1});yy+=rowStep}y+=h+16}
function footerNoteBlock(){if(!cfg.showFooterNote||!String(cfg.texts.footerNote||'').trim())return;const lines=wrapCount(cfg.texts.footerNote,W-2*M-34,'11px Arial'),naturalH=Math.max(44,22+lines*18),minH=Math.max(36,18+lines*16),h=fitBlockHeight(naturalH,minH,12,.82);card(M,y,W-2*M,h,12,PANEL,LINE);pdfText(ctx,cfg.texts.footerNote,W/2,y+Math.max(8,(h-lines*18)/2),W-2*M-34,18,{font:'11px Arial',color:MUTED,align:'center',maxLines:6});y+=h+12}
function finalGalleryFill(){
if(!finalGallery.length)return;
const title=cfg.texts.finalGalleryTitle||'Как это выглядит на вашем столе',gap=12;
const titleLines=wrappedLines(title,W-2*M,'500 21px Georgia'),titleH=Math.max(36,titleLines.length*28+8);
const free=Math.max(0,bottom-y),minPhotoH=92;
// This block may consume only the whitespace already left on the current last page.
// It never calls newPage() and never creates an extra page.
if(free<titleH+minPhotoH)return;
drawWrappedLines(titleLines,W/2,y,28,{font:'500 21px Georgia',color:GREEN_DEEP,align:'center'});
y+=titleH;
const available=Math.max(minPhotoH,bottom-y),count=available>=205&&finalGallery.length>=2?2:1;
if(count===1){const img=finalGallery[1]||finalGallery[0];if(img)pdfDrawCover(ctx,img,M,y,W-2*M,available,14)}
else{const imgs=finalGallery.slice(0,2),cellW=(W-2*M-gap)/2;if(imgs[0])pdfDrawCover(ctx,imgs[0],M,y,cellW,available,14);if(imgs[1])pdfDrawCover(ctx,imgs[1],M+cellW+gap,y,cellW,available,14)}
y=bottom;
}
newPage();
if(id==='editorial-grid'){brandHeader();hero();drawItems();summary();pricing();sellingBlock();infoCards();finalGalleryFill();footerNoteBlock();}
else if(id==='midnight-glass'){brandHeader();hero();sellingBlock();summary();infoCards();drawItems();pricing();finalGalleryFill();footerNoteBlock();}
else if(id==='emerald-gold'){brandHeader();hero();summary();sellingBlock();infoCards();pricing();drawItems();footerNoteBlock();finalGalleryFill();}
else{brandHeader();hero();sellingBlock();drawItems();summary();infoCards();pricing();footerNoteBlock();finalGalleryFill();}
canvases.forEach((c,i)=>{const x=c.getContext('2d');x.setTransform(SCALE,0,0,SCALE,0,0);x.save();x.fillStyle=FOOT;x.fillRect(0,H-52,W,52);x.strokeStyle=LINE;x.lineWidth=1;x.beginPath();x.moveTo(0,H-52.5);x.lineTo(W,H-52.5);x.stroke();pdfText(x,'Солнце Кейтеринг · Санкт-Петербург',M,H-36,620,22,{font:'12px Arial',color:MUTED,maxLines:1});pdfText(x,`${i+1} / ${canvases.length}`,W-M,H-36,100,22,{font:'12px Arial',color:MUTED,align:'right',maxLines:1});x.restore()});
return canvases;
}
async function renderOfferPdfPages(s){
const signature=window.SunSignatureOfferPDFV18,classic=window.SunClassicOfferPDFV1767;
if(signature?.renderPages)return signature.renderPages(s,renderOfferPdfPagesBase);
if(classic?.renderPages)return classic.renderPages(s,renderOfferPdfPagesBase);
return renderOfferPdfPagesBase(s);
}
async function pdfBlobFromPages(canvases){const jpeg=[];for(const c of (canvases||[]))jpeg.push(await pdfCanvasJpeg(c));return pdfFromJpegs(jpeg)}
async function buildOfferPdfBlob(s){return pdfBlobFromPages(await renderOfferPdfPages(s))}
async function triggerPdfDownload(blob,name){
const isAppleTouch=/iP(?:ad|hone|od)/i.test(navigator.userAgent||'')||(navigator.platform==='MacIntel'&&Number(navigator.maxTouchPoints||0)>1);
if(isAppleTouch&&typeof File==='function'&&navigator.share&&navigator.canShare){
try{const file=new File([blob],name,{type:'application/pdf'});if(navigator.canShare({files:[file]})){await navigator.share({files:[file],title:'Предложение клиенту'});return 'share'}}catch(err){if(err?.name==='AbortError')return 'cancel';console.warn('PDF share fallback:',err)}
}
const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;a.style.display='none';a.rel='noopener';document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),120000);return 'download';
}
function withTimeout(promise,ms,label='Операция'){return Promise.race([promise,new Promise((_,reject)=>setTimeout(()=>reject(new Error(label+' превысила время ожидания')),ms))])}
function prepareCurrentOfferPdf(prepared,renderToken=currentOffer.renderToken){
const btn=$('sunOfferPdf');if(btn){btn.disabled=true;btn.textContent='Готовлю PDF…'}
let promise;
const source=currentOffer.pages?.length?pdfBlobFromPages(currentOffer.pages):preparedPdfSnapshot(prepared).then(buildOfferPdfBlob);
promise=withTimeout(source,60000,'Подготовка PDF').then(blob=>{if(currentOffer.renderToken===renderToken){currentOffer.pdfBlob=blob;if(btn){btn.disabled=false;btn.textContent='Скачать PDF'}}return blob}).catch(err=>{console.error(err);if(currentOffer.renderToken===renderToken){currentOffer.pdfBlob=null;if(btn){btn.disabled=false;btn.textContent='Скачать PDF'}}toast(`Не удалось подготовить PDF: ${err?.message||'неизвестная ошибка'}. Нажмите «Скачать PDF», чтобы повторить.`,'error');return null}).finally(()=>{if(currentOffer.pdfPromise===promise)currentOffer.pdfPromise=null});currentOffer.pdfPromise=promise;return promise;
}
async function downloadCurrentOfferPdf(){
if(!currentOffer.prepared&&!currentOffer.pdfBlob){toast('Сначала откройте предложение клиенту.','error');return}
const btn=$('sunOfferPdf');if(btn){btn.disabled=true;btn.textContent='Готовлю PDF…'}
try{
if(currentOffer.editTimer){clearTimeout(currentOffer.editTimer);currentOffer.editTimer=null;saveOfferOverrides();await renderOffer(currentOffer.snapshot)}
else if(!currentOffer.pages?.length&&currentOffer.snapshot)await renderOffer(currentOffer.snapshot);
await ensureCurrentOfferFresh();
let blob=currentOffer.pdfBlob;
if(!blob&&currentOffer.pdfPromise)blob=await currentOffer.pdfPromise;
if(!blob&&currentOffer.prepared)blob=await prepareCurrentOfferPdf(currentOffer.prepared);
// One automatic retry after a failed background preparation.
if(!blob&&currentOffer.prepared)blob=await prepareCurrentOfferPdf(currentOffer.prepared);
if(!blob)throw new Error('PDF не удалось сформировать');
currentOffer.pdfBlob=blob;
const mode=await triggerPdfDownload(blob,filename('pdf'));
if(mode==='share')toast('PDF готов. Выберите «Сохранить в Файлы» или нужное приложение.');
else if(mode!=='cancel')toast('PDF скачивается.');
}catch(err){console.error(err);toast(`Не удалось скачать PDF: ${err?.message||'неизвестная ошибка'}. Попробуйте ещё раз.`,'error')}
finally{if(btn){btn.disabled=false;btn.textContent='Скачать PDF'}}
}
function ensureButton(){
const actions=document.querySelector('#new .actions');if(!actions)return;let b=$('sunClientOfferButton');if(!b){b=document.createElement('button');b.id='sunClientOfferButton';b.type='button';b.className='outline';b.textContent='Предложение клиенту';b.onclick=openCurrentDraftOffer;const del=$('deleteOrderBtn');if(del)window.SunSafe.insertBefore(actions,b,del);else actions.appendChild(b);}const d=getDraft();b.title=d?.id?'Открыть клиентское предложение':'Сначала сохраните заказ';
}
function patchOrderViewer(){qa('.sun-order-view-actions').forEach(actions=>{if(actions.querySelector('[data-sun-offer-id]'))return;const edit=actions.querySelector('[data-viewer-action="edit"]');const id=edit?.dataset?.id;if(!id)return;const b=document.createElement('button');b.className='outline';b.type='button';b.textContent='Предложение';b.dataset.sunOfferId=id;b.onclick=()=>openOfferForOrder(id);const spacer=actions.querySelector('.spacer');window.SunSafe.insertBefore(actions,b,spacer||null);});}
async function catalogPhotoChanged(itemId){
if(!currentOffer.snapshot||!currentOffer.orderId)return;
if(!$('sunClientOfferModal')?.classList.contains('on'))return;
if(!currentOffer.snapshot.items?.some(item=>String(item.id)===String(itemId)))return;
await renderOffer(currentOffer.snapshot);
}
const refreshOpenOffer=()=>{if(currentOffer.snapshot&&currentOffer.orderId)renderOffer(currentOffer.snapshot).catch(err=>console.warn('Offer refresh:',err))};
window.addEventListener('sunoffertemplatechange',refreshOpenOffer);
window.addEventListener('sunclientoffersettingschange',refreshOpenOffer);
window.sunOpenClientOffer=openOfferForOrder;window.sunOpenCurrentClientOffer=openCurrentDraftOffer;window.sunGetClientOfferPdfBlob=()=>currentOffer.pdfBlob;window.sunClientOfferDownload=downloadCurrentOfferPdf;window.sunClientOfferCatalogChanged=catalogPhotoChanged;window.sunClientOfferDebugHtml=offerHtml;window.sunClientOfferDebugPdf=async prepared=>buildOfferPdfBlob(await preparedPdfSnapshot(prepared));window.sunClientOfferDebugPdfPages=async prepared=>renderOfferPdfPages(await preparedPdfSnapshot(prepared));window.sunClientOfferDebugCreateSnapshot=createSnapshot;window.sunClientOfferDebugBoxCount=offerBoxCount;
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;ensureButton();patchOrderViewer();},50);};[$('new'),$('orders')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
document.addEventListener('click',e=>{const b=e.target.closest('[data-sun-offer-id]');if(b){e.preventDefault();e.stopPropagation();openOfferForOrder(b.dataset.sunOfferId)}},true);
ensureModal();ensureButton();patchOrderViewer();setTimeout(ensureButton,150);setTimeout(patchOrderViewer,300);
})();
;
/* ===== MODULE: auto-offer.js ===== */
(function(){
'use strict';
if(window.__sunAutoOfferV1)return;window.__sunAutoOfferV1=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const esc=window.SunSafe.escapeHTML;
const money=v=>`${Math.round(Number(v||0)).toLocaleString('ru-RU')} \u20bd`;
const txt={
gen:'\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435',
title:'\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0443',
hint:'\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0433\u043e\u0441\u0442\u0435\u0439. \u0411\u044e\u0434\u0436\u0435\u0442 \u0438 \u0432\u0435\u0441 \u043c\u043e\u0436\u043d\u043e \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c.',
guests:'\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0433\u043e\u0441\u0442\u0435\u0439',
budget:'\u0411\u044e\u0434\u0436\u0435\u0442 \u043d\u0430 \u0433\u043e\u0441\u0442\u044f, \u20bd',
weight:'\u0412\u0435\u0441 \u043d\u0430 \u0433\u043e\u0441\u0442\u044f, \u0433',
optional:'\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e',
close:'\u0417\u0430\u043a\u0440\u044b\u0442\u044c',
regen:'\u041f\u0435\u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c',
apply:'\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u043a \u0437\u0430\u043a\u0430\u0437\u0443',
total:'\u041c\u0435\u043d\u044e',per:'\u041d\u0430 \u0433\u043e\u0441\u0442\u044f',weightPer:'\u0412\u0435\u0441 / \u0433\u043e\u0441\u0442\u044f',piecesPer:'\u0417\u0430\u043a\u0443\u0441\u043e\u043a / \u0433\u043e\u0441\u0442\u044f',
empty:'\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0433\u043e\u0441\u0442\u0435\u0439 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u00ab\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u00bb.',
lowBudget:'\u0417\u0430\u0434\u0430\u043d\u043d\u044b\u0439 \u0431\u044e\u0434\u0436\u0435\u0442 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043d\u0438\u0437\u043a\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u0431\u043e\u0440\u0430 \u043c\u0435\u043d\u044e.',
applied:'\u041f\u043e\u0434\u043e\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u043c\u0435\u043d\u044e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 \u0437\u0430\u043a\u0430\u0437. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0435\u0433\u043e \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u00ab\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u00bb.',
noEvent:'\u2014 \u041d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u2014',custom:'\u0421\u0432\u043e\u0451\u2026',customPh:'\u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u0430\u0442'
};
const toast=(m,t='success')=>{try{window.SunEnterprise?.toast?.(m,t)}catch(_){};};
const getBoxes=()=>{try{if(Array.isArray(boxes))return boxes}catch(_){}try{return JSON.parse(localStorage.getItem('sunBoxes')||'[]')}catch(_){return[]}};
const getOrders=()=>{try{if(Array.isArray(orders))return orders}catch(_){}try{return JSON.parse(localStorage.getItem('sunOrders')||'[]')}catch(_){return[]}};
const getDraft=()=>{try{return draft}catch(_){return null}};
const parseWeight=v=>{const s=String(v||'').toLowerCase().replace(/,/g,'.'),m=s.match(/(\d+(?:\.\d+)?)\s*(\u043a\u0433|kg|\u0433|g)(?:\s|$)/);if(!m)return 0;const n=Number(m[1]);return m[2]==='\u043a\u0433'||m[2]==='kg'?n*1000:n};
const guestMax=v=>{const a=String(v||'').match(/\d+/g)?.map(Number)||[];return a.length?Math.max(...a):0};
function portions(item){
const explicit=Math.max(0,Number(item?.pieces||0));if(explicit>0)return explicit;
let sum=0;
for(const x of item?.ingredients||[]){if(!Array.isArray(x))continue;const q=Math.max(0,Number(x[1]||0)),u=String(x[2]||'').toLowerCase();if(/\u0448\u0442|\u043f\u043e\u0440|\u043a\u0443\u0441/.test(u))sum+=q;}
if(sum>=5)return sum;
return Math.max(sum,guestMax(item?.guests),10);
}
function popularityMap(){
const stats=new Map();let maxOrders=0,maxQty=0;
for(const o of getOrders()){
if(String(o?.status||'').toLowerCase()==='отменён')continue;
const seen=new Set();
for(const l of o?.lines||[]){const id=String(l?.id??'');if(!id)continue;let st=stats.get(id);if(!st){st={orders:0,qty:0};stats.set(id,st)}st.qty+=Math.max(0,Number(l?.qty||0));if(!seen.has(id)){st.orders+=1;seen.add(id)}}
}
for(const st of stats.values()){maxOrders=Math.max(maxOrders,st.orders);maxQty=Math.max(maxQty,st.qty)}
const out=new Map();for(const [id,st] of stats)out.set(id,(maxOrders?st.orders/maxOrders:0)*.78+(maxQty?st.qty/maxQty:0)*.22);return out;
}
function foodCandidates(){const pop=popularityMap();return getBoxes().filter(x=>Number(x?.category||0)===0&&x?.hidden!==true&&Number(x?.price||0)>0&&parseWeight(x?.weight)>0&&!/\u0433\u043e\u0442\u043e\u0432\u044b\u0435\s+\u043c\u0435\u043d\u044e/i.test(String(x?.catalogSection||''))).map(x=>({raw:x,id:String(x.id),name:String(x.name||''),section:String(x.catalogSection||'\u0411\u043e\u043a\u0441\u044b'),price:Number(x.price||0),grams:parseWeight(x.weight),portions:portions(x),photo:String(x.photo||'sun-logo.png'),popularity:Number(pop.get(String(x.id))||0)}));}
function hashNoise(text,seed){let h=2166136261^(seed*2654435761);for(const c of String(text)){h^=c.charCodeAt(0);h=Math.imul(h,16777619)}return ((h>>>0)%10000)/10000;}
function stateKey(s){return [...s.q.entries()].sort((a,b)=>a[0].localeCompare(b[0])).map(([id,q])=>id+':'+q).join('|')}
function score(s,t,seed){
const pg=s.portions/t.targetPortions,pp=pg<1?(1-pg)*5:(pg-1)*1.15;
let wp=0;if(t.targetWeight){const r=s.grams/t.targetWeight;wp=r<1?(1-r)*4:(r-1)*1.1;}
let bp=0;if(t.budget){if(s.cost>t.budget)return 100+(s.cost-t.budget)/t.budget*20;bp=Math.max(0,(t.budget-s.cost)/t.budget)*.72}else bp=(s.cost/Math.max(1,t.guests*5000))*.04;
const boxes=[...s.q.values()].reduce((a,b)=>a+b,0),unique=s.q.size,sections=s.sections.size;
const minPositions=t.guests>=15?4:(t.guests>=8?3:2);const diversity=(boxes-unique)*.08+Math.max(0,Math.min(boxes,4)-sections)*.055-Math.min(sections,4)*.025+Math.max(0,minPositions-unique)*.24;
const popularityBonus=boxes?Math.min(.22,(Number(s.popularity||0)/boxes)*.18):0;
return pp+wp+bp+diversity-popularityBonus+hashNoise(stateKey(s),seed)*.012;
}
function generateMenu(t,seed){
const cand=foodCandidates();if(!cand.length)return null;
const empty={q:new Map(),portions:0,grams:0,cost:0,sections:new Set(),popularity:0};
let beam=[empty],all=[empty];
const maxBoxes=Math.min(30,Math.max(4,Math.ceil(t.targetPortions/20)+4,t.targetWeight?Math.ceil(t.targetWeight/700)+4:0));
for(let step=0;step<maxBoxes;step++){
const map=new Map();
for(const st of beam){
for(const c of cand){
const old=st.q.get(c.id)||0;if(old>=4)continue;
const cost=st.cost+c.price;if(t.budget&&cost>t.budget)continue;
const q=new Map(st.q);q.set(c.id,old+1);const sections=new Set(st.sections);sections.add(c.section);
const ns={q,portions:st.portions+c.portions,grams:st.grams+c.grams,cost,sections,popularity:Number(st.popularity||0)+Number(c.popularity||0)};const key=stateKey(ns),prev=map.get(key);
if(!prev||score(ns,t,seed)<score(prev,t,seed))map.set(key,ns);
}
}
if(!map.size)break;
const next=[...map.values()].sort((a,b)=>score(a,t,seed)-score(b,t,seed)).slice(0,130);beam=next;all.push(...next.slice(0,60));
const best=next[0];if(best&&best.portions>=t.targetPortions*1.05&&(!t.targetWeight||best.grams>=t.targetWeight*1.03)&&step>=3)break;
}
const useful=all.filter(s=>s.q.size>0&&( !t.budget || s.cost<=t.budget));if(!useful.length)return null;
const best=useful.sort((a,b)=>score(a,t,seed)-score(b,t,seed))[0];
const items=[...best.q.entries()].map(([id,qty])=>{const c=cand.find(x=>x.id===id);return {...c,qty,sum:c.price*qty,totalGrams:c.grams*qty,totalPortions:c.portions*qty}}).sort((a,b)=>a.section.localeCompare(b.section,'ru')||a.name.localeCompare(b.name,'ru'));
return {...best,items};
}
function ensureStyle(){if($('sun-auto-offer-style'))return;const st=document.createElement('style');st.id='sun-auto-offer-style';st.textContent=`
#sunAutoOfferModal{z-index:100060}#sunAutoOfferModal .dialog{width:min(980px,calc(100vw - 28px));max-height:94vh;padding:0;overflow:auto;border-radius:18px;background:#f5f3fa}.sun-auto-head{position:sticky;top:0;z-index:3;display:flex;align-items:flex-start;gap:12px;padding:15px 17px;background:#fff;border-bottom:1px solid #e4e0ef}.sun-auto-head h2{margin:0;color:#241d45;font-size:20px}.sun-auto-head p{margin:4px 0 0;color:#7b748d;font-size:12px}.sun-auto-head .spacer{flex:1}.sun-auto-form{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;padding:16px 17px 5px}.sun-auto-form label{display:grid;gap:6px;color:#5d5670;font-size:11px;font-weight:800}.sun-auto-form small{font-weight:500;color:#9b94aa}.sun-auto-form input{width:100%;border:1px solid #d9d2e9;border-radius:11px;padding:10px 11px;background:#fff;color:#29243d}.sun-auto-generate{margin:10px 17px 2px}.sun-auto-result{padding:12px 17px 18px}.sun-auto-placeholder{padding:30px;text-align:center;border:1px dashed #cbc2df;border-radius:16px;color:#827a95;background:#fff}.sun-auto-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px;margin-bottom:12px}.sun-auto-kpi{background:#241d45;color:#fff;border-radius:14px;padding:13px}.sun-auto-kpi small{display:block;color:#c9c2db;font-size:10px}.sun-auto-kpi b{display:block;margin-top:5px;font-size:18px}.sun-auto-warning{margin:0 0 12px;padding:10px 12px;border-radius:11px;background:#fff4d9;color:#77550c;font-size:12px}.sun-auto-items{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}.sun-auto-item{display:grid;grid-template-columns:82px minmax(0,1fr);gap:10px;padding:9px;background:#fff;border:1px solid #e1dceb;border-radius:14px}.sun-auto-item img{width:82px;height:82px;object-fit:cover;border-radius:10px;background:#eeeaf7}.sun-auto-item h3{margin:2px 0 5px;color:#302a45;font-size:13px;line-height:1.25}.sun-auto-item p{margin:3px 0;color:#827b92;font-size:10px}.sun-auto-item b{color:#5b48a6}.sun-auto-foot{display:flex;justify-content:flex-end;gap:8px;padding:13px 17px;background:#fff;border-top:1px solid #e4e0ef;position:sticky;bottom:0}.sun-event-custom-input{display:none!important;margin-top:6px!important}.sun-event-custom-input.on{display:block!important}#sunOrderCards .sun-order-card.sun-has-prepayment .sun-order-card-date{background:linear-gradient(145deg,#238b57,#36ad72)!important;border-color:#238b57!important;box-shadow:0 5px 14px rgba(35,139,87,.24)!important}
@media(max-width:700px){#sunAutoOfferModal{padding:0}#sunAutoOfferModal .dialog{width:100vw;height:100vh;max-height:none;border-radius:0}.sun-auto-form{grid-template-columns:1fr}.sun-auto-summary{grid-template-columns:1fr 1fr}.sun-auto-items{grid-template-columns:1fr}.sun-auto-head h2{font-size:17px}}
`;document.head.appendChild(st)}
let generationSeed=1,currentResult=null,currentTargets=null;
function ensureModal(){if($('sunAutoOfferModal'))return;const m=document.createElement('div');m.className='modal';m.id='sunAutoOfferModal';m.innerHTML=`<div class="dialog"><div class="sun-auto-head"><div><h2>${txt.title}</h2><p>${txt.hint}</p></div><div class="spacer"></div><button class="outline" id="sunAutoClose" type="button">${txt.close}</button></div><div class="sun-auto-form"><label>${txt.guests}<input id="sunAutoGuests" type="number" min="1" step="1" inputmode="numeric" placeholder="20"></label><label>${txt.budget} <small>${txt.optional}</small><input id="sunAutoBudget" type="number" min="0" step="10" inputmode="decimal" placeholder="1500"></label><label>${txt.weight} <small>${txt.optional}</small><input id="sunAutoWeight" type="number" min="0" step="10" inputmode="decimal" placeholder="300"></label></div><button class="primary sun-auto-generate" id="sunAutoGenerate" type="button">${txt.gen}</button><div class="sun-auto-result" id="sunAutoResult"><div class="sun-auto-placeholder">${txt.empty}</div></div><div class="sun-auto-foot" id="sunAutoFoot" style="display:none"><button class="outline" id="sunAutoRegen" type="button">${txt.regen}</button><button class="primary" id="sunAutoApply" type="button">${txt.apply}</button></div></div>`;document.body.appendChild(m);$('sunAutoClose').onclick=closeGenerator;$('sunAutoGenerate').onclick=()=>runGenerator(false);$('sunAutoRegen').onclick=()=>runGenerator(true);$('sunAutoApply').onclick=applyGenerated;}
function openGenerator(){ensureStyle();ensureModal();const d=getDraft()||{},g=Number($('eventGuests')?.value||d.guestsCount||0);$('sunAutoGuests').value=g||'';$('sunAutoResult').innerHTML=`<div class="sun-auto-placeholder">${txt.empty}</div>`;$('sunAutoFoot').style.display='none';currentResult=null;$('sunAutoOfferModal').classList.add('on');}
function closeGenerator(){$('sunAutoOfferModal')?.classList.remove('on')}
function runGenerator(regen){
const guests=Math.max(0,Math.round(Number($('sunAutoGuests')?.value||0))),perBudget=Math.max(0,Number($('sunAutoBudget')?.value||0)),perWeight=Math.max(0,Number($('sunAutoWeight')?.value||0));
if(!guests){toast(txt.guests,'warn');$('sunAutoGuests')?.focus();return}
if(regen)generationSeed++;const targets={guests,targetPortions:guests*5,targetWeight:perWeight?guests*perWeight:0,budget:perBudget?guests*perBudget:0,perBudget,perWeight};const result=generateMenu(targets,generationSeed);currentTargets=targets;currentResult=result;renderGenerated(result,targets);
}
function renderGenerated(r,t){const root=$('sunAutoResult'),foot=$('sunAutoFoot');if(!r){root.innerHTML=`<div class="sun-auto-warning">${txt.lowBudget}</div>`;foot.style.display='none';return}
const pp=r.cost/t.guests,wp=r.grams/t.guests,pieces=r.portions/t.guests;const warns=[];if(r.portions<t.targetPortions*.92)warns.push(`\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c ${pieces.toFixed(1)} \u0437\u0430\u043a\u0443\u0441\u043e\u043a \u043d\u0430 \u0433\u043e\u0441\u0442\u044f \u0432\u043c\u0435\u0441\u0442\u043e \u0446\u0435\u043b\u0435\u0432\u044b\u0445 5.`);if(t.targetWeight&&r.grams<t.targetWeight*.9)warns.push(`\u0412\u0435\u0441 ${Math.round(wp)} \u0433/\u0433\u043e\u0441\u0442\u044f \u043d\u0438\u0436\u0435 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0445 ${Math.round(t.perWeight)} \u0433.`);
root.innerHTML=`<div class="sun-auto-summary"><div class="sun-auto-kpi"><small>${txt.total}</small><b>${money(r.cost)}</b></div><div class="sun-auto-kpi"><small>${txt.per}</small><b>${money(pp)}</b></div><div class="sun-auto-kpi"><small>${txt.weightPer}</small><b>${Math.round(wp).toLocaleString('ru-RU')} \u0433</b></div><div class="sun-auto-kpi"><small>${txt.piecesPer}</small><b>${pieces.toFixed(1)}</b></div></div>${warns.length?`<div class="sun-auto-warning">${warns.join('<br>')}</div>`:''}<div class="sun-auto-items">${r.items.map(i=>`<article class="sun-auto-item"><img src="${esc(i.photo)}" alt=""><div><h3>${esc(i.name)}</h3><p>${esc(i.section)} \u00b7 <b>\u00d7${i.qty}</b></p><p>${money(i.sum)} \u00b7 ${Math.round(i.totalGrams).toLocaleString('ru-RU')} \u0433 \u00b7 ${Math.round(i.totalPortions)} \u043f\u043e\u0440\u0446.</p></div></article>`).join('')}</div>`;foot.style.display='flex';}
function applyGenerated(){if(!currentResult||!currentTargets)return;const d=getDraft();if(!d)return;d.lines=currentResult.items.map(i=>({id:i.id,qty:i.qty}));d.guestsCount=currentTargets.guests;const g=$('eventGuests');if(g){g.value=currentTargets.guests;g.dispatchEvent(new Event('input',{bubbles:true}))}try{window.render?.();window.updateOrderSummary?.()}catch(_){}closeGenerator();toast(txt.applied,'success');}
function ensureGeneratorButton(){const actions=document.querySelector('#new .actions');if(!actions)return;let b=$('sunAutoOfferButton');if(!b){b=document.createElement('button');b.id='sunAutoOfferButton';b.type='button';b.className='outline';b.textContent=txt.gen;b.onclick=openGenerator;const offer=$('sunClientOfferButton');if(offer)offer.insertAdjacentElement('afterend',b);else actions.appendChild(b)}}
function decoratePaidCards(){const map=new Map(getOrders().map(o=>[String(o.id),o]));qa('#sunOrderCards .sun-order-card').forEach(card=>{const o=map.get(String(card.dataset.orderId||''));card.classList.toggle('sun-has-prepayment',Number(o?.prepayment||0)>0)})}
function ensureEventOptions(){
const s=$('event');if(!s||s.tagName!=='SELECT')return;const keep=String(s.value||'');
qa('option',s).filter(o=>(o.textContent||'').trim()==='Новое мероприятие'||o.dataset.sunCustom==='1').forEach(o=>o.remove());
$('sunEventCustomInput')?.remove();
let blank=qa('option',s).find(o=>o.dataset.sunBlank==='1'||o.value==='');
if(!blank){blank=document.createElement('option');blank.value='';blank.textContent=txt.noEvent;blank.dataset.sunBlank='1';s.insertBefore(blank,s.firstChild)}else{blank.value='';if(blank.textContent!==txt.noEvent)blank.textContent=txt.noEvent;blank.dataset.sunBlank='1';if(s.firstElementChild!==blank)s.insertBefore(blank,s.firstChild)}
const opts=qa('option',s);s.value=opts.some(o=>o.value===keep)?keep:'';
if(s.dataset.sunPersistentEventBound!=='1'){s.dataset.sunPersistentEventBound='1';s.addEventListener('change',()=>{const d=getDraft();if(d)d.event=s.value||''})}
}
function syncEventFromDraft(){ensureEventOptions();const s=$('event'),d=getDraft();if(!s||!d)return;const value=String(d.event||'').trim();if(!value){s.value='';return}let opt=qa('option',s).find(o=>o.value===value);if(!opt){opt=document.createElement('option');opt.value=value;opt.textContent=value;opt.dataset.sunLegacy='1';s.appendChild(opt)}s.value=value;}
function prepareEventForSave(){ensureEventOptions();const s=$('event'),d=getDraft();if(s&&d)d.event=s.value||'';}
const oldSave=window.saveOrder;if(typeof oldSave==='function')window.saveOrder=function(...a){prepareEventForSave();const r=oldSave.apply(this,a);setTimeout(()=>{syncEventFromDraft();decoratePaidCards()},0);return r};
const oldOpen=window.openOrder;if(typeof oldOpen==='function')window.openOrder=function(...a){const r=oldOpen.apply(this,a);setTimeout(syncEventFromDraft,0);setTimeout(syncEventFromDraft,80);return r};
const oldReset=window.resetDraft;if(typeof oldReset==='function')window.resetDraft=function(...a){const r=oldReset.apply(this,a);setTimeout(syncEventFromDraft,0);return r};
ensureStyle();ensureModal();ensureGeneratorButton();ensureEventOptions();syncEventFromDraft();decoratePaidCards();
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;ensureGeneratorButton();ensureEventOptions();decoratePaidCards();},50);};[$('new'),$('orders')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
setTimeout(()=>{ensureGeneratorButton();syncEventFromDraft();decoratePaidCards()},180);setTimeout(()=>{ensureGeneratorButton();syncEventFromDraft();decoratePaidCards()},700);
})();
;
/* ===== MODULE: mobile-pwa.js ===== */
/* Solnce Catering classic PWA/sync layer - keeps the original application UI */
(()=>{
'use strict';
if(window.__sunClassicPwaV1)return;
window.__sunClassicPwaV1=true;
const q=(s,r=document)=>r.querySelector(s);
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=window.SunSafe.escapeHTML;
/* PWA only. No alternate mobile chrome or mobile home is created here. */
function initPwa(){
if('serviceWorker' in navigator && /^https?:$/.test(location.protocol)){
navigator.serviceWorker.register('./service-worker.js').then(reg=>reg.update()).catch(()=>{});
}
}
/* Cross-device sync. This is deliberately UI-neutral. */
const SYNC_META='sunCloudSyncMetaV1';
const SYNC_TOKEN='sunCloudSyncTokenV1';
const SYNC_WORKSPACE='sunCloudSyncWorkspaceV1';
const sync={busy:false,status:'local',timer:null,lastError:''};
const excludedKey=k=>k===SYNC_META||k===SYNC_TOKEN||k===SYNC_WORKSPACE||k.startsWith('sunCloudSyncTransient');
function hash(s){
s=String(s??'');
let h=2166136261;
for(let i=0;i<s.length;i++){h^=s.charCodeAt(i);h=Math.imul(h,16777619)}
return (h>>>0).toString(16);
}
function getMeta(){try{return JSON.parse(localStorage.getItem(SYNC_META)||'{}')||{}}catch(_){return{}}}
function setMeta(m){localStorage.setItem(SYNC_META,JSON.stringify(m))}
function syncWorkspace(){return localStorage.getItem(SYNC_WORKSPACE)||'main'}
function syncHeaders(){return {'Content-Type':'application/json','X-Sun-Token':localStorage.getItem(SYNC_TOKEN)||''}}
function captureSyncQuery(){
try{
const u=new URL(location.href);
const token=u.searchParams.get('syncToken');
const workspace=u.searchParams.get('workspace');
let changed=false;
if(token){localStorage.setItem(SYNC_TOKEN,token);u.searchParams.delete('syncToken');changed=true}
if(workspace){localStorage.setItem(SYNC_WORKSPACE,workspace);u.searchParams.delete('workspace');changed=true}
if(changed)history.replaceState(null,'',u.pathname+u.search+u.hash);
}catch(_){ }
}
function setSyncStatus(status,msg=''){
sync.status=status;
sync.lastError=msg;
const statusEl=q('#sunSyncSettingsStatus');
if(statusEl){
statusEl.textContent=status==='ok'?'Синхронизация активна':status==='error'?`Ошибка: ${msg||'нет связи'}`:'Локальный режим';
}
}
async function pullRemote(){
const token=localStorage.getItem(SYNC_TOKEN);
if(!token||!/^https?:$/.test(location.protocol))return {entries:{}};
const r=await fetch(`/api/sync?workspace=${encodeURIComponent(syncWorkspace())}`,{headers:syncHeaders(),cache:'no-store'});
if(!r.ok)throw new Error(`HTTP ${r.status}`);
return r.json();
}
function scanLocalChanges(meta,force=false){
const changes={};
const now=Date.now();
for(let i=0;i<localStorage.length;i++){
const key=localStorage.key(i);
if(!key||excludedKey(key))continue;
const value=localStorage.getItem(key)??'';
const h=hash(value);
const old=meta[key];
if(force||!old||old.h!==h){
meta[key]={h,t:now};
changes[key]={value,updatedAt:now};
}
}
return changes;
}
async function pushChanges(changes){
if(!Object.keys(changes).length)return;
const r=await fetch('/api/sync',{method:'POST',headers:syncHeaders(),body:JSON.stringify({workspace:syncWorkspace(),changes})});
if(!r.ok)throw new Error(`HTTP ${r.status}`);
return r.json();
}
function activeView(){return qa('.view.on').find(v=>v.id)||null}
function refreshActiveView(){
const active=document.activeElement;
const editing=active&&/^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);
if(editing||qa('.modal.on').length)return;
const view=activeView();
if(!view)return;
const map={orders:'Заказы',calendar:'Календарь',shopping:'Закупки',stock:'Склад',clients:'Клиенты',mailings:'Рассылки',money:'Деньги',team:'Команда',suppliers:'Поставщики',stats:'Статистика','sun-print-center':'Печать','sun-routes-view':'Маршруты','enterprise-settings':'Настройки'};
const label=map[view.id];
if(!label)return;
const button=qa('header nav button').find(b=>String(b.textContent||'').trim()===label||String(b.textContent||'').trim()===(label==='Закупки'?'Закупка':label));
if(button){try{HTMLElement.prototype.click.call(button)}catch(_){}}
}
async function syncNow(){
if(sync.busy)return;
const token=localStorage.getItem(SYNC_TOKEN);
if(!token||!/^https?:$/.test(location.protocol)){setSyncStatus('local');ensureSyncSettings();return}
sync.busy=true;
try{
const meta=getMeta();
const remote=await pullRemote();
let remoteChanged=false;
const entries=remote.entries||{};
Object.entries(entries).forEach(([key,item])=>{
if(excludedKey(key)||!item)return;
const rt=Number(item.updatedAt||0),lt=Number(meta[key]?.t||0);
if(rt>lt){
localStorage.setItem(key,String(item.value??''));
meta[key]={h:hash(item.value??''),t:rt};
remoteChanged=true;
}
});
const changes=scanLocalChanges(meta,Object.keys(entries).length===0);
setMeta(meta);
await pushChanges(changes);
setSyncStatus('ok');
ensureSyncSettings();
if(remoteChanged){
window.dispatchEvent(new CustomEvent('suncloudsync',{detail:{remoteChanged:true}}));
refreshActiveView();
}
}catch(e){
setSyncStatus('error',e?.message||'нет связи');
ensureSyncSettings();
}finally{sync.busy=false}
}
window.sunSyncNow=syncNow;
function ensureSyncSettings(){
const settings=q('#enterprise-settings');
if(!settings||q('#sunSyncSettingsCard'))return;
const card=document.createElement('section');
card.id='sunSyncSettingsCard';
card.className='settings-card';
card.style.cssText='margin-top:14px;border:1px solid #e2e4e9;border-radius:12px;background:#fff;padding:14px';
const token=localStorage.getItem(SYNC_TOKEN)||'';
card.innerHTML=`<h2 style="margin:0 0 7px;color:#30284f">Синхронизация устройств</h2><p class="hint" style="margin:0 0 10px">Одна база для компьютера и телефона. Интерфейс приложения остаётся одинаковым на всех устройствах.</p><div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"><label>Рабочая база<input id="sunSyncWorkspaceInput" value="${esc(syncWorkspace())}"></label><label>Режим<input value="${token?'Общая база':'Локально'}" readonly></label></div><p style="margin:9px 0"><b id="sunSyncSettingsStatus">${token?'Подключение…':'Локальный режим'}</b></p><div class="actions"><button class="primary" type="button" id="sunSyncNowBtn">Синхронизировать сейчас</button><button class="outline" type="button" id="sunSyncForgetBtn">Отключить общую базу</button></div>${token?'':'<p class="hint">Чтобы включить общую базу, запускайте приложение через server.js и открывайте на телефоне ссылку сервера.</p>'}`;
const audit=[...settings.children].find(el=>(el.querySelector?.(':scope > h2')?.textContent||'').trim()==='История изменений');
if(audit)window.SunSafe.insertBefore(settings,card,audit);else settings.appendChild(card);
q('#sunSyncWorkspaceInput',card)?.addEventListener('change',e=>{localStorage.setItem(SYNC_WORKSPACE,String(e.target.value||'main').trim()||'main');syncNow()});
const now=q('#sunSyncNowBtn',card);if(now)now.onclick=()=>syncNow();
const forget=q('#sunSyncForgetBtn',card);if(forget)forget.onclick=()=>{if(confirm('Отключить синхронизацию на этом устройстве? Локальные данные останутся.')){localStorage.removeItem(SYNC_TOKEN);setSyncStatus('local');card.remove();ensureSyncSettings()}};
setSyncStatus(sync.status,sync.lastError);
}
function initSync(){
// Legacy same-Wi-Fi server sync is intentionally disabled in Cloud v2 builds.
// Supabase synchronization is handled by cloud-sync-v2.js.
setSyncStatus('local');
}
function init(){
initPwa();
initSync();
document.documentElement.classList.add('sun-classic-mobile-ready');
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init,{once:true});else init();
})();
;
/* ===== MODULE: batch-update.js ===== */
(function(){
'use strict';
if(window.__sunBatchUpdate20260828)return;window.__sunBatchUpdate20260828=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const orderList=()=>{try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){}try{return JSON.parse(localStorage.getItem('sunOrders')||'[]')}catch(_){return[]}};
const localToday=()=>{const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`};
const dateKey=o=>/^\d{4}-\d{2}-\d{2}$/.test(String(o?.date||''))?String(o.date):'';
const timeKey=o=>/^\d{2}:\d{2}$/.test(String(o?.time||''))?String(o.time):'23:59';
let ordersMode='current';
function splitOrders(all){
const today=localToday();
const current=[],done=[];
all.forEach(o=>{const d=dateKey(o);(d&&d<today?done:current).push(o)});
current.sort((a,b)=>{const ad=dateKey(a)||'9999-12-31',bd=dateKey(b)||'9999-12-31';return (ad+timeKey(a)).localeCompare(bd+timeKey(b))||Number(a.id||0)-Number(b.id||0)});
done.sort((a,b)=>{const ad=dateKey(a)||'0000-00-00',bd=dateKey(b)||'0000-00-00';return (bd+timeKey(b)).localeCompare(ad+timeKey(a))||Number(b.id||0)-Number(a.id||0)});
return {current,done};
}
function ensureOrdersTabs(){
const view=$('orders');if(!view)return;
let bar=$('sunOrdersPeriodTabs');
if(!bar){bar=document.createElement('div');bar.id='sunOrdersPeriodTabs';bar.className='sun-orders-period-tabs';bar.innerHTML='<button type="button" data-orders-period="current">Текущие</button><button type="button" data-orders-period="done">Завершённые</button>';const anchor=view.querySelector('.sun-orders-viewbar')||view.querySelector('.head')||view.firstElementChild?.nextSibling;const h=view.querySelector('h1');if(h)h.insertAdjacentElement('afterend',bar);else view.prepend(bar);bar.addEventListener('click',e=>{const b=e.target.closest('[data-orders-period]');if(!b)return;ordersMode=b.dataset.ordersPeriod==='done'?'done':'current';window.renderOrders?.();});}
const split=splitOrders(orderList());
qa('[data-orders-period]',bar).forEach(b=>{const key=b.dataset.ordersPeriod;b.classList.toggle('on',key===ordersMode);const text=key==='current'?`Текущие · ${split.current.length}`:`Завершённые · ${split.done.length}`;if(b.textContent!==text)b.textContent=text});
}
const previousRenderOrders=window.renderOrders;
if(typeof previousRenderOrders==='function')window.renderOrders=function(...args){
ensureOrdersTabs();
let ref=null,backup=null;
try{if(typeof orders!=='undefined'&&Array.isArray(orders)){ref=orders;backup=orders.slice();const split=splitOrders(backup),show=ordersMode==='done'?split.done:split.current;orders.length=0;orders.push(...show)}}catch(_){}
let result;
try{result=previousRenderOrders.apply(this,args)}finally{if(ref&&backup){ref.length=0;ref.push(...backup)}}
setTimeout(()=>{ensureOrdersTabs();compactSettingsLists();setDeliverySteps()},0);
return result;
};
let backupExpanded=false,auditExpanded=false;
function compactList(host,rowSelector,kind){
if(!host)return;const rows=qa(rowSelector,host);const expanded=kind==='backup'?backupExpanded:auditExpanded;
rows.forEach((r,i)=>{r.style.display=(expanded||i<4)?'':'none'});
let b=host.querySelector(':scope > .sun-compact-toggle');
if(rows.length<=4){b?.remove();return}
if(!b){b=document.createElement('button');b.type='button';b.className='outline sun-compact-toggle';b.onclick=()=>{if(kind==='backup')backupExpanded=!backupExpanded;else auditExpanded=!auditExpanded;compactSettingsLists()};host.appendChild(b)}
const text=expanded?'Скрыть':`Показать ещё (${rows.length-4})`;if(b.textContent!==text)b.textContent=text;
}
function compactSettingsLists(){compactList($('backupList'),'.backup-row','backup');compactList($('auditList'),'.audit-row','audit')}
function setDeliverySteps(){
const delivery=$('deliveryCost');if(delivery)delivery.step='100';
const transport=$('orderDeliveryCost');if(transport)transport.step='100';
}
function ensureCss(){if($('sun-batch-update-style'))return;const st=document.createElement('style');st.id='sun-batch-update-style';st.textContent=`
.sun-orders-period-tabs{display:flex;gap:7px;align-items:center;margin:10px 0 13px;padding:4px;width:max-content;max-width:100%;border-radius:12px;background:#f1eff8;border:1px solid #e1dced}.sun-orders-period-tabs button{border:0;background:transparent;color:#716a84;border-radius:9px;padding:7px 12px;font-size:11px;font-weight:800;cursor:pointer}.sun-orders-period-tabs button.on{background:#241d45;color:#fff;box-shadow:0 4px 12px rgba(36,29,69,.18)}.sun-compact-toggle{margin-top:9px!important}.sun-extra-services-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:8px}.sun-extra-service{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border:1px solid #d9d2e9;border-radius:14px;background:#fff;color:#4e4961}.sun-extra-service:before{content:'✓';display:grid;place-items:center;flex:0 0 24px;height:24px;border-radius:8px;background:#241d45;color:#f2c85f;font-weight:900}.sun-extra-service b{display:block;color:#241d45;font-size:14px}.sun-extra-service small{display:block;color:#817a94;margin-top:2px;line-height:1.3}@media(max-width:620px){.sun-orders-period-tabs{width:100%}.sun-orders-period-tabs button{flex:1}.sun-extra-services-grid{grid-template-columns:1fr}}
`;document.head.appendChild(st)}
function maintain(){ensureCss();ensureOrdersTabs();compactSettingsLists();setDeliverySteps()}
(()=>{let queued=false;const run=()=>{if(queued)return;queued=true;setTimeout(()=>{queued=false;maintain();},60);};[$('new'),$('orders'),$('enterprise-settings')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
document.addEventListener('click',e=>{const nav=e.target.closest('header nav button');if(nav&&String(nav.dataset.navLabel||nav.textContent||'').trim()==='Настройки')setTimeout(compactSettingsLists,80)},true);
maintain();setTimeout(maintain,200);setTimeout(maintain,900);
})();
;
/* ===== MODULE: cloud-sync-v2.js ===== */
(() => {
'use strict';
if (window.SunCloudV2) return;
const VERSION = '2026.09.05-cloud-rbac-v7-developer-support';
const CONFIG_KEY = 'sunCloudV2Config';
const CLIENT_ID_KEY = 'sunCloudClientIdV2';
const DB_NAME = 'SunCloudV2';
const DB_VERSION = 1;
const BUCKET = 'sun-media';
const SUPABASE_JS = 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.112.4';
const MEDIA_PREFIX = '__sun_media__:';
const SYNC_DEBOUNCE = 1800;
const $ = id => document.getElementById(id);
const esc=window.SunSafe.escapeHTML;
const clone = value => value == null ? value : (typeof structuredClone === 'function' ? structuredClone(value) : JSON.parse(JSON.stringify(value)));
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const toast = (text, type='info', timeout=4500) => {
try { if (window.SunEnterprise?.toast) return window.SunEnterprise.toast(text, type, timeout); } catch (_) {}
console[type === 'error' ? 'error' : 'log']('[SunCloud]', text);
};
const DEFAULT_SUPABASE_URL = 'https://cksuehzcimitsxmeloes.supabase.co';
const DEFAULT_SUPABASE_KEY = 'sb_publishable_v8Z3hEBnu7zsDwAb5KCWcg_T96IejsM';
const SUPABASE_API_PROXY = 'https://api.caterium.ru';
const PROXY_FETCH_TIMEOUT_MS = 7000;
function supabaseProxyFetch(input, init) {
try {
const url = typeof input === 'string' ? input : input?.url;
if (url && url.indexOf(DEFAULT_SUPABASE_URL) === 0) {
const proxied = SUPABASE_API_PROXY + url.slice(DEFAULT_SUPABASE_URL.length);
input = typeof input === 'string' ? proxied : new Request(proxied, input);
}
} catch (_) {}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), PROXY_FETCH_TIMEOUT_MS);
const externalSignal = init?.signal;
if (externalSignal) {
if (externalSignal.aborted) controller.abort();
else externalSignal.addEventListener('abort', () => controller.abort(), {once:true});
}
return fetch(input, {...init, signal: controller.signal}).finally(() => clearTimeout(timer));
}
let config = loadConfig();
let client = null;
let clientSignature = '';
let session = null;
let memberships = [];
let workspace = null;
let supportMode = null;
let membershipsLoading = false;
let membershipsLoaded = false;
let realtimeChannel = null;
let syncTimer = null;
let isSyncing = false;
let dirty = false;
const CLOUD_RPC_TIMEOUT_MS=12000;
const CLOUD_CONFLICT_MAX_RETRIES=4;
function errText(e,fallback='Неизвестная ошибка'){
if(e==null)return fallback;
if(typeof e==='string')return e||fallback;
if(e.message)return String(e.message);
try{const j=JSON.stringify(e);if(j&&j!=='{}'&&j!=='null')return j;}catch(_){}
return fallback;
}
async function sunCloudAwait(promise,label='Облачный запрос',timeout=CLOUD_RPC_TIMEOUT_MS){
let timer=0;
try{return await Promise.race([Promise.resolve(promise),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error(label+': превышено время ожидания ('+Math.round(timeout/1000)+' сек.)')),timeout)})]);}
finally{if(timer)clearTimeout(timer)}
}
let suppressStorageTracking = false;
let observedSignature = '';
let lastStatus = 'local';
let lastError = '';
function loadConfig() {
let value = {};
try { value = JSON.parse(localStorage.getItem(CONFIG_KEY) || '{}') || {}; } catch (_) {}
if (!value.url || !value.key) {
try {
const legacy = JSON.parse(localStorage.getItem('sunEnterpriseSettingsV1') || '{}') || {};
const cloud = legacy.cloud || {};
value.url ||= String(cloud.supabaseUrl || '').trim();
value.key ||= String(cloud.anonKey || '').trim();
} catch (_) {}
}
return {
url: String(value.url || DEFAULT_SUPABASE_URL || '').trim().replace(/\/$/, ''),
key: String(value.key || DEFAULT_SUPABASE_KEY || '').trim(),
autoSync: value.autoSync !== false,
workspaceId: String(value.workspaceId || ''),
migrated: value.migrated && typeof value.migrated === 'object' ? value.migrated : {},
lastSync: String(value.lastSync || ''),
localWorkspaceId: String(value.localWorkspaceId || ''),
tenantStorageReady: value.tenantStorageReady === true,
legacyLocalWorkspaceId: String(value.legacyLocalWorkspaceId || (value.tenantStorageReady === true ? '' : (value.workspaceId || '')))
};
}
function saveConfig() {
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
}
function disableLegacyCloud() {
try {
const raw = localStorage.getItem('sunEnterpriseSettingsV1');
if (!raw) return;
const data = JSON.parse(raw);
if (!data || typeof data !== 'object') return;
data.cloud = Object.assign({}, data.cloud || {}, {enabled:false, autoSync:false, lastSync:data.cloud?.lastSync || ''});
localStorage.setItem('sunEnterpriseSettingsV1', JSON.stringify(data));
} catch (_) {}
}
function clientId() {
let id = localStorage.getItem(CLIENT_ID_KEY);
if (!id) {
id = crypto.randomUUID ? crypto.randomUUID() : `client-${Date.now()}-${Math.random().toString(36).slice(2)}`;
localStorage.setItem(CLIENT_ID_KEY, id);
}
return id;
}
function workspaceMigrated() {
return Boolean(workspace?.id && config.migrated?.[workspace.id]);
}
const ROLE_LABELS = {admin:'Администратор',manager:'Менеджер',kitchen:'Кухня',courier:'Курьер',viewer:'Только просмотр'};
function hasPermission(permission) {
if (!workspace || workspace.is_active === false) return false;
const value=workspace.permissions?.[permission];
if (typeof value === 'boolean') return value;
return workspace.role === 'admin';
}
function canWrite() {
if (supportMode) return false;
if (!workspace || workspace.is_active === false) return false;
const permissions=workspace.permissions || {};
const entries=Object.entries(permissions);
if (entries.length) return entries.some(([key,value]) => value === true && (key.endsWith('.edit') || key.endsWith('.create') || key.endsWith('.delete') || key === 'users.manage' || key === 'backups.manage'));
return workspace.role === 'admin';
}
function roleLabel(role) {
return ROLE_LABELS[role] || role || '—';
}
function shouldSyncKey(key) {
key = String(key || '');
if (!key.startsWith('sun')) return false;
if (key === CONFIG_KEY || key === CLIENT_ID_KEY) return false;
if (key === 'sunUsersV1' || key === 'sunSessionUserV1') return false;
if (key === 'sunFallbackBackupsV1') return false;
if (key === 'sunStaticMapGeocodeCacheV2') return false;
if (key === 'sunCloudSyncMetaV1' || key === 'sunCloudSyncTokenV1' || key === 'sunCloudSyncWorkspaceV1') return false;
if (key === 'sunOrdersView' || key === 'sunCalendarMode' || key === 'sunCatalogViewModeV1' || key === 'sunNewOrderSplitRatioV5') return false;
if (key.startsWith('sunRecoveryCorrupt.')) return false;
return true;
}
function sanitizeEnterpriseSettings(raw) {
try {
const data = JSON.parse(raw);
if (!data || typeof data !== 'object') return raw;
data.rolesEnabled = false;
if (data.cloud) data.cloud = {enabled:false,supabaseUrl:'',anonKey:'',workspaceId:'',autoSync:false,lastSync:''};
return JSON.stringify(data);
} catch (_) { return raw; }
}
function openDb() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains('meta')) db.createObjectStore('meta', {keyPath:'key'});
if (!db.objectStoreNames.contains('media')) db.createObjectStore('media', {keyPath:'path'});
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function idbGet(store, key) {
try {
const db = await openDb();
return await new Promise((resolve, reject) => {
const request = db.transaction(store, 'readonly').objectStore(store).get(key);
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
} catch (_) { return null; }
}
async function idbPut(store, value) {
try {
const db = await openDb();
await new Promise((resolve, reject) => {
const tx = db.transaction(store, 'readwrite');
tx.objectStore(store).put(value);
tx.oncomplete = resolve;
tx.onerror = () => reject(tx.error);
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
});
return true;
} catch (_) { return false; }
}
// Keep the browser working copy isolated by cloud workspace. The live app still
// reads the historical unprefixed sun* keys, so before changing workspace we
// snapshot those keys to IndexedDB, clear them, and restore only the target
// workspace. This prevents orders/clients/finance/etc. from leaking between
// accounts while avoiding a second large copy inside localStorage.
function isTenantLocalKey(key) {
key=String(key||'');
if(!key.startsWith('sun'))return false;
if(key===CONFIG_KEY||key===CLIENT_ID_KEY)return false;
if(key.startsWith('sunCloud'))return false;
if(key==='sunStaticMapGeocodeCacheV2')return false;
return true;
}
function tenantLocalKeys() {
const out=[];
for(let i=0;i<localStorage.length;i++){
const key=localStorage.key(i);
if(key&&isTenantLocalKey(key))out.push(key);
}
return out;
}
async function captureTenantLocal(workspaceId) {
const id=String(workspaceId||'').trim();
if(!id)return false;
const storage={};
for(const key of tenantLocalKeys())storage[key]=localStorage.getItem(key)??'';
return await idbPut('meta',{key:`tenant-local:${id}`,value:{workspaceId:id,capturedAt:new Date().toISOString(),storage}});
}
function clearTenantLocal() {
tenantLocalKeys().forEach(key=>localStorage.removeItem(key));
}
async function restoreTenantLocal(workspaceId) {
const id=String(workspaceId||'').trim();
if(!id)return false;
const row=await idbGet('meta',`tenant-local:${id}`),storage=row?.value?.storage;
if(!storage||typeof storage!=='object')return false;
for(const [key,value] of Object.entries(storage)){
if(isTenantLocalKey(key))localStorage.setItem(key,String(value??''));
}
return true;
}
async function switchTenantLocal(nextWorkspaceId) {
const next=String(nextWorkspaceId||'').trim();
// One-time upgrade adoption. If the old config already named a workspace,
// current local data belongs to that legacy workspace only. This prevents a
// signed-out old database from being accidentally attached to a different
// account on the first login after upgrading.
if(!config.tenantStorageReady){
const legacy=String(config.legacyLocalWorkspaceId||'').trim();
if(legacy&&legacy!==next){
await captureTenantLocal(legacy);
clearTenantLocal();
config.tenantStorageReady=true;
config.localWorkspaceId=next;
config.legacyLocalWorkspaceId='';
let restored=false;
if(next)restored=await restoreTenantLocal(next);
if(next&&!restored)config.migrated[next]=false;
saveConfig();
return {changed:true,restored,legacyCaptured:true};
}
if(next){
config.tenantStorageReady=true;
config.localWorkspaceId=next;
config.legacyLocalWorkspaceId='';
saveConfig();
await captureTenantLocal(next);
return {changed:false,restored:true,adopted:true};
}
// A purely local pre-cloud installation has no workspace id yet. Keep it
// untouched until the first company is selected, so migration stays safe.
return {changed:false,restored:false};
}
const prev=String(config.localWorkspaceId||'').trim();
if(prev===next)return {changed:false,restored:Boolean(next)};
if(prev)await captureTenantLocal(prev);
clearTenantLocal();
config.localWorkspaceId=next;
let restored=false;
if(next)restored=await restoreTenantLocal(next);
// If this device has no saved working copy for the target workspace, force a
// clean cloud pull on reload instead of merging an empty browser state.
if(next&&!restored)config.migrated[next]=false;
saveConfig();
return {changed:true,restored};
}
async function adoptCurrentLocalAsWorkspace(workspaceId) {
const id=String(workspaceId||'').trim();
if(!id)return false;
config.tenantStorageReady=true;
config.localWorkspaceId=id;
config.legacyLocalWorkspaceId='';
config.workspaceId=id;
config.migrated[id]=false;
saveConfig();
await captureTenantLocal(id);
return true;
}
function seedOfficialCatalogForFreshWorkspace() {
clearTenantLocal();
let seed=[];
try{if(Array.isArray(window.SUN_OFFICIAL_CATALOG))seed=clone(window.SUN_OFFICIAL_CATALOG)}catch(_){}
localStorage.setItem('sunBoxes',JSON.stringify(Array.isArray(seed)?seed:[]));
localStorage.setItem('sunOrders','[]');
if(window.SUN_OFFICIAL_CATALOG_VERSION)localStorage.setItem('sunOfficialCatalogVersion',String(window.SUN_OFFICIAL_CATALOG_VERSION));
}
let signOutInProgress=false;
function clearLocalSupabaseSessionFallback() {
// Supabase normally clears its own persisted auth token. If the network call
// is blocked or hangs, remove only this project's auth keys so the user can
// still leave the account on the current device without touching app data.
let projectRef='';
try{projectRef=new URL(config.url).hostname.split('.')[0]||'';}catch(_){}
const prefixes=projectRef?[`sb-${projectRef}-auth-token`]:[];
for(const storage of [localStorage,sessionStorage]){
try{
const remove=[];
for(let i=0;i<storage.length;i++){
const key=storage.key(i)||'';
if(prefixes.some(prefix=>key===prefix||key.startsWith(prefix+'.')||key.startsWith(prefix+'-')))remove.push(key);
}
remove.forEach(key=>storage.removeItem(key));
}catch(_){}
}
}
function setSignOutUiBusy(busy) {
const selectors=['#sunCloudSignOutV2','#sunLogoutBtn','#sunGateSignOutV3','#sunCloudUsePasswordV3','[data-signout]','[data-saas-signout]'];
document.querySelectorAll(selectors.join(',')).forEach(btn=>{
if(!(btn instanceof HTMLButtonElement))return;
if(busy){btn.dataset.sunOldText=btn.textContent||'';btn.disabled=true;btn.textContent='Выходим…';}
else{btn.disabled=false;if(btn.dataset.sunOldText){btn.textContent=btn.dataset.sunOldText;delete btn.dataset.sunOldText;}}
});
}
async function signOutAndReload() {
if(signOutInProgress)return;
signOutInProgress=true;
setSignOutUiBusy(true);
clearTimeout(syncTimer);
setStatus('syncing','Выходим из аккаунта…');
try{
// Preserve the company working copy first. Do not block logout on a cloud
// sync: the workspace snapshot remains available and will sync after the
// next login if there were unsent changes.
const snapshot=Promise.resolve().then(async()=>{if(supportMode){clearTenantLocal();supportMode=null;config.localWorkspaceId='';saveConfig();return {changed:true};}return switchTenantLocal('');});
await Promise.race([snapshot,sleep(2200).then(()=>{throw new Error('Tenant snapshot timeout');})]);
}catch(error){
console.warn('[SunCloud] tenant snapshot before sign-out failed',error);
try{clearTenantLocal();config.localWorkspaceId='';saveConfig();}catch(_){}
}
try{
if(client?.auth){
const result=await Promise.race([
client.auth.signOut({scope:'local'}),
sleep(2500).then(()=>{throw new Error('Supabase local sign-out timeout');})
]);
if(result?.error)throw result.error;
}else clearLocalSupabaseSessionFallback();
}catch(error){
console.warn('[SunCloud] Supabase sign-out fallback',error);
try{client?.auth.stopAutoRefresh?.();}catch(_){}
clearLocalSupabaseSessionFallback();
}
session=null;workspace=null;memberships=[];membershipsLoading=false;membershipsLoaded=true;unsubscribeRealtime();
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed'));}catch(_){}
// Reload regardless of network state so a blocked logout endpoint can never
// leave the user trapped in the previous account screen.
location.reload();
}
async function getBaseline() {
if (!workspace?.id) return null;
const row = await idbGet('meta', `baseline:${workspace.id}`);
return row?.value || null;
}
async function setBaseline(row) {
if (!workspace?.id || !row) return;
await idbPut('meta', {key:`baseline:${workspace.id}`, value:{payload:clone(row.payload),revision:Number(row.revision || 0),updated_at:row.updated_at || '',client_id:row.client_id || ''}});
}
function stable(value) {
if (value === undefined) return '__undefined__';
if (value === null || typeof value !== 'object') return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(stable).join(',')}]`;
return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${stable(value[k])}`).join(',')}}`;
}
function equal(a, b) { return stable(a) === stable(b); }
function isPlainObject(value) {
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function idArray(value) {
return Array.isArray(value) && value.every(item => isPlainObject(item) && item.id != null);
}
function mergeById(base, local, remote, conflicts, path) {
const bm = new Map((base || []).map(x => [String(x.id), x]));
const lm = new Map((local || []).map(x => [String(x.id), x]));
const rm = new Map((remote || []).map(x => [String(x.id), x]));
const order = [];
[...(remote || []), ...(local || []), ...(base || [])].forEach(x => { const id=String(x.id); if (!order.includes(id)) order.push(id); });
const out = [];
for (const id of order) {
const b = bm.get(id), l = lm.get(id), r = rm.get(id);
if (l === undefined && r === undefined) continue;
if (l === undefined) {
if (b !== undefined && !equal(r,b)) conflicts.push(`${path}[${id}]: удалено локально, изменено в облаке`);
if (b === undefined || !equal(r,b)) out.push(clone(r));
continue;
}
if (r === undefined) {
if (b !== undefined && !equal(l,b)) { conflicts.push(`${path}[${id}]: изменено локально, удалено в облаке`); out.push(clone(l)); }
else if (b === undefined) out.push(clone(l));
continue;
}
out.push(mergeNode(b, l, r, conflicts, `${path}[${id}]`));
}
return out;
}
function mergeNode(base, local, remote, conflicts, path='state') {
if (equal(local, remote)) return clone(local);
if (equal(local, base)) return clone(remote);
if (equal(remote, base)) return clone(local);
if (idArray(local) && idArray(remote) && (!base || idArray(base))) {
return mergeById(Array.isArray(base)?base:[], local, remote, conflicts, path);
}
if (isPlainObject(local) && isPlainObject(remote) && (base == null || isPlainObject(base))) {
const out = {};
const keys = new Set([...Object.keys(base || {}), ...Object.keys(local), ...Object.keys(remote)]);
for (const key of keys) {
const b = base?.[key], l = local[key], r = remote[key];
if (!(key in local) && !(key in remote)) continue;
if (!(key in local)) {
if (b !== undefined && !equal(r,b)) conflicts.push(`${path}.${key}: удалено локально, изменено в облаке`);
if (b === undefined || !equal(r,b)) out[key] = clone(r);
continue;
}
if (!(key in remote)) {
if (b !== undefined && !equal(l,b)) { conflicts.push(`${path}.${key}: изменено локально, удалено в облаке`); out[key]=clone(l); }
else if (b === undefined) out[key]=clone(l);
continue;
}
out[key] = mergeNode(b, l, r, conflicts, `${path}.${key}`);
}
return out;
}
conflicts.push(path);
return clone(local);
}
function mergePayloads(basePayload, localPayload, remotePayload) {
const conflicts = [];
const base = basePayload?.storage || {};
const local = localPayload?.storage || {};
const remote = remotePayload?.storage || {};
const merged = {};
const keys = new Set([...Object.keys(base), ...Object.keys(local), ...Object.keys(remote)]);
for (const key of keys) {
const b = base[key], l = local[key], r = remote[key];
if (l === undefined && r === undefined) continue;
if (l === undefined) {
if (b !== undefined && !equal(r,b)) conflicts.push(key);
if (b === undefined || !equal(r,b)) merged[key] = clone(r);
continue;
}
if (r === undefined) {
if (b !== undefined && !equal(l,b)) { conflicts.push(key); merged[key] = clone(l); }
else if (b === undefined) merged[key] = clone(l);
continue;
}
merged[key] = mergeNode(b, l, r, conflicts, key);
}
return {payload:{format:'sun-cloud-v2',version:2,storage:merged}, conflicts:[...new Set(conflicts)]};
}
function dataUrlToBlob(dataUrl) {
const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUrl || '');
if (!match) throw new Error('Invalid data URL');
const mime = match[1] || 'application/octet-stream';
const binary = match[2] ? atob(match[3]) : decodeURIComponent(match[3]);
const bytes = new Uint8Array(binary.length);
for (let i=0;i<binary.length;i++) bytes[i] = binary.charCodeAt(i);
return new Blob([bytes], {type:mime});
}
function blobToDataUrl(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ''));
reader.onerror = () => reject(reader.error || new Error('FileReader error'));
reader.readAsDataURL(blob);
});
}
async function shrinkImageIfNeeded(dataUrl, blob) {
if (blob.size <= 12 * 1024 * 1024) return blob;
const image = new Image();
image.src = dataUrl;
await new Promise((resolve, reject) => { image.onload=resolve; image.onerror=reject; });
const max = 2400;
const scale = Math.min(1, max / Math.max(image.naturalWidth || 1, image.naturalHeight || 1));
const canvas = document.createElement('canvas');
canvas.width = Math.max(1, Math.round((image.naturalWidth || 1) * scale));
canvas.height = Math.max(1, Math.round((image.naturalHeight || 1) * scale));
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.drawImage(image,0,0,canvas.width,canvas.height);
return await new Promise(resolve => canvas.toBlob(value => resolve(value || blob), 'image/jpeg', .9));
}
async function sha256(blob) {
const digest = await crypto.subtle.digest('SHA-256', await blob.arrayBuffer());
return [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2,'0')).join('');
}
function extensionFor(type) {
return ({'image/jpeg':'jpg','image/png':'png','image/webp':'webp','image/gif':'gif'})[String(type||'').toLowerCase()] || 'bin';
}
async function uploadDataUrl(dataUrl) {
if (!client || !workspace?.id) return dataUrl;
let blob = dataUrlToBlob(dataUrl);
blob = await shrinkImageIfNeeded(dataUrl, blob);
const hash = await sha256(blob);
const ext = extensionFor(blob.type);
const path = `${workspace.id}/media/${hash.slice(0,2)}/${hash}.${ext}`;
const {error} = await client.storage.from(BUCKET).upload(path, blob, {upsert:false,contentType:blob.type || 'application/octet-stream',cacheControl:'31536000'});
if (error && !/exist|duplicate|409/i.test(`${error.message || ''} ${error.statusCode || ''}`)) throw error;
await idbPut('media', {path, blob, cachedAt:new Date().toISOString()});
return MEDIA_PREFIX + path;
}
async function encodeMedia(value) {
if (typeof value === 'string') {
if (/^data:image\//i.test(value)) return await uploadDataUrl(value);
return value;
}
if (Array.isArray(value)) {
const out = [];
for (const item of value) out.push(await encodeMedia(item));
return out;
}
if (isPlainObject(value)) {
const out = {};
for (const [key,item] of Object.entries(value)) out[key] = await encodeMedia(item);
return out;
}
return value;
}
async function downloadMedia(path) {
const cached = await idbGet('media', path);
if (cached?.blob) return await blobToDataUrl(cached.blob);
const {data, error} = await client.storage.from(BUCKET).download(path);
if (error) throw error;
await idbPut('media', {path, blob:data, cachedAt:new Date().toISOString()});
return await blobToDataUrl(data);
}
async function decodeMedia(value) {
if (typeof value === 'string' && value.startsWith(MEDIA_PREFIX)) return await downloadMedia(value.slice(MEDIA_PREFIX.length));
if (Array.isArray(value)) {
const out=[]; for (const item of value) out.push(await decodeMedia(item)); return out;
}
if (isPlainObject(value)) {
const out={}; for (const [key,item] of Object.entries(value)) out[key]=await decodeMedia(item); return out;
}
return value;
}
function quotaExceeded(error) {return error?.name==='QuotaExceededError'||/quota|exceeded/i.test(String(error?.message||error||''));}
function compactLocalImageData(dataUrl,{maxSide=900,maxChars=130000,quality=.78,force=false}={}) {
return new Promise(resolve => {
if (!/^data:image\//i.test(String(dataUrl || ''))) return resolve(dataUrl);
const image = new Image();
image.onerror = () => resolve(dataUrl);
image.onload = () => {
const iw=Math.max(1,image.naturalWidth||image.width||1), ih=Math.max(1,image.naturalHeight||image.height||1);
if (!force && String(dataUrl).length<=maxChars && Math.max(iw,ih)<=maxSide) return resolve(dataUrl);
let scale=Math.min(1,maxSide/Math.max(iw,ih)), q=quality, best=dataUrl;
for (let attempt=0; attempt<5; attempt++) {
const canvas=document.createElement('canvas');
canvas.width=Math.max(1,Math.round(iw*scale)); canvas.height=Math.max(1,Math.round(ih*scale));
const ctx=canvas.getContext('2d'); if(!ctx) break;
ctx.fillStyle='#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.drawImage(image,0,0,canvas.width,canvas.height);
try { best=canvas.toDataURL('image/jpeg',q)||best; } catch (_) { break; }
if (best.length<=maxChars) break;
scale*=.78; q=Math.max(.52,q-.07);
}
resolve(best);
};
image.src=dataUrl;
});
}
async function compactSunBoxesForStorage(value, options={}) {
if (!Array.isArray(value)) return value;
const out=[];
for (const item of value) {
if (!isPlainObject(item)) { out.push(item); continue; }
const copy={...item};
if (typeof copy.photo==='string' && /^data:image\//i.test(copy.photo)) copy.photo=await compactLocalImageData(copy.photo,options);
out.push(copy);
}
return out;
}
async function collectLocalPayload() {
const storage = {};
for (let i=0;i<localStorage.length;i++) {
const key = localStorage.key(i);
if (!key || !shouldSyncKey(key)) continue;
let raw = localStorage.getItem(key) ?? '';
if (key === 'sunEnterpriseSettingsV1') raw = sanitizeEnterpriseSettings(raw);
try {
const parsed = JSON.parse(raw);
storage[key] = {t:'j',v:await encodeMedia(parsed)};
} catch (_) {
storage[key] = {t:'s',v:await encodeMedia(raw)};
}
}
return {format:'sun-cloud-v2',version:2,storage};
}
function currentSyncableKeys() {
const out=[];
for(let i=0;i<localStorage.length;i++){
const key=localStorage.key(i); if(key&&shouldSyncKey(key)) out.push(key);
}
return out;
}
async function applyPayload(payload, replace=false) {
if (!payload?.storage || typeof payload.storage !== 'object') throw new Error('Некорректный формат облачной базы.');
suppressStorageTracking = true;
try {
if (replace) {
const remoteKeys = new Set(Object.keys(payload.storage));
currentSyncableKeys().forEach(key => { if (!remoteKeys.has(key)) localStorage.removeItem(key); });
}
for (const [key,entry] of Object.entries(payload.storage)) {
if (!shouldSyncKey(key) || !entry) continue;
let decoded = await decodeMedia(entry.v);
if (key === 'sunBoxes' && entry.t === 'j') decoded = await compactSunBoxesForStorage(decoded,{maxSide:900,maxChars:130000,quality:.78});
let raw = entry.t === 'j' ? JSON.stringify(decoded) : String(decoded ?? '');
try {
localStorage.setItem(key, raw);
} catch (error) {
if (key !== 'sunBoxes' || entry.t !== 'j' || !quotaExceeded(error)) throw error;
decoded = await compactSunBoxesForStorage(decoded,{maxSide:640,maxChars:80000,quality:.66,force:true});
raw = JSON.stringify(decoded);
try { localStorage.setItem(key, raw); }
catch (retryError) {
if (quotaExceeded(retryError)) throw new Error('Каталог с фотографиями все еще превышает локальный лимит браузера даже после оптимизации фото.');
throw retryError;
}
toast('Фотографии каталога оптимизированы, чтобы не превышать лимит браузера.','info',5200);
}
}
} finally {
suppressStorageTracking = false;
}
refreshRuntime();
}
function refreshRuntime() {
try { if (typeof boxes !== 'undefined') boxes = JSON.parse(localStorage.getItem('sunBoxes') || '[]'); } catch (_) {}
try { if (typeof orders !== 'undefined') orders = JSON.parse(localStorage.getItem('sunOrders') || '[]'); } catch (_) {}
for (const name of ['render','renderOrders','renderStats','renderClients','renderStock','renderManager']) {
try { if (typeof window[name] === 'function') window[name](); } catch (_) {}
}
try { window.dispatchEvent(new CustomEvent('sun:cloud-state-applied')); } catch (_) {}
}
function payloadEmpty(payload) {
return !payload?.storage || Object.keys(payload.storage).length === 0;
}
async function fetchRemoteRow() {
if (!client || !workspace?.id) return null;
const {data,error} = await sunCloudAwait(client.rpc('sun_fetch_app_state',{p_workspace:workspace.id}),'Загрузка облачной базы');
if (error) throw error;
return Array.isArray(data) ? (data[0] || null) : (data || null);
}
async function upsertPayload(payload, expectedRevision=null) {
const args={p_workspace:workspace.id,p_payload:payload,p_client_id:clientId(),p_expected_revision:expectedRevision==null?null:Number(expectedRevision)};
const {data,error} = await sunCloudAwait(client.rpc('sun_save_app_state_v17',args),'Сохранение облачной базы');
if (error) throw error;
return Array.isArray(data) ? data[0] : data;
}
async function backupBeforeCloud(reason) {
try { await window.SunEnterprise?.createBackup?.(reason); } catch (_) {}
}
async function migrateLocalToCloud() {
if (!canWrite()) return toast('Для переноса данных нужны права на изменение рабочей базы.','warn');
if (!navigator.onLine) return toast('Нет подключения к интернету. Локальные данные сохранены и никуда не пропадут.','warn');
setStatus('syncing','Создаю первую облачную копию…');
try {
await backupBeforeCloud('Перед первым переносом в Supabase');
const remote = await fetchRemoteRow();
if (remote && !payloadEmpty(remote.payload)) {
if (!confirm('В этой рабочей базе уже есть облачные данные. Заменить их текущими данными с этого устройства?')) {
setStatus('ready','Перенос отменён.'); return;
}
}
const localPayload = await collectLocalPayload();
const row = await upsertPayload(localPayload, remote?.revision ?? null);
await setBaseline(row);
config.migrated[workspace.id] = true;
config.lastSync = row.updated_at || new Date().toISOString();
config.autoSync = true;
saveConfig(); dirty=false;
setStatus('ready','Все текущие данные перенесены в облако.');
toast('Данные и загруженные фотографии перенесены в Supabase. Автосинхронизация включена.','success',6500);
subscribeRealtime();
try{window.dispatchEvent(new CustomEvent('sun:cloud-sync-complete',{detail:{row:clone(row),payload:clone(localPayload),kind:'migrate'}}));}catch(_){}
renderCloudUI();
} catch (error) { handleError(error, 'Не удалось перенести данные в облако.'); }
}
async function pullRemote({replace=false,quiet=false}={}) {
if (!client || !workspace?.id) return;
setStatus('syncing','Загружаю облачные данные…');
try {
const row = await fetchRemoteRow();
if (!row || payloadEmpty(row.payload)) {
setStatus('ready','В облаке пока нет данных.'); return;
}
if (!quiet) await backupBeforeCloud('Перед загрузкой данных из Supabase');
await applyPayload(row.payload, replace);
await setBaseline(row);
config.migrated[workspace.id] = true;
config.lastSync = row.updated_at || new Date().toISOString();
saveConfig(); dirty=false;
setStatus('ready','Облачные данные загружены.');
if (!quiet) toast('Данные загружены из облака.','success');
try{window.dispatchEvent(new CustomEvent('sun:cloud-sync-complete',{detail:{row:clone(row),payload:clone(row.payload),kind:'pull'}}));}catch(_){}
renderCloudUI();
} catch (error) { handleError(error,'Не удалось загрузить облачные данные.'); }
}
async function syncNow({quiet=false,retryCount=0}={}) {
if (isSyncing || !client || !session || !workspace?.id || !workspaceMigrated()) return;
if (!navigator.onLine) { setStatus('offline','Офлайн — изменения ждут подключения.'); return; }
isSyncing = true;
setStatus('syncing','Синхронизация…');
try {
const [baseline, remote, localPayload] = await Promise.all([getBaseline(), fetchRemoteRow(), collectLocalPayload()]);
if (!remote || payloadEmpty(remote.payload)) {
if (!canWrite()) throw new Error('В облаке нет базы, а у пользователя нет прав на её создание.');
const row = await upsertPayload(localPayload, remote?.revision ?? null);
await setBaseline(row); dirty=false; config.lastSync=row.updated_at||new Date().toISOString();saveConfig();
setStatus('ready','Синхронизировано.'); if(!quiet)toast('Синхронизация завершена.','success'); return;
}
if (!baseline) {
await applyPayload(remote.payload, true);
await setBaseline(remote); dirty=false; config.lastSync=remote.updated_at||new Date().toISOString();saveConfig();
setStatus('ready','Подключена существующая облачная база.'); if(!quiet)toast('Загружена актуальная облачная база.','success'); return;
}
const merged = mergePayloads(baseline.payload, localPayload, remote.payload);
let finalRow = remote;
const changedVsRemote = !equal(merged.payload, remote.payload);
const changedVsLocal = !equal(merged.payload, localPayload);
if (changedVsRemote) {
if (!canWrite()) {
await applyPayload(remote.payload, true);
finalRow = remote;
} else {
finalRow = await upsertPayload(merged.payload, remote.revision);
if (changedVsLocal) await applyPayload(merged.payload, true);
}
} else if (changedVsLocal) {
await applyPayload(remote.payload, true);
}
await setBaseline(finalRow);
dirty=false;
config.lastSync=finalRow.updated_at||new Date().toISOString();saveConfig();
setStatus('ready', merged.conflicts.length ? `Синхронизировано, объединено конфликтов: ${merged.conflicts.length}.` : 'Синхронизировано.');
if (merged.conflicts.length) toast(`Изменения с двух устройств объединены. Проверено конфликтов: ${merged.conflicts.length}.`,'warn',6500);
else if (!quiet) toast('Синхронизация завершена.','success');
try{window.dispatchEvent(new CustomEvent('sun:cloud-sync-complete',{detail:{row:clone(finalRow),payload:clone(finalRow.payload),kind:'sync',conflicts:merged.conflicts.length}}));}catch(_){}
window.SunStabilityV17?.markSynced?.(finalRow?.revision);
renderCloudUI();
} catch (error) {
const msg=String(error?.message||'');
if(msg.includes('SUN_CONFLICT')){
const nextRetry=Number(retryCount||0)+1;
try{window.SunStabilityV17?.recordConflict?.(msg);}catch(_){}
if(nextRetry>CLOUD_CONFLICT_MAX_RETRIES){handleError(error,'Не удалось синхронизировать после нескольких безопасных повторов.');}
else{const delay=Math.min(5000,400*Math.pow(2,nextRetry-1));setStatus('pending','Облачная версия изменилась. Повтор '+nextRetry+'/'+CLOUD_CONFLICT_MAX_RETRIES+' через '+(Math.round(delay/100)/10)+' сек.…');setTimeout(()=>syncNow({quiet:true,retryCount:nextRetry}),delay);}
}else handleError(error,'Ошибка синхронизации.');
}
finally { isSyncing=false; }
}
async function handleRealtimeRow(row) {
if (!row || row.client_id === clientId() || isSyncing || !workspaceMigrated()) return;
try {
const baseline = await getBaseline();
if (!baseline) { await applyPayload(row.payload,true);await setBaseline(row);setStatus('ready','Получены данные с другого устройства.');return; }
const localPayload = await collectLocalPayload();
const merged = mergePayloads(baseline.payload, localPayload, row.payload);
await applyPayload(merged.payload, true);
if (!equal(merged.payload,row.payload) && canWrite()) {
const saved=await upsertPayload(merged.payload,row.revision);await setBaseline(saved);
} else await setBaseline(row);
dirty=false;config.lastSync=row.updated_at||new Date().toISOString();saveConfig();
setStatus('ready','Получены изменения с другого устройства.');
toast(merged.conflicts.length?'Получены облачные изменения; параллельные правки объединены.':'Данные обновлены с другого устройства.','success',3500);
renderCloudUI();
} catch (error) { handleError(error,'Не удалось применить изменения с другого устройства.'); }
}
function scheduleSync(reason='change') {
dirty = true;
try{window.SunStabilityV17?.markPending?.(reason);}catch(_){}
if (!config.autoSync || !workspaceMigrated() || !session || !workspace || !canWrite()) {
updatePill(); return;
}
clearTimeout(syncTimer);
syncTimer = setTimeout(() => syncNow({quiet:true}), SYNC_DEBOUNCE);
if (!navigator.onLine) setStatus('offline','Офлайн — изменения сохранены локально.');
else setStatus('pending',`Изменения ожидают синхронизации (${reason}).`);
}
function storageSignature() {
const parts=[];
for(let i=0;i<localStorage.length;i++){
const key=localStorage.key(i); if(!key||!shouldSyncKey(key))continue;
const value=localStorage.getItem(key)||'';
let h=2166136261;
const step=Math.max(1,Math.floor(value.length/64));
for(let j=0;j<value.length;j+=step){h^=value.charCodeAt(j);h=Math.imul(h,16777619);}
parts.push(`${key}:${value.length}:${h>>>0}`);
}
return parts.sort().join('|');
}
function installStorageTracking() {
if (Storage.prototype.__sunCloudV2Tracked) return;
const nativeSet = Storage.prototype.setItem;
const nativeRemove = Storage.prototype.removeItem;
Object.defineProperty(Storage.prototype,'__sunCloudV2Tracked',{value:true,configurable:true});
Storage.prototype.setItem=function(key,value){
const old = this===localStorage ? this.getItem(key) : null;
const result = nativeSet.call(this,key,value);
if(this===localStorage&&!suppressStorageTracking&&shouldSyncKey(key)&&old!==String(value))scheduleSync(key);
return result;
};
Storage.prototype.removeItem=function(key){
const had=this===localStorage&&this.getItem(key)!==null;
const result=nativeRemove.call(this,key);
if(this===localStorage&&!suppressStorageTracking&&had&&shouldSyncKey(key))scheduleSync(key);
return result;
};
observedSignature=storageSignature();
setInterval(()=>{
if(document.hidden||suppressStorageTracking)return;
const next=storageSignature();
if(next!==observedSignature){observedSignature=next;scheduleSync('данные приложения');}
},9000);
['sun:catalog-categories-changed','sun:client-offer-catalog-changed','sun:cloud-dirty'].forEach(name=>window.addEventListener(name,()=>scheduleSync(name)));
}
function setStatus(kind, text='') {
lastStatus=kind;lastError=kind==='error'?text:'';
const el=$('sunCloudV2Status');
if(el){el.textContent=text||statusText();el.dataset.kind=kind;}
updatePill();
}
function statusText() {
if(lastStatus==='error')return lastError||'Ошибка облака';
if(!config.url||!config.key)return 'Облако не подключено';
if(!navigator.onLine)return 'Офлайн — работаем с локальной копией';
if(!session)return 'Нужно войти в облачный аккаунт';
if(supportMode)return `Режим поддержки · ${supportMode.name||'Компания'} · только просмотр`;
if(!workspace)return 'Нужно выбрать или создать рабочую базу';
if(!workspaceMigrated())return 'Нужен первый перенос данных в облако';
if(lastStatus==='syncing')return 'Синхронизация…';
if(dirty)return 'Есть несинхронизированные изменения';
return config.lastSync?`Синхронизировано ${new Date(config.lastSync).toLocaleString('ru-RU')}`:'Облако готово';
}
// Облачный статус больше не висит поверх всех разделов.
// Управление облаком доступно только в «Настройки → Облако Supabase».
function ensurePill() {
const pill=$('sunCloudPillV2');
if(pill) pill.remove();
return null;
}
function updatePill() {
ensurePill();
}
function injectStyles() {
if($('sunCloudV2Styles'))return;
const style=document.createElement('style');style.id='sunCloudV2Styles';style.textContent=`
.sun-cloud-v2-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.sun-cloud-v2-grid .wide{grid-column:1/-1}.sun-cloud-v2-box{border:1px solid #e0e4e6;border-radius:9px;padding:12px;background:#fbfcfc}.sun-cloud-v2-box h3{margin:0 0 8px;color:#15364c;font-size:14px}.sun-cloud-v2-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.sun-cloud-v2-status{margin-top:10px;border-radius:8px;padding:9px 10px;background:#edf4f6;color:#365161;font-size:12px}.sun-cloud-v2-status[data-kind="error"]{background:#fff0ee;color:#943d3d}.sun-cloud-v2-status[data-kind="ready"]{background:#eef8f2;color:#347154}.sun-cloud-v2-status[data-kind="offline"]{background:#fff7e6;color:#8b6b25}.sun-cloud-v2-note{font-size:12px;color:#68717a;margin:6px 0}.sun-cloud-v2-user{font-weight:800;color:#15364c}.sun-cloud-v2-code{font:700 13px/1.2 ui-monospace,monospace;background:#f2f4f5;border-radius:7px;padding:8px;word-break:break-all}.sun-cloud-v2-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.sun-cloud-v2-row label{flex:1;min-width:170px}.sun-cloud-v2-small{font-size:11px;color:#7b8388}
@media(max-width:760px){.sun-cloud-v2-grid{grid-template-columns:1fr}.sun-cloud-v2-grid .wide{grid-column:auto}}
`;document.head.appendChild(style);
}
function findCloudCard() {
const cards=[...document.querySelectorAll('#enterprise-settings .enterprise-card')];
return cards.find(card=>(card.querySelector('h2')?.textContent||'').trim()==='Облачная синхронизация') || $('sunCloudV2Card');
}
function ensureCloudCard() {
const card=findCloudCard();if(!card)return null;
if(card.id!=='sunCloudV2Card'){card.id='sunCloudV2Card';card.classList.add('wide');renderCloudUI();}
return card;
}
function renderCloudUI() {
const card=findCloudCard();if(!card)return;
card.id='sunCloudV2Card';card.classList.add('wide');
const connected=Boolean(config.url&&config.key);
const signed=Boolean(session?.user);
const selected=Boolean(workspace);
card.innerHTML=`<h2>Профиль и аккаунт</h2><p class="hint">Вход, выход, рабочая компания и пользователи компании.</p>
<div class="sun-cloud-v2-grid">
${connected&&!signed?`<div class="sun-cloud-v2-box wide"><h3>Войти в профиль</h3><div class="sun-cloud-v2-row"><label>Email<input id="sunCloudEmailV2" type="email" autocomplete="username"></label><label>Пароль<input id="sunCloudPasswordV2" type="password" autocomplete="current-password"></label></div><div class="sun-cloud-v2-actions"><button class="primary" id="sunCloudSignInV2" type="button">Войти</button><button class="outline" id="sunCloudSignUpV2" type="button">Зарегистрироваться</button></div></div>`:''}
${!connected?`<div class="sun-cloud-v2-box wide"><h3>Подключение недоступно</h3><p class="sun-cloud-v2-note">Техническое подключение настраивается только из кабинета разработчика.</p></div>`:''}
${signed?`<div class="sun-cloud-v2-box wide"><h3>Профиль</h3><div class="sun-cloud-v2-row"><span class="sun-cloud-v2-user">${esc(session.user.email||session.user.id)}</span><button class="outline" id="sunCloudSignOutV2" type="button">Выйти</button></div></div>`:''}
${signed?`<div class="sun-cloud-v2-box wide"><h3>Пароль</h3><div class="sun-cloud-v2-row"><label>Новый пароль<input id="sunAccountNewPasswordV1" type="password" autocomplete="new-password" minlength="8"></label><label>Подтвердите пароль<input id="sunAccountNewPassword2V1" type="password" autocomplete="new-password" minlength="8"></label></div><div class="sun-cloud-v2-actions"><button class="outline" id="sunAccountChangePasswordV1" type="button">Изменить пароль</button></div></div>`:''}
${signed&&!selected?`<div class="sun-cloud-v2-box wide"><h3>Рабочая компания</h3><p class="sun-cloud-v2-note">${membershipsLoading?'Загружаю доступные рабочие базы…':'У этого аккаунта пока нет доступной рабочей базы.'}</p><div class="sun-cloud-v2-actions"><button class="outline" id="sunCloudReloadMembershipsV2" type="button">Проверить ещё раз</button><button class="primary" id="sunCloudCreateWorkspaceV2" type="button">Создать новую компанию</button></div></div>`:''}
${signed&&selected?`<div class="sun-cloud-v2-box wide"><h3>Рабочая компания</h3><div><b>${esc(workspace.name)}</b>${supportMode?'<span class="sun-cloud-v2-note" style="margin-left:8px">· поддержка, только просмотр</span>':''}</div><div class="sun-cloud-v2-note">Роль: ${supportMode?'Разработчик · поддержка':esc(roleLabel(workspace.role))}${workspace.display_name?` · ${esc(workspace.display_name)}`:''}</div>${!supportMode&&memberships.length>1?`<label>Компания<select id="sunCloudWorkspaceSelectV2">${memberships.map(m=>`<option value="${esc(m.id)}" ${m.id===workspace.id?'selected':''}>${esc(m.name)} · ${esc(roleLabel(m.role))}</option>`).join('')}</select></label>`:''}</div>`:''}
${signed&&selected&&!supportMode?`<div class="sun-cloud-v2-box wide"><h3>Синхронизация</h3><p class="sun-cloud-v2-note">Принудительно загрузить актуальные заказы, клиентов и остальные данные с сервера, заменив локальную копию на этом устройстве. Перед загрузкой автоматически создаётся резервная копия.</p><div class="sun-cloud-v2-actions"><button class="outline" id="sunCloudPullV2" type="button">Загрузить из облака</button></div></div>`:''}
${signed&&selected&&!supportMode&&hasPermission('users.manage')?`<div class="sun-cloud-v2-box wide"><h3>Пользователи</h3><p class="sun-cloud-v2-note">Управление сотрудниками и правами внутри этой компании.</p><div class="sun-cloud-v2-actions"><button class="primary" id="sunCloudManageUsersV3" type="button">Пользователи и права</button></div></div>`:''}
</div><div class="sun-cloud-v2-status" id="sunCloudV2Status" data-kind="${esc(lastStatus)}">${esc(statusText())}</div>`;
bindCloudUI();
}
function bindCloudUI() {
$('sunCloudConnectV2')?.addEventListener('click', async()=>{
config.url=String($('sunCloudUrlV2')?.value||'').trim().replace(/\/$/,'');config.key=String($('sunCloudKeyV2')?.value||'').trim();saveConfig();
if(!config.url||!config.key)return toast('Введите Project URL и Publishable key.','warn');
client=null;session=null;workspace=null;memberships=[];await connectFromConfig(false);renderCloudUI();
});
$('sunCloudSignInV2')?.addEventListener('click',()=>signIn(false));
$('sunCloudSignUpV2')?.addEventListener('click',()=>signIn(true));
$('sunCloudSignOutV2')?.addEventListener('click',signOutAndReload);
$('sunAccountChangePasswordV1')?.addEventListener('click',async()=>{
const p1i=$('sunAccountNewPasswordV1'),p2i=$('sunAccountNewPassword2V1');
const p1=String(p1i?.value||''),p2=String(p2i?.value||'');
if(p1.length<8)return toast('Новый пароль должен содержать минимум 8 символов.','warn');
if(p1!==p2)return toast('Пароли не совпадают.','warn');
if(!client)return toast('Облачный сервис не подключён.','error');
try{const r=await client.auth.updateUser({password:p1});if(r.error)throw r.error;if(p1i)p1i.value='';if(p2i)p2i.value='';toast('Пароль изменён.','success');}
catch(e){toast(String(e?.message||e||'Не удалось изменить пароль.'),'error');}
});
$('sunCloudCreateWorkspaceV2')?.addEventListener('click',createWorkspace);
$('sunCloudReloadMembershipsV2')?.addEventListener('click',async()=>{const tenantSwitched=await loadMemberships();if(tenantSwitched){location.reload();return;}renderCloudUI();if(workspace){subscribeRealtime();await pullRemote({replace:true,quiet:true});}});
$('sunCloudMigrateV2')?.addEventListener('click',migrateLocalToCloud);
$('sunCloudSyncNowV2')?.addEventListener('click',()=>syncNow({quiet:false}));
$('sunCloudPullV2')?.addEventListener('click',async()=>{if(confirm('Заменить локальную рабочую копию актуальными данными из облака? Перед загрузкой будет создана резервная копия.'))await pullRemote({replace:true,quiet:false});});
$('sunCloudAutoV2')?.addEventListener('change',e=>{config.autoSync=e.target.checked;saveConfig();if(config.autoSync)scheduleSync('автосинхронизация включена');renderCloudUI();});
$('sunCloudWorkspaceSelectV2')?.addEventListener('change',async e=>{const next=String(e.target.value||'');config.workspaceId=next;saveConfig();const switched=await switchTenantLocal(next);if(switched.changed){location.reload();return;}selectWorkspace();subscribeRealtime();renderCloudUI();if(workspaceMigrated())await syncNow({quiet:true});else await pullRemote({replace:true,quiet:true});});
$('sunCloudManageUsersV3')?.addEventListener('click',()=>window.dispatchEvent(new CustomEvent('sun:open-cloud-users')));
}
async function loadSupabaseLibrary() {
if(window.supabase?.createClient)return;
const existing=$('sunSupabaseJsV2');
if(existing){for(let i=0;i<80&&!window.supabase?.createClient;i++)await sleep(100);if(window.supabase?.createClient)return;}
await new Promise((resolve,reject)=>{
const script=document.createElement('script');script.id='sunSupabaseJsV2';script.src=SUPABASE_JS;script.async=true;script.onload=resolve;script.onerror=()=>reject(new Error('Не удалось загрузить библиотеку Supabase. Проверьте интернет.'));document.head.appendChild(script);
});
if(!window.supabase?.createClient)throw new Error('Supabase SDK не загрузился.');
}
async function connectFromConfig(silent=true) {
if(!config.url||!config.key){setStatus('local','Облако не подключено.');return;}
setStatus('syncing','Подключаю Supabase…');
try{
await loadSupabaseLibrary();
const signature=`${config.url}|${config.key}`;
if(!client||signature!==clientSignature){
client=window.supabase.createClient(config.url,config.key,{auth:{persistSession:true,autoRefreshToken:true,detectSessionInUrl:true},global:{fetch:supabaseProxyFetch}});clientSignature=signature;
client.auth.onAuthStateChange((_event,next)=>{session=next;membershipsLoading=Boolean(next?.user);membershipsLoaded=!next?.user;setTimeout(async()=>{try{if(!next?.user){const sw=await switchTenantLocal('');if(sw.changed){location.reload();return;}}else{const sw=await loadMemberships();if(sw){location.reload();return;}}}finally{renderCloudUI();updatePill();}},0);});
}
const {data,error}=await client.auth.getSession();if(error)throw error;session=data.session||null;
const tenantSwitched=session?await loadMemberships():(await switchTenantLocal('')).changed;
if(tenantSwitched){location.reload();return;}
if(session&&workspace){
subscribeRealtime();
if(workspaceMigrated())await syncNow({quiet:true});
else {
const remote=await fetchRemoteRow();
if(remote&&!payloadEmpty(remote.payload)){
await backupBeforeCloud('Перед автоматическим подключением к существующей облачной базе');
await applyPayload(remote.payload,true);await setBaseline(remote);config.migrated[workspace.id]=true;config.lastSync=remote.updated_at||new Date().toISOString();saveConfig();dirty=false;setStatus('ready','Подключена существующая облачная база.');
} else setStatus('setup','Готово к первому переносу данных.');
}
} else if(session)setStatus('setup',membershipsLoading?'Загружаю рабочую базу…':'Для этого аккаунта рабочая база не найдена.');
else setStatus('auth','Подключено. Войдите в аккаунт.');
if(!silent)toast('Supabase подключён.','success');
}catch(error){handleError(error,'Не удалось подключиться к Supabase.');}
finally{renderCloudUI();}
}
async function signIn(signUp) {
const email=String($('sunCloudEmailV2')?.value||'').trim();const password=String($('sunCloudPasswordV2')?.value||'');
if(!email||password.length<6)return toast('Введите email и пароль не короче 6 символов.','warn');
setStatus('syncing',signUp?'Создаю аккаунт…':'Выполняю вход…');
try{
const result=signUp?await client.auth.signUp({email,password,options:{data:{registration_source:'caterium_public_signup'}}}):await client.auth.signInWithPassword({email,password});
if(result.error)throw result.error;session=result.data.session||null;
if(signUp&&!session){const retry=await client.auth.signInWithPassword({email,password});if(retry.error)throw retry.error;session=retry.data.session||null;}
{const tenantSwitched=await loadMemberships();if(tenantSwitched){location.reload();return;}setStatus('setup','Вход выполнен.');toast('Вход выполнен.','success');}
renderCloudUI();
}catch(error){handleError(error,signUp?'Не удалось создать аккаунт.':'Не удалось войти.');}
}
async function loadMemberships() {
memberships=[];workspace=null;membershipsLoaded=false;
if(!client||!session?.user)return false;
membershipsLoading=true;
try{
let rows=[];
const rpc=await sunCloudAwait(client.rpc('sun_my_workspaces'),'Загрузка рабочей базы');
if(!rpc.error&&Array.isArray(rpc.data)){
rows=rpc.data.map(x=>({workspace_id:x.id,name:x.name,role:x.role,display_name:x.display_name,is_active:x.is_active,permissions:x.permissions}));
}else{
// Compatibility fallback for an older server schema.
const direct=await sunCloudAwait(client.from('sun_workspace_members').select('workspace_id,role,display_name,is_active,permissions').eq('user_id',session.user.id),'Загрузка рабочей базы');
if(direct.error)throw rpc.error||direct.error;
const active=(direct.data||[]).filter(x=>x.is_active!==false);
if(active.length){
const ids=active.map(x=>x.workspace_id), names={};
const w=await sunCloudAwait(client.from('sun_workspaces').select('id,name').in('id',ids),'Загрузка рабочей базы');if(w.error)throw w.error;(w.data||[]).forEach(x=>names[x.id]=x.name);
rows=active.map(x=>({...x,name:names[x.workspace_id]||'Рабочая база'}));
}
}
memberships=rows.filter(x=>x.is_active!==false).map(x=>({id:x.workspace_id,name:x.name||'Рабочая база',role:x.role,display_name:x.display_name||'',is_active:x.is_active!==false,permissions:x.permissions||{}}));
if(memberships.length&&!memberships.some(x=>x.id===config.workspaceId))config.workspaceId=memberships[0].id;
if(!memberships.length)config.workspaceId='';
saveConfig();selectWorkspace();
const switched=await switchTenantLocal(workspace?.id||'');
membershipsLoaded=true;
if(switched.changed)return true;
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed',{detail:{workspace:clone(workspace),user:clone(session.user)}}));}catch(_){}
return false;
}catch(error){
membershipsLoaded=true;
setStatus('error',error?.message||'Не удалось загрузить рабочую базу.');
return false;
}finally{membershipsLoading=false;}
}
function selectWorkspace(){workspace=memberships.find(x=>x.id===config.workspaceId)||memberships[0]||null;if(workspace&&config.workspaceId!==workspace.id){config.workspaceId=workspace.id;saveConfig();}}
async function createTrialWorkspace(companyName='Новая компания'){
if(!client||!session?.user)throw new Error('Сначала войдите в аккаунт.');
const name=String(companyName||'').trim()||'Новая компания';
await loadMemberships();
if(workspace)return clone(workspace);
setStatus('syncing','Создаю компанию и включаю 14-дневный Полный тариф…');
const {data,error}=await client.rpc('sun_create_workspace',{p_name:name});if(error)throw error;
const newId=String(data||'');if(!newId)throw new Error('Сервер не вернул ID новой компании.');
await switchTenantLocal(newId);
seedOfficialCatalogForFreshWorkspace();
try{localStorage.setItem('sunCompanyProfileV1',JSON.stringify({name,createdAt:new Date().toISOString()}));}catch(_){}
await adoptCurrentLocalAsWorkspace(newId);
await loadMemberships();
if(!workspace||String(workspace.id)!==newId){config.workspaceId=newId;saveConfig();selectWorkspace();}
if(!workspace)throw new Error('Компания создана, но не появилась в списке доступных компаний.');
const remote=await fetchRemoteRow();
const localPayload=await collectLocalPayload();
const row=await upsertPayload(localPayload,remote?.revision??null);
await setBaseline(row);
config.migrated[newId]=true;config.lastSync=row?.updated_at||new Date().toISOString();config.autoSync=true;saveConfig();dirty=false;
try{await client.rpc('sun_v17_mirror_legacy',{p_workspace:newId,p_payload:localPayload,p_client_id:clientId()});}catch(_){}
subscribeRealtime();
setStatus('ready','Компания создана · Полный тариф бесплатно 14 дней.');
renderCloudUI();
return clone(workspace);
}
async function createWorkspace(){
const name=String($('sunCloudWorkspaceNameV2')?.value||'Новая компания').trim()||'Новая компания';
try{await createTrialWorkspace(name);toast('Компания создана: Полный тариф на 14 дней, каталог готов, заказы и клиенты пустые.','success',7000);}
catch(error){handleError(error,'Не удалось создать рабочую базу.');}
}
async function acceptInviteToken(rawToken){
const token=String(rawToken||'').trim();if(!token)throw new Error('Приглашение не найдено.');
if(!client||!session?.user)throw new Error('Сначала войдите в Caterium.');
setStatus('syncing','Подключаю компанию по приглашению…');
const {data,error}=await client.rpc('sun_accept_invite',{p_token:token});if(error)throw error;
config.workspaceId=String(data||'');saveConfig();
await loadMemberships();
subscribeRealtime();
const remote=await fetchRemoteRow();
if(remote&&!payloadEmpty(remote.payload)){
await backupBeforeCloud('Перед подключением к компании по приглашению');
await applyPayload(remote.payload,true);await setBaseline(remote);
config.migrated[workspace.id]=true;config.lastSync=remote.updated_at||new Date().toISOString();saveConfig();
}
setStatus('ready','Вы присоединились к компании.');renderCloudUI();
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed',{detail:{workspace:clone(workspace)}}));}catch(_){}
return clone(workspace);
}
async function joinWorkspace(){
const token=String($('sunCloudInviteCodeV2')?.value||'').trim();if(!token)return toast('Введите код приглашения.','warn');
try{await acceptInviteToken(token);toast('Вы присоединились к компании.','success',6000);}
catch(error){handleError(error,'Не удалось принять приглашение. Возможно, ссылка истекла или уже использована.');}
}
async function createInvite(){
const role=String($('sunCloudInviteRoleV2')?.value||'manager');
try{const {data,error}=await client.rpc('sun_create_invite',{p_workspace:workspace.id,p_role:role});if(error)throw error;const token=String(data||'');const host=$('sunCloudInviteResultV2');if(host)host.innerHTML=`<p class="sun-cloud-v2-note">Передайте сотруднику этот одноразовый код (действует 7 дней):</p><div class="sun-cloud-v2-code">${esc(token)}</div><div class="sun-cloud-v2-actions"><button class="outline" id="sunCloudCopyInviteV2" type="button">Скопировать код</button></div>`;$('sunCloudCopyInviteV2')?.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(token);toast('Код скопирован.','success');}catch(_){toast('Скопируйте код вручную.','warn');}});}
catch(error){handleError(error,'Не удалось создать приглашение.');}
}
function unsubscribeRealtime(){if(realtimeChannel&&client){try{client.removeChannel(realtimeChannel);}catch(_){}}realtimeChannel=null;}
function subscribeRealtime(){
unsubscribeRealtime();if(!client||!session||!workspace?.id)return;
realtimeChannel=client.channel(`sun-state-${workspace.id}-${clientId()}`).on('postgres_changes',{event:'INSERT',schema:'public',table:'sun_sync_events',filter:`workspace_id=eq.${workspace.id}`},async payload=>{
if(payload?.new?.client_id===clientId())return;
try{const row=await fetchRemoteRow();if(row)await handleRealtimeRow(row);}catch(error){handleError(error,'Не удалось получить изменения с другого устройства.');}
}).subscribe(status=>{if(status==='CHANNEL_ERROR')setStatus('error','Realtime не подключился. Обычная синхронизация продолжит работать.');});
}
function handleError(error, message) {
console.error('[SunCloudV2]',error);const detail=String(error?.message||error||'Неизвестная ошибка');setStatus('error',`${message} ${detail}`);toast(`${message} ${detail}`,'error',7500);renderCloudUI();
}
function observeSettings() {
const settingsRoot=$('enterprise-settings');if(settingsRoot){const observer=new MutationObserver(()=>{if(findCloudCard()&&!$('sunCloudV2Card'))ensureCloudCard();});observer.observe(settingsRoot,{childList:true,subtree:true});}
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const name=String(b.dataset.navLabel||b.textContent||'').trim();if(name==='Настройки')setTimeout(()=>{ensureCloudCard();renderCloudUI();},80);},true);
setTimeout(()=>{ensureCloudCard();renderCloudUI();},250);
}
function hookEnterpriseSchedule() {
const timer=setInterval(()=>{if(window.SunEnterprise){window.SunEnterprise.scheduleCloudPush=()=>scheduleSync('сохранение приложения');clearInterval(timer);}},250);
setTimeout(()=>clearInterval(timer),10000);
}
window.addEventListener('online',()=>{setStatus('pending','Интернет восстановлен. Синхронизирую…');if(workspaceMigrated())syncNow({quiet:true});});
window.addEventListener('offline',()=>setStatus('offline','Офлайн — изменения сохраняются на этом устройстве.'));
async function enterSupportWorkspace(workspaceId,name='Компания') {
const id=String(workspaceId||'').trim();if(!id)throw new Error('Не выбрана компания.');
if(!client||!session?.user)throw new Error('Сначала войдите в аккаунт разработчика.');
const admin=await sunCloudAwait(client.rpc('sun_is_platform_admin'),'Проверка доступа разработчика',8000);if(admin.error)throw admin.error;if(admin.data!==true)throw new Error('Требуется аккаунт разработчика.');
const previousWorkspaceId=String(config.workspaceId||'');
const previousLocalWorkspaceId=String(config.localWorkspaceId||workspace?.id||'');
if(previousLocalWorkspaceId)await captureTenantLocal(previousLocalWorkspaceId);
clearTenantLocal();config.localWorkspaceId='';saveConfig();unsubscribeRealtime();
const result=await sunCloudAwait(client.rpc('sun_dev_support_snapshot',{p_workspace:id}),'Загрузка компании в режиме поддержки',12000);if(result.error)throw result.error;
const row=Array.isArray(result.data)?result.data[0]:result.data;if(!row?.payload)throw new Error('У компании нет облачной рабочей копии.');
await applyPayload(row.payload,true);
supportMode={workspaceId:id,name:String(name||'Компания'),previousWorkspaceId,previousLocalWorkspaceId,enteredAt:new Date().toISOString()};
workspace={id,name:String(name||'Компания'),role:'platform_support',display_name:'Разработчик · поддержка',is_active:true,permissions:{}};
dirty=false;setStatus('ready',`Режим поддержки · ${name||'Компания'} · только просмотр`);
try{window.dispatchEvent(new CustomEvent('sun:cloud-permissions-changed',{detail:{workspace:clone(workspace),support:true}}));window.dispatchEvent(new CustomEvent('sun:cloud-state-applied',{detail:{workspaceId:id,support:true}}));}catch(_){}
renderCloudUI();return clone(supportMode);
}
async function refreshSupportWorkspace(){
if(!supportMode||!client||!session?.user)return false;
const id=String(supportMode.workspaceId||workspace?.id||'').trim();if(!id)return false;
const result=await sunCloudAwait(client.rpc('sun_dev_support_snapshot',{p_workspace:id}),'Обновление данных компании',12000);if(result.error)throw result.error;
const row=Array.isArray(result.data)?result.data[0]:result.data;if(!row?.payload)throw new Error('У компании нет облачной рабочей копии.');
await applyPayload(row.payload,true);dirty=false;config.lastSync=new Date().toISOString();saveConfig();
setStatus('ready','Режим поддержки · '+(supportMode.name||'Компания')+' · только просмотр');
try{window.dispatchEvent(new CustomEvent('sun:cloud-state-applied',{detail:{workspaceId:id,support:true,refresh:true}}));}catch(_){}
renderCloudUI();return true;
}
async function exitSupportWorkspace() {
if(!supportMode)return false;
clearTenantLocal();supportMode=null;workspace=null;config.localWorkspaceId='';saveConfig();
const switched=await loadMemberships();renderCloudUI();
if(switched||workspace){location.reload();return true;}
return true;
}
async function setTechnicalConnection(url,key) {
const admin=client&&session?.user?await client.rpc('sun_is_platform_admin'):null;
if(!admin||admin.error||admin.data!==true)throw new Error('Техническое подключение доступно только разработчику.');
config.url=String(url||'').trim().replace(/\/$/,'');config.key=String(key||'').trim();saveConfig();
if(!config.url||!config.key)throw new Error('Введите Project URL и Publishable key.');
client=null;clientSignature='';session=null;workspace=null;memberships=[];supportMode=null;
await connectFromConfig(false);return true;
}
function setAutoSyncDeveloper(enabled){config.autoSync=enabled!==false;saveConfig();if(config.autoSync&&workspace&&!supportMode)scheduleSync('автосинхронизация включена');renderCloudUI();return config.autoSync;}
window.SunCloudV2={
VERSION,
signOut:signOutAndReload,
adoptCurrentLocalAsWorkspace,
createTrialWorkspace,
acceptInvite:acceptInviteToken,
syncNow:()=>syncNow({quiet:false}),
pull:()=>pullRemote({replace:true,quiet:false}),
migrate:migrateLocalToCloud,
hasPermission,
roleLabel,
getClient:()=>client,
getSession:()=>session?clone(session):null,
getWorkspace:()=>workspace?clone(workspace):null,
getClientId:()=>clientId(),
enterSupportWorkspace,
refreshSupportWorkspace,
exitSupportWorkspace,
isSupportMode:()=>Boolean(supportMode),
getSupportMode:()=>supportMode?clone(supportMode):null,
getTechnicalConfig:()=>({url:config.url,key:config.key,autoSync:config.autoSync,lastSync:config.lastSync,workspaceId:config.workspaceId,localWorkspaceId:config.localWorkspaceId}),
setTechnicalConnection,
setAutoSyncDeveloper,
snapshotLocal:()=>collectLocalPayload(),
reloadMemberships:async()=>{await loadMemberships();renderCloudUI();return workspace?clone(workspace):null;},
status:()=>({connected:Boolean(client),signedIn:Boolean(session),supportMode:supportMode?clone(supportMode):null,workspace:workspace?clone(workspace):null,membershipsLoading,membershipsLoaded,membershipCount:memberships.length,dirty,lastStatus,lastError})
};
async function boot(){
injectStyles();disableLegacyCloud();installStorageTracking();hookEnterpriseSchedule();observeSettings();updatePill();
if(config.url&&config.key)await connectFromConfig(true);else setStatus('local','Облако не подключено. Откройте Настройки → Облако Supabase.');
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
;
/* ===== MODULE: admin-rbac-v3.js ===== */
(() => {
'use strict';
if (window.SunAdminRBACV3) return;
const VERSION = '2026.09.06-admin-rbac-v7-granular-admins';
const $ = id => document.getElementById(id);
const qa = (s,r=document) => [...r.querySelectorAll(s)];
const esc=window.SunSafe.escapeHTML;
const toast = (text,type='info',timeout=4500) => {
try { if (window.SunEnterprise?.toast) return window.SunEnterprise.toast(text,type,timeout); } catch (_) {}
alert(text);
};
const ROLE_LABELS = {admin:'Администратор',manager:'Менеджер',kitchen:'Кухня',courier:'Курьер',viewer:'Только просмотр'};
const GROUPS = [
{title:'Заказы', items:[['orders.view','Просмотр заказов'],['orders.create','Создание заказов'],['orders.edit','Изменение заказов'],['orders.delete','Удаление заказов'],['calendar.view','Календарь'],['map.view','Карта']]},
{title:'Клиенты и каталог', items:[['clients.view','Просмотр клиентов'],['clients.edit','Изменение клиентов'],['catalog.view','Просмотр каталога'],['catalog.edit','Изменение каталога']]},
{title:'Производство и доставка', items:[['production.view','Просмотр производства'],['production.edit','Изменение производства'],['shopping.view','Просмотр закупок'],['shopping.edit','Изменение закупок'],['stock.view','Просмотр склада'],['stock.edit','Изменение склада'],['routes.view','Просмотр маршрутов'],['routes.edit','Изменение маршрутов']]},
{title:'Управление', items:[['mailings.view','Просмотр рассылок'],['mailings.edit','Изменение рассылок'],['money.view','Просмотр денег'],['money.edit','Изменение денег'],['stats.view','Статистика'],['team.view','Просмотр команды'],['team.edit','Изменение команды'],['suppliers.view','Просмотр поставщиков'],['suppliers.edit','Изменение поставщиков'],['print.view','Печать']]},
{title:'Администрирование', items:[['settings.view','Просмотр настроек'],['settings.edit','Изменение настроек'],['users.manage','Пользователи и права'],['audit.view','История изменений'],['backups.manage','Резервные копии']]}
];
const PRESETS = {
admin: Object.fromEntries(GROUPS.flatMap(g=>g.items).map(([k])=>[k,true])),
manager: {
'orders.view':true,'orders.create':true,'orders.edit':true,'orders.delete':false,'calendar.view':true,'map.view':true,
'clients.view':true,'clients.edit':true,'catalog.view':true,'catalog.edit':false,
'production.view':false,'production.edit':false,'shopping.view':false,'shopping.edit':false,'stock.view':false,'stock.edit':false,'routes.view':true,'routes.edit':false,
'mailings.view':true,'mailings.edit':true,'money.view':false,'money.edit':false,'stats.view':true,'team.view':true,'team.edit':false,'suppliers.view':false,'suppliers.edit':false,'print.view':true,
'settings.view':false,'settings.edit':false,'users.manage':false,'audit.view':false,'backups.manage':false
},
kitchen: {
'orders.view':true,'orders.create':false,'orders.edit':false,'orders.delete':false,'calendar.view':true,'map.view':false,
'clients.view':false,'clients.edit':false,'catalog.view':true,'catalog.edit':false,
'production.view':true,'production.edit':true,'shopping.view':true,'shopping.edit':false,'stock.view':true,'stock.edit':true,'routes.view':false,'routes.edit':false,
'mailings.view':false,'mailings.edit':false,'money.view':false,'money.edit':false,'stats.view':false,'team.view':true,'team.edit':false,'suppliers.view':false,'suppliers.edit':false,'print.view':true,
'settings.view':false,'settings.edit':false,'users.manage':false,'audit.view':false,'backups.manage':false
},
courier: {
'orders.view':true,'orders.create':false,'orders.edit':false,'orders.delete':false,'calendar.view':true,'map.view':true,
'clients.view':false,'clients.edit':false,'catalog.view':false,'catalog.edit':false,
'production.view':false,'production.edit':false,'shopping.view':false,'shopping.edit':false,'stock.view':false,'stock.edit':false,'routes.view':true,'routes.edit':true,
'mailings.view':false,'mailings.edit':false,'money.view':false,'money.edit':false,'stats.view':false,'team.view':false,'team.edit':false,'suppliers.view':false,'suppliers.edit':false,'print.view':false,
'settings.view':false,'settings.edit':false,'users.manage':false,'audit.view':false,'backups.manage':false
},
viewer: {
'orders.view':true,'orders.create':false,'orders.edit':false,'orders.delete':false,'calendar.view':true,'map.view':true,
'clients.view':true,'clients.edit':false,'catalog.view':true,'catalog.edit':false,
'production.view':false,'production.edit':false,'shopping.view':false,'shopping.edit':false,'stock.view':false,'stock.edit':false,'routes.view':false,'routes.edit':false,
'mailings.view':false,'mailings.edit':false,'money.view':false,'money.edit':false,'stats.view':true,'team.view':false,'team.edit':false,'suppliers.view':false,'suppliers.edit':false,'print.view':true,
'settings.view':false,'settings.edit':false,'users.manage':false,'audit.view':false,'backups.manage':false
}
};
let members = [];
let editing = null;
let inviting = false;
let applying = false;
let lastWorkspaceId = '';
function cloud(){ return window.SunCloudV2 || null; }
function workspace(){ return cloud()?.getWorkspace?.() || null; }
function session(){ return cloud()?.getSession?.() || null; }
function client(){ return cloud()?.getClient?.() || null; }
function has(p){ try{if(localStorage.getItem('sunLocalOnlyModeV1')==='1')return true;}catch(_){} const c=cloud(); return c?.hasPermission ? c.hasPermission(p) : false; }
function currentPermissions(){ return workspace()?.permissions || {}; }
function normalizeLegacyLocalAuth(){
try{
const raw=localStorage.getItem('sunEnterpriseSettingsV1');
if(!raw)return;
const s=JSON.parse(raw)||{};
if(s.rolesEnabled){s.rolesEnabled=false;localStorage.setItem('sunEnterpriseSettingsV1',JSON.stringify(s));}
}catch(_){}
}
function installStyle(){
if($('sun-admin-rbac-style'))return;
const style=document.createElement('style');style.id='sun-admin-rbac-style';style.textContent=`
#sun-cloud-users-modal .dialog{max-width:980px;width:min(980px,calc(100vw - 24px));max-height:92vh;overflow:auto}
.sun-rbac-toolbar{display:flex;gap:8px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin:8px 0 12px}
.sun-rbac-members{display:grid;gap:8px}.sun-rbac-member{display:grid;grid-template-columns:minmax(220px,1.4fr) minmax(150px,.7fr) auto;gap:10px;align-items:center;border:1px solid #dfe5e8;border-radius:10px;padding:10px 12px;background:#fff}
.sun-rbac-member small{display:block;color:#75808a;margin-top:3px}.sun-rbac-off{opacity:.58}.sun-rbac-perm-groups{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:12px}.sun-rbac-group{border:1px solid #dfe5e8;border-radius:10px;padding:10px;background:#fafcfc}.sun-rbac-group h4{margin:0 0 8px;color:#17384d}.sun-rbac-check{display:flex!important;flex-direction:row!important;align-items:center!important;gap:8px!important;margin:6px 0!important}.sun-rbac-check input{width:17px!important;height:17px!important;flex:0 0 auto}.sun-rbac-role-line{display:grid;grid-template-columns:1.2fr 1fr auto;gap:10px;align-items:end}.sun-rbac-danger{margin-left:auto}.sun-rbac-section-title{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:18px 0 8px}.sun-rbac-section-title h3{margin:0;color:#17384d;font-size:15px}.sun-rbac-invite{display:grid;grid-template-columns:minmax(190px,1.2fr) minmax(130px,.65fr) auto;gap:10px;align-items:center;border:1px dashed #cfd9df;border-radius:10px;padding:10px 12px;background:#fbfcfc}.sun-rbac-invite small{display:block;color:#75808a;margin-top:3px}.sun-rbac-invite-form{border:1px solid #dfe5e8;border-radius:12px;padding:14px;background:#fbfcfc}.sun-rbac-invite-grid{display:grid;grid-template-columns:1.2fr 1.5fr 1fr;gap:10px}.sun-rbac-linkbox{margin-top:12px;padding:10px;border-radius:9px;background:#f1f5f6;word-break:break-all;font:600 12px/1.45 ui-monospace,monospace}.sun-rbac-lock-note{font-size:12px;color:#6e7880;margin-top:6px}.sun-cloud-identity-v3{font-size:12px;color:#adc0cc;padding:3px 7px}.sun-cloud-pin-card{border-top:1px solid #e2e7e9;margin-top:10px;padding-top:10px}
body.sun-cloud-auth-required .view,body.sun-cloud-auth-required header{filter:blur(2px);pointer-events:none;user-select:none}.sun-cloud-auth-gate{position:fixed;inset:0;z-index:10000;background:#132f44eb;display:grid;place-items:center;padding:20px}.sun-cloud-auth-card{width:min(430px,100%);background:#fff;border-radius:16px;padding:22px;box-shadow:0 24px 80px #0005}.sun-cloud-auth-brand{display:flex;align-items:center;gap:12px;margin-bottom:16px}.sun-cloud-auth-brand img{width:58px;height:58px;object-fit:contain}.sun-cloud-auth-card h2{margin:0;color:#17384d}.sun-cloud-auth-card label{display:block;margin:10px 0}.sun-cloud-auth-card input{width:100%}.sun-auth-password{position:relative}.sun-auth-password input{padding-right:46px}.sun-auth-eye{position:absolute;right:7px;bottom:6px;width:34px;height:34px;border:0;background:transparent;border-radius:8px;cursor:pointer;font-size:17px;color:#536674}.sun-auth-eye:hover{background:#eef2f3}.sun-auth-switch{margin:14px 0 0;text-align:center;font-size:13px}.sun-auth-switch button{border:0;background:transparent;color:#245c7c;font-weight:800;cursor:pointer;text-decoration:underline;text-underline-offset:3px}.sun-cloud-auth-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.sun-cloud-auth-error{color:#9a3838;font-size:12px;margin-top:8px;min-height:18px}.sun-auth-mode-tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px;background:#f2f4f5;border-radius:11px;padding:4px;margin:10px 0 14px}.sun-auth-mode-tabs button{border:0;border-radius:8px;padding:9px 10px;background:transparent;font-weight:800;cursor:pointer}.sun-auth-mode-tabs button.on{background:#fff;box-shadow:0 2px 10px #0001;color:#17384d}.sun-auth-trial{background:#eef8f2;color:#286447;border-radius:10px;padding:10px 11px;font-size:12px;font-weight:700;margin:10px 0}.sun-auth-success{background:#eef8f2;color:#286447;border-radius:10px;padding:10px 11px;font-size:12px;margin-top:10px}
@media(max-width:760px){.sun-rbac-perm-groups{grid-template-columns:1fr}.sun-rbac-member,.sun-rbac-invite{grid-template-columns:1fr}.sun-rbac-role-line,.sun-rbac-invite-grid{grid-template-columns:1fr}}
`;document.head.appendChild(style);
}
function navPermission(label){
const map={
'Главная':'app.read','Новый заказ':'orders.create','Заказы':'orders.view','Календарь':'calendar.view','Карта':'map.view',
'Производство':'production.view','Закупки':'shopping.view','Склад':'stock.view','Клиенты':'clients.view','Рассылки':'mailings.view',
'Деньги':'money.view','Команда':'team.view','Поставщики':'suppliers.view','Статистика':'stats.view','Печать':'print.view','Маршруты':'routes.view','Настройки':'settings.view'
};
return map[label] || null;
}
function canSeeSettings(){return has('settings.view')||has('users.manage')||has('backups.manage')||has('audit.view');}
function applyNavPermissions(){
const ws=workspace(), ss=session();
if(!ws||!ss?.user)return;
qa('header nav button').forEach(btn=>{
const label=String(btn.dataset.navLabel||btn.textContent||'').trim();
let allowed=true;
if(label==='Настройки')allowed=canSeeSettings();
else {const perm=navPermission(label);if(perm)allowed=has(perm);}
btn.hidden=!allowed;btn.style.display=allowed?'':'none';
});
const label=$('sunCurrentUserLabel');
if(label)label.textContent=`${ws.display_name||ss.user.email||'Пользователь'} · ${ROLE_LABELS[ws.role]||ws.role}`;
const logout=$('sunLogoutBtn');
if(logout){logout.style.display='flex';logout.onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut();}catch(_){}location.reload();};}
applyActionPermissions();
purgeSensitiveLocalData();
}
function applyActionPermissions(){
const del=$('deleteOrderBtn');if(del)del.style.display=(del.style.display==='none'&&!draftIdSafe())?'none':(has('orders.delete')?'inline-block':'none');
qa('#orders [data-sun-delete-order],#orders button').forEach(b=>{if(/удал/i.test(b.textContent||''))b.style.display=has('orders.delete')?'':'none';});
const save=qa('#new button').find(b=>(b.textContent||'').trim()==='Сохранить');if(save){const existing=draftIdSafe();save.disabled=existing?!has('orders.edit'):!has('orders.create');save.title=save.disabled?'Недостаточно прав':'';}
qa('#new .catalog-head button').forEach(b=>{if((b.textContent||'').trim()==='Настроить')b.style.display=has('catalog.edit')?'':'none';});
qa('#manager .primary,#editor .primary,#editor .danger').forEach(b=>{b.disabled=!has('catalog.edit');});
}
function draftIdSafe(){try{return Boolean(window.draft?.id||draft?.id);}catch(_){return false;}}
function purgeSensitiveLocalData(){
const rules=[
['sunFinanceRecordsV2',()=>has('money.view')||has('money.edit')],
['sunAuditLogV1',()=>has('audit.view')],
['sunSuppliers',()=>has('suppliers.view')||has('suppliers.edit')||has('shopping.view')],
['sunStockMoves',()=>has('stock.view')||has('stock.edit')||has('shopping.view')||has('production.view')],
['sunStock',()=>has('stock.view')||has('stock.edit')||has('shopping.view')||has('production.view')],
['sunEmployees',()=>has('team.view')||has('team.edit')||has('production.view')||has('routes.view')],
['sunClientCommunicationV1',()=>has('clients.view')||has('clients.edit')||has('orders.create')||has('orders.edit')],
['sunClientLoyaltyV1',()=>has('clients.view')||has('clients.edit')||has('orders.create')||has('orders.edit')]
];
for(const [key,ok] of rules){if(!ok())try{localStorage.removeItem(key);}catch(_){}}
}
function guardGlobal(name, permission, mode){
const fn=window[name];if(typeof fn!=='function'||fn.__sunRbacGuard)return;
const wrapped=function(...args){
let perm=permission;
if(typeof permission==='function')perm=permission(...args);
if(perm&&!has(perm)){toast('У вас нет прав для этого действия.','warn');return;}
return fn.apply(this,args);
};
wrapped.__sunRbacGuard=true;wrapped.__sunOriginal=fn;window[name]=wrapped;
}
function installGuards(){
guardGlobal('saveOrder',()=>draftIdSafe()?'orders.edit':'orders.create');
guardGlobal('deleteOrder','orders.delete');
guardGlobal('deleteOrderById','orders.delete');
guardGlobal('saveBox','catalog.edit');
guardGlobal('removeBox','catalog.edit');
guardGlobal('editBox','catalog.edit');
guardGlobal('writeOffOrderStock','stock.edit');
guardGlobal('sunEditSupplier','suppliers.edit');
guardGlobal('editStockProduct','stock.edit');
}
function hideLegacyRoleCard(){
const view=$('enterprise-settings');if(!view)return;
qa('section',view).filter(s=>(s.querySelector(':scope > h2')?.textContent||'').trim()==='Пользователи и роли').forEach(s=>s.remove());
if(!has('backups.manage'))qa('section',view).filter(s=>(s.querySelector(':scope > h2')?.textContent||'').includes('Резервное копирование')).forEach(s=>s.style.display='none');
if(!has('audit.view'))qa('section',view).filter(s=>(s.querySelector(':scope > h2')?.textContent||'').includes('История изменений')).forEach(s=>s.style.display='none');
}
function ensureModal(){
if($('sun-cloud-users-modal'))return;
const modal=document.createElement('div');modal.id='sun-cloud-users-modal';modal.className='modal';modal.innerHTML=`<div class="dialog"><div class="dialog-head"><h2>Пользователи и права</h2><button type="button" data-rbac-close>×</button></div><div id="sunRbacBody"></div></div>`;document.body.appendChild(modal);
modal.addEventListener('click',e=>{if(e.target===modal||e.target.closest('[data-rbac-close]'))closeModal();});
}
function openModal(){ensureModal();$('sun-cloud-users-modal').classList.add('on');loadMembers();}
function closeModal(){$('sun-cloud-users-modal')?.classList.remove('on');editing=null;inviting=false;}
async function loadMembers(){
const c=client(),ws=workspace(),body=$('sunRbacBody');if(!c||!ws||!body)return;
body.innerHTML='<p class="hint">Загружаю сотрудников…</p>';
try{
const memberRes=await c.rpc('sun_list_workspace_members',{p_workspace:ws.id});
if(memberRes.error)throw memberRes.error;
members=memberRes.data||[];renderMembers();
}catch(e){body.innerHTML=`<p class="error">${esc(e.message||e)}</p>`;}
}
async function copyText(value,success='Данные скопированы.'){
try{await navigator.clipboard.writeText(String(value||''));toast(success,'success');}
catch(_){prompt('Скопируйте данные:',String(value||''));}
}
function renderMembers(){
const body=$('sunRbacBody');if(!body)return;
if(editing){renderEditor();return;}if(inviting){renderEmployeeForm();return;}
const active=members.map(m=>`<div class="sun-rbac-member ${m.is_active?'':'sun-rbac-off'}"><div><b>${esc(m.display_name||m.email||'Сотрудник')}</b><small>${esc(m.email||'')} · ${m.is_active?'активен':'доступ отключён'}</small></div><span class="role-chip">${esc(ROLE_LABELS[m.role]||m.role)}</span><button class="outline" type="button" data-rbac-edit="${esc(m.user_id)}">Права</button></div>`).join('')||'<p class="hint">Сотрудников пока нет.</p>';
body.innerHTML=`<div class="sun-rbac-toolbar"><div><b>${esc(workspace()?.name||'Рабочая база')}</b><div class="hint">Создайте сотрудника здесь. Новый сотрудник получит временный пароль для первого входа.</div></div><div class="actions"><button class="primary" type="button" id="sunRbacAddUser">+ Добавить сотрудника</button><button class="outline" type="button" id="sunRbacReload">Обновить</button></div></div><div class="sun-rbac-section-title"><h3>Сотрудники</h3><span class="hint">${members.filter(m=>m.is_active).length}</span></div><div class="sun-rbac-members">${active}</div>`;
$('sunRbacAddUser').onclick=()=>{inviting=true;renderEmployeeForm();};$('sunRbacReload').onclick=loadMembers;
qa('[data-rbac-edit]',body).forEach(b=>b.onclick=()=>{editing=members.find(m=>String(m.user_id)===String(b.dataset.rbacEdit));renderEditor();});
}
function renderEmployeeForm(){
const body=$('sunRbacBody');if(!body)return;
body.innerHTML=`<div class="sun-rbac-toolbar"><button class="outline" type="button" id="sunRbacEmployeeBack">← Сотрудники</button><div class="hint">Сотруднику не нужна отдельная ссылка-приглашение.</div></div><div class="sun-rbac-invite-form"><h3 style="margin:0 0 12px;color:#17384d">Добавить сотрудника</h3><div class="sun-rbac-invite-grid"><label>Имя<input id="sunRbacEmployeeName" autocomplete="name" placeholder="Например, Анна"></label><label>Email<input id="sunRbacEmployeeEmail" type="email" autocomplete="email" placeholder="name@example.com"></label><label>Роль<select id="sunRbacEmployeeRole">${Object.entries(ROLE_LABELS).map(([k,v])=>`<option value="${k}" ${k==='manager'?'selected':''}>${esc(v)}</option>`).join('')}</select></label></div><p class="hint">Если аккаунта ещё нет, Caterium создаст его и покажет временный пароль. Если аккаунт уже существует, он просто будет добавлен в эту компанию.</p><div class="actions"><button class="primary" type="button" id="sunRbacCreateEmployee">Создать сотрудника</button><button class="outline" type="button" id="sunRbacEmployeeCancel">Отмена</button></div><div id="sunRbacEmployeeResult"></div></div>`;
const back=()=>{inviting=false;renderMembers();};$('sunRbacEmployeeBack').onclick=back;$('sunRbacEmployeeCancel').onclick=back;$('sunRbacCreateEmployee').onclick=createEmployeeV28;
}
async function createEmployeeV28(){
const c=client(),ws=workspace(),btn=$('sunRbacCreateEmployee');if(!c||!ws||!btn)return;
const name=String($('sunRbacEmployeeName')?.value||'').trim(),email=String($('sunRbacEmployeeEmail')?.value||'').trim(),role=String($('sunRbacEmployeeRole')?.value||'manager');
if(!name)return toast('Введите имя сотрудника.','warn');
if(!email)return toast('Введите email сотрудника.','warn');
btn.disabled=true;btn.textContent='Создаю…';
try{
const prep=await c.rpc('sun_employee_prepare_v28',{p_workspace:ws.id,p_email:email,p_display_name:name,p_role:role});if(prep.error)throw prep.error;
let result=prep.data||{};
if(result.status==='already_member'){
toast('Этот пользователь уже добавлен в компанию.','warn');btn.disabled=false;btn.textContent='Создать сотрудника';return;
}
if(result.status==='existing'){
const fin=await c.rpc('sun_employee_finalize_v28',{p_workspace:ws.id,p_user_id:result.user_id,p_display_name:name,p_role:role});if(fin.error)throw fin.error;
result={...(fin.data||{}),created:false};
}else{
const call=await c.functions.invoke('caterium-create-employee',{body:{workspace_id:ws.id,email,display_name:name,role}});if(call.error)throw call.error;
if(call.data?.error)throw new Error(call.data.error);
result=call.data||{};
if(result.status==='existing'&&result.user_id){
const fin=await c.rpc('sun_employee_finalize_v28',{p_workspace:ws.id,p_user_id:result.user_id,p_display_name:name,p_role:role});if(fin.error)throw fin.error;
result={...(fin.data||{}),created:false};
}
}
const host=$('sunRbacEmployeeResult');
if(result.created&&result.temporary_password){
const credentials=`Caterium\nEmail: ${email}\nВременный пароль: ${result.temporary_password}`;
host.innerHTML=`<div class="sun-auth-success"><b>Сотрудник создан.</b><br>Передайте ему данные для первого входа:</div><div class="sun-rbac-linkbox"><b>Логин:</b> ${esc(email)}<br><b>Временный пароль:</b> ${esc(result.temporary_password)}</div><p class="hint">При первом входе Caterium попросит сотрудника придумать свой новый пароль.</p><div class="actions"><button class="primary" type="button" id="sunRbacCopyEmployee">Скопировать данные</button><button class="outline" type="button" id="sunRbacEmployeeDone">Готово</button></div>`;
$('sunRbacCopyEmployee').onclick=()=>copyText(credentials,'Логин и временный пароль скопированы.');
}else{
host.innerHTML=`<div class="sun-auth-success"><b>Сотрудник добавлен.</b><br>Аккаунт ${esc(email)} уже существовал в Caterium. Сотрудник входит своим обычным паролем.</div><div class="actions"><button class="primary" type="button" id="sunRbacEmployeeDone">Готово</button></div>`;
}
$('sunRbacEmployeeDone').onclick=async()=>{inviting=false;await loadMembers();};
toast('Сотрудник добавлен.','success');
}catch(e){toast(e.message||String(e),'error',7000);btn.disabled=false;btn.textContent='Создать сотрудника';}
}
function permissionHtml(perms, role){
return GROUPS.map(g=>`<div class="sun-rbac-group"><h4>${esc(g.title)}</h4>${g.items.map(([key,label])=>{const checked=typeof perms?.[key]==='boolean'?perms[key]:(role==='admin');return `<label class="sun-rbac-check"><input type="checkbox" data-rbac-perm="${esc(key)}" ${checked?'checked':''}><span>${esc(label)}</span></label>`;}).join('')}</div>`).join('');
}
function renderEditor(){
const body=$('sunRbacBody'),m=editing;if(!body||!m)return;
const perms=Object.assign({},PRESETS[m.role]||PRESETS.viewer,m.permissions||{});
const adminCount=members.filter(x=>x.role==='admin'&&x.is_active).length;
body.innerHTML=`<div class="sun-rbac-toolbar"><button class="outline" type="button" id="sunRbacBack">← Пользователи</button><div class="hint">${esc(m.email||'')}</div></div><div class="sun-rbac-role-line"><label>Имя<input id="sunRbacName" value="${esc(m.display_name||'')}"></label><label>Роль<select id="sunRbacRole">${Object.entries(ROLE_LABELS).map(([k,v])=>`<option value="${k}" ${m.role===k?'selected':''}>${esc(v)}</option>`).join('')}</select></label><label class="sun-rbac-check"><input id="sunRbacActive" type="checkbox" ${m.is_active?'checked':''}> Активный</label></div><div class="sun-rbac-perm-groups" id="sunRbacPerms">${permissionHtml(perms,m.role)}</div><div class="actions" style="margin-top:14px"><button class="primary" type="button" id="sunRbacSave">Сохранить права</button><button class="danger sun-rbac-danger" type="button" id="sunRbacRemove">Удалить доступ</button></div><p class="sun-rbac-lock-note">Активных администраторов: <b>${adminCount}</b>. Ограничение срабатывает только если после изменения не останется ни одного активного администратора. Хотя бы у одного активного администратора должно оставаться право «Пользователи и права».</p>`;
$('sunRbacBack').onclick=()=>{editing=null;renderMembers();};
$('sunRbacRole').onchange=()=>{
const role=$('sunRbacRole').value;
$('sunRbacPerms').innerHTML=permissionHtml(PRESETS[role]||PRESETS.viewer,role);
};
$('sunRbacSave').onclick=saveMember;
$('sunRbacRemove').onclick=removeMember;
}
function readEditorPermissions(){
const out={'app.read':true};
qa('[data-rbac-perm]',$('sunRbacPerms')).forEach(i=>out[i.dataset.rbacPerm]=i.checked);
return out;
}
async function saveMember(){
const c=client(),ws=workspace(),m=editing;if(!c||!ws||!m)return;
const btn=$('sunRbacSave');btn.disabled=true;
try{
const {error}=await c.rpc('sun_admin_update_member',{p_workspace:ws.id,p_user:m.user_id,p_display_name:$('sunRbacName').value.trim(),p_role:$('sunRbacRole').value,p_is_active:$('sunRbacActive').checked,p_permissions:readEditorPermissions()});
if(error)throw error;
toast('Права пользователя сохранены.','success');editing=null;
await cloud()?.reloadMemberships?.();
applyNavPermissions();
if(has('users.manage'))await loadMembers();else closeModal();
}catch(e){toast(e.message||String(e),'error',7000);}finally{if(btn)btn.disabled=false;}
}
async function removeMember(){
const c=client(),ws=workspace(),m=editing;if(!c||!ws||!m)return;
if(!confirm(`Удалить доступ пользователю ${m.display_name||m.email||''}?`))return;
try{const {error}=await c.rpc('sun_admin_remove_member',{p_workspace:ws.id,p_user:m.user_id});if(error)throw error;toast('Доступ удалён.','success');editing=null;await cloud()?.reloadMemberships?.();applyNavPermissions();if(has('users.manage'))await loadMembers();else closeModal();}
catch(e){toast(e.message||String(e),'error',7000);}
}
const hex=bytes=>[...bytes].map(x=>x.toString(16).padStart(2,'0')).join('');
const unhex=value=>new Uint8Array(String(value||'').match(/.{1,2}/g)?.map(x=>parseInt(x,16))||[]);
async function pinDigest(pin,salt){
const raw=new TextEncoder().encode(String(pin||''));
if(crypto?.subtle){
const key=await crypto.subtle.importKey('raw',raw,'PBKDF2',false,['deriveBits']);
const bits=await crypto.subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt,iterations:120000},key,256);
return hex(new Uint8Array(bits));
}
let v=2166136261;for(let n=0;n<5000;n++)for(const b of [...raw,...salt]){v^=b;v=Math.imul(v,16777619);}return String(v>>>0);
}
async function makePinRecord(pin){const salt=crypto.getRandomValues(new Uint8Array(16));return `v1:${hex(salt)}:${await pinDigest(pin,salt)}`;}
async function verifyPin(pin,record){const parts=String(record||'').split(':');if(parts.length!==3||parts[0]!=='v1')return false;const salt=unhex(parts[1]);return (await pinDigest(pin,salt))===parts[2];}
function pinKey(){const id=session()?.user?.id;return id?`sunCloudQuickPinV3:${id}`:'';}
function pinUnlockKey(){const id=session()?.user?.id;return id?`sunCloudQuickPinUnlockedV3:${id}`:'';}
async function setQuickPin(){
const id=session()?.user?.id;if(!id)return;
const p1=prompt('Введите новый PIN (48 цифр):','');if(p1===null)return;if(!/^\d{4,8}$/.test(p1))return toast('PIN должен содержать 48 цифр.','warn');
const p2=prompt('Повторите PIN:','');if(p1!==p2)return toast('PIN-коды не совпадают.','warn');
localStorage.setItem(pinKey(),await makePinRecord(p1));sessionStorage.setItem(pinUnlockKey(),'1');toast('Быстрый PIN включён на этом устройстве.','success');injectQuickPinCard();
}
function removeQuickPin(){const key=pinKey();if(!key)return;localStorage.removeItem(key);sessionStorage.removeItem(pinUnlockKey());toast('Быстрый PIN отключён.','success');injectQuickPinCard();}
function injectQuickPinCard(){
const card=$('sunCloudV2Card');if(!card||!session()?.user)return;
let box=$('sunCloudQuickPinCardV3');if(!box){box=document.createElement('div');box.id='sunCloudQuickPinCardV3';box.className='sun-cloud-v2-box wide sun-cloud-pin-card';card.appendChild(box);}
const enabled=Boolean(pinKey()&&localStorage.getItem(pinKey()));
box.innerHTML=`<h3>Быстрый вход на этом устройстве</h3><p class="sun-cloud-v2-note">Основной аккаунт — email и пароль. PIN только быстро разблокирует уже авторизованное устройство и не меняет облачные права.</p><div class="sun-cloud-v2-actions"><button class="outline" type="button" id="sunSetQuickPinV3">${enabled?'Изменить PIN':'Включить PIN'}</button>${enabled?'<button class="outline" type="button" id="sunRemoveQuickPinV3">Отключить PIN</button>':''}</div>`;
$('sunSetQuickPinV3').onclick=setQuickPin;$('sunRemoveQuickPinV3')?.addEventListener('click',removeQuickPin);
}
function showPinLockIfNeeded(){
const ss=session(),ws=workspace();if(!ss?.user||!ws)return;
const key=pinKey(), unlock=pinUnlockKey();if(!key||!localStorage.getItem(key)||sessionStorage.getItem(unlock)==='1'||$('sunCloudPinLockV3'))return;
const overlay=document.createElement('div');overlay.id='sunCloudPinLockV3';overlay.className='sun-cloud-auth-gate';overlay.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Caterium</h2><div class="hint">${esc(ws.display_name||ss.user.email||'Пользователь')} · ${esc(ROLE_LABELS[ws.role]||ws.role)}</div></div></div><label>PIN<input id="sunCloudPinInputV3" type="password" inputmode="numeric" autocomplete="current-password"></label><div class="sun-cloud-auth-actions"><button class="primary" id="sunCloudPinUnlockV3" type="button">Войти</button><button class="outline" id="sunCloudUsePasswordV3" type="button">Выйти из аккаунта</button></div><div class="sun-cloud-auth-error" id="sunCloudPinErrorV3"></div></div>`;document.body.appendChild(overlay);
const unlockFn=async()=>{const v=$('sunCloudPinInputV3').value;if(await verifyPin(v,localStorage.getItem(key))){sessionStorage.setItem(unlock,'1');overlay.remove();applyNavPermissions();}else $('sunCloudPinErrorV3').textContent='Неверный PIN.';};
$('sunCloudPinUnlockV3').onclick=unlockFn;$('sunCloudPinInputV3').addEventListener('keydown',e=>{if(e.key==='Enter')unlockFn();});$('sunCloudUsePasswordV3').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut();}catch(_){}location.reload();};setTimeout(()=>$('sunCloudPinInputV3')?.focus(),50);
}
const PENDING_REGISTRATION_KEY='sunPendingRegistrationV23';
const LOCAL_ONLY_KEY='sunLocalOnlyModeV1';
let registrationProvisioning=false;
function savePendingRegistration(email,companyName){try{localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({email:String(email||'').trim().toLowerCase(),companyName:String(companyName||'').trim()||'Новая компания',createdAt:new Date().toISOString()}));}catch(_){}}
function getPendingRegistration(email=''){try{const raw=JSON.parse(localStorage.getItem(PENDING_REGISTRATION_KEY)||'null');if(!raw?.email)return null;if(email&&String(raw.email).toLowerCase()!==String(email).trim().toLowerCase())return null;return raw}catch(_){return null}}
function clearPendingRegistration(){try{localStorage.removeItem(PENDING_REGISTRATION_KEY)}catch(_){}}
async function finishPendingRegistration(gate,email){
const pending=getPendingRegistration(email);if(!pending||registrationProvisioning)return false;
registrationProvisioning=true;const err=gate?.querySelector('#sunGateErrorV3');if(err)err.textContent='Создаю вашу компанию…';
try{const ws=await cloud()?.createTrialWorkspace?.(pending.companyName||'Новая компания');if(!ws)throw new Error('Не удалось создать компанию.');clearPendingRegistration();if(err)err.textContent='Готово. Открываю Caterium…';setTimeout(async()=>{gate?.remove();document.body.classList.remove('sun-cloud-auth-required');try{await cloud()?.pull?.()}catch(_){}applyNavPermissions();},350);return true;}
catch(e){if(err)err.textContent=errText(e,'Не удалось завершить регистрацию.');return false}
finally{registrationProvisioning=false}
}
const INVITE_QUERY_KEY='invite';
let invitePreviewCache=null;
let invitePreviewLoading=false;
function currentInviteToken(){try{return String(new URL(location.href).searchParams.get(INVITE_QUERY_KEY)||'').trim()}catch(_){return ''}}
function clearInviteFromUrl(){try{const u=new URL(location.href);u.searchParams.delete(INVITE_QUERY_KEY);history.replaceState(null,'',u.toString())}catch(_){}}
function passwordField(id,label,autocomplete='current-password'){
return `<label>${esc(label)}<div class="sun-auth-password"><input id="${id}" type="password" autocomplete="${autocomplete}"><button class="sun-auth-eye" type="button" data-toggle-password="${id}" aria-label="Показать пароль">◉</button></div></label>`;
}
function bindPasswordEyes(root){qa('[data-toggle-password]',root).forEach(b=>b.onclick=()=>{const i=root.querySelector(`#${b.dataset.togglePassword}`);if(!i)return;i.type=i.type==='password'?'text':'password';b.textContent=i.type==='password'?'◉':'×';b.setAttribute('aria-label',i.type==='password'?'Показать пароль':'Скрыть пароль');});}
async function getInvitePreview(token){
if(!token)return null;if(invitePreviewCache?.token===token)return invitePreviewCache.data;if(invitePreviewLoading)return null;
invitePreviewLoading=true;try{const c=client();if(!c)return null;const {data,error}=await c.rpc('sun_invite_preview_v27',{p_token:token});if(error)throw error;const row=Array.isArray(data)?data[0]:data;invitePreviewCache={token,data:row||null};return row||null;}finally{invitePreviewLoading=false;}
}
async function finishInvite(token,gate){
const err=gate?.querySelector('#sunGateErrorV3');if(err)err.textContent='Добавляю вас в компанию…';
try{const ws=await cloud()?.acceptInvite?.(token);if(!ws)throw new Error('Не удалось присоединиться к компании.');clearInviteFromUrl();invitePreviewCache=null;gate?.remove();document.body.classList.remove('sun-cloud-auth-required');try{await cloud()?.pull?.()}catch(_){}applyNavPermissions();toast(`Вы присоединились к компании «${ws.name||'Компания'}».`,'success',6500);return true;}
catch(e){if(err)err.textContent=String(e?.message||e||'Не удалось принять приглашение.');return false;}
}
async function renderInviteGate(gate,token){
let preview;
try{preview=await getInvitePreview(token);}catch(e){gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Приглашение недоступно</h2><div class="hint">Caterium</div></div></div><p class="sun-cloud-auth-error">${esc(e?.message||e||'Не удалось проверить приглашение.')}</p><div class="sun-cloud-auth-actions"><button class="outline" id="sunInviteGoHome" type="button">Перейти ко входу</button></div></div>`;gate.querySelector('#sunInviteGoHome').onclick=()=>{clearInviteFromUrl();gate.remove();ensureAuthGate();};return;}
if(!preview||!preview.is_valid){gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Приглашение недействительно</h2><div class="hint">Ссылка истекла, отменена или уже использована.</div></div></div><div class="sun-cloud-auth-actions"><button class="primary" id="sunInviteGoHome" type="button">Перейти ко входу</button></div></div>`;gate.querySelector('#sunInviteGoHome').onclick=()=>{clearInviteFromUrl();gate.remove();ensureAuthGate();};return;}
const ss=session(),signedEmail=String(ss?.user?.email||'').toLowerCase(),inviteEmail=String(preview.email||'').toLowerCase();
if(ss?.user){
if(signedEmail===inviteEmail){gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Приглашение в ${esc(preview.workspace_name||'компанию')}</h2><div class="hint">${esc(preview.display_name||preview.email||'Сотрудник')} · ${esc(ROLE_LABELS[preview.role]||preview.role)}</div></div></div><p class="hint">Вы вошли как <b>${esc(ss.user.email||'')}</b>. Нажмите кнопку, чтобы добавить эту компанию в ваш аккаунт.</p><div class="sun-cloud-auth-actions"><button class="primary" id="sunInviteAcceptSigned" type="button">Присоединиться к компании</button><button class="outline" id="sunInviteSignOut" type="button">Войти другим аккаунтом</button></div><div class="sun-cloud-auth-error" id="sunGateErrorV3"></div></div>`;gate.querySelector('#sunInviteAcceptSigned').onclick=()=>finishInvite(token,gate);gate.querySelector('#sunInviteSignOut').onclick=async()=>{try{await window.SunCloudV2?.signOut?.()}catch(_){}};return;}
gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Приглашение в ${esc(preview.workspace_name||'компанию')}</h2><div class="hint">Для ${esc(preview.email||'другого email')}</div></div></div><p class="hint">Сейчас вы вошли как <b>${esc(ss.user.email||'')}</b>. Для принятия приглашения нужно войти под адресом <b>${esc(preview.email||'')}</b>.</p><div class="sun-cloud-auth-actions"><button class="primary" id="sunInviteSignOut" type="button">Выйти и продолжить</button></div></div>`;gate.querySelector('#sunInviteSignOut').onclick=async()=>{try{await window.SunCloudV2?.signOut?.()}catch(_){}};return;
}
gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Вас пригласили в ${esc(preview.workspace_name||'компанию')}</h2><div class="hint">Роль: ${esc(ROLE_LABELS[preview.role]||preview.role)}</div></div></div><label>Имя<input id="sunInviteNameV27" autocomplete="name" value="${esc(preview.display_name||'')}"></label><label>Email<input id="sunInviteEmailV27" type="email" value="${esc(preview.email||'')}" readonly></label>${passwordField('sunInvitePasswordV27','Пароль','new-password')}${passwordField('sunInvitePassword2V27','Подтвердите пароль','new-password')}<div class="sun-cloud-auth-actions"><button class="primary" id="sunInviteJoinV27" type="button">Присоединиться</button></div><div class="sun-cloud-auth-error" id="sunGateErrorV3"></div><p class="hint">Если аккаунт с этим email уже есть — введите его пароль. Если аккаунта ещё нет — придумайте новый пароль и повторите его.</p></div>`;
bindPasswordEyes(gate);const btn=gate.querySelector('#sunInviteJoinV27');const run=async()=>{const c=client(),email=String(preview.email||'').trim(),name=String(gate.querySelector('#sunInviteNameV27')?.value||'').trim(),password=String(gate.querySelector('#sunInvitePasswordV27')?.value||''),password2=String(gate.querySelector('#sunInvitePassword2V27')?.value||''),err=gate.querySelector('#sunGateErrorV3');if(password.length<6){err.textContent='Пароль должен содержать минимум 6 символов.';return;}if(password!==password2){err.textContent='Пароли не совпадают.';return;}btn.disabled=true;err.textContent='Проверяю аккаунт…';try{let r=await c.auth.signInWithPassword({email,password});if(r.error){r=await c.auth.signUp({email,password,options:{data:{name,registration_source:'caterium_invite_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error)throw new Error('Этот email уже зарегистрирован. Введите пароль от существующего аккаунта.');r=retry;}}const userId=r.data.user?.id||r.data.session?.user?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');err.textContent='Аккаунт готов. Подключаю компанию…';setTimeout(()=>finishInvite(token,gate),180);}catch(e){err.textContent=String(e?.message||e||'Не удалось войти или создать аккаунт.');btn.disabled=false;}};btn.onclick=run;gate.querySelector('#sunInvitePassword2V27').addEventListener('keydown',e=>{if(e.key==='Enter')run()});
}
// Password changes now happen from account settings (see cloud-sync-v2.js's
// renderCloudUI "Пароль" box) instead of a forced post-login gate: the old
// gate raced with membership loading (loadMemberships finishing and
// ensureAuthGate's periodic poll tearing the gate down as soon as
// signedIn+workspace was true) and could disappear before the employee
// ever saw it.
function ensureAuthGate(){
const st=cloud()?.status?.();if(!st?.connected)return;
const ws=workspace(),ss=session(),inviteToken=currentInviteToken();
if(inviteToken){
let gate=$('sunCloudAuthGateV3');if(!gate){document.body.classList.add('sun-cloud-auth-required');gate=document.createElement('div');gate.id='sunCloudAuthGateV3';gate.className='sun-cloud-auth-gate';gate.innerHTML='<div class="sun-cloud-auth-card"><p class="hint">Проверяю приглашение…</p></div>';document.body.appendChild(gate);renderInviteGate(gate,inviteToken);}return;
}
if(st.signedIn&&ws){$('sunCloudAuthGateV3')?.remove();document.body.classList.remove('sun-cloud-auth-required');try{localStorage.removeItem(LOCAL_ONLY_KEY)}catch(_){}return;}
if(!st.signedIn){try{if(localStorage.getItem(LOCAL_ONLY_KEY)==='1'){$('sunCloudAuthGateV3')?.remove();document.body.classList.remove('sun-cloud-auth-required');return;}}catch(_){}}
if($('sunCloudAuthGateV3')){if(st.signedIn&&st.membershipsLoading){const e=$('sunGateErrorV3');if(e)e.textContent='Загружаю рабочую базу…';}return;}
document.body.classList.add('sun-cloud-auth-required');
const gate=document.createElement('div');gate.id='sunCloudAuthGateV3';gate.className='sun-cloud-auth-gate';
if(st.signedIn&&ss?.user&&!ws){
const pending=getPendingRegistration(ss.user.email||'');
gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>${pending?'Создаю компанию':st.membershipsLoading?'Загружаю рабочую базу':'Аккаунт готов'}</h2><div class="hint">${esc(ss.user.email||'Аккаунт авторизован')}</div></div></div><p class="hint">${pending?'Регистрация завершена. Сейчас подготовим вашу рабочую компанию.':st.membershipsLoading?'Проверяю доступы этого аккаунта на сервере.':'У этого аккаунта пока нет компании. Укажите название, чтобы создать её.'}</p>${!pending&&!st.membershipsLoading?'<label>Название компании<input id="sunGateRecoveryCompanyV25" autocomplete="organization" value="Моя компания"></label>':''}<div class="sun-cloud-auth-actions"><button class="primary" id="sunGateRetryWorkspaceV3" type="button">${pending?'Продолжить':st.membershipsLoading?'Проверить ещё раз':'Создать компанию'}</button><button class="outline" id="sunGateSignOutV3" type="button">Выйти</button></div><div class="sun-cloud-auth-error" id="sunGateErrorV3">${pending?'Подготавливаю компанию…':st.membershipsLoading?'Загружаю рабочую базу…':''}</div></div>`;
document.body.appendChild(gate);if(pending&&!st.membershipsLoading)setTimeout(()=>finishPendingRegistration(gate,ss.user.email||''),80);
gate.querySelector('#sunGateRetryWorkspaceV3').onclick=async()=>{const e=gate.querySelector('#sunGateErrorV3');if(getPendingRegistration(ss.user.email||''))return finishPendingRegistration(gate,ss.user.email||'');if(!st.membershipsLoading){const companyName=String(gate.querySelector('#sunGateRecoveryCompanyV25')?.value||'').trim();if(!companyName){e.textContent='Введите название компании.';return;}savePendingRegistration(ss.user.email||'',companyName);return finishPendingRegistration(gate,ss.user.email||'');}e.textContent='Проверяю доступ…';try{const found=await cloud()?.reloadMemberships?.();if(found){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();setTimeout(applyNavPermissions,100);}else e.textContent='Рабочая база для этого аккаунта не найдена.';}catch(err){e.textContent=errText(err,'Не удалось проверить доступ.');}};
gate.querySelector('#sunGateSignOutV3').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut();}catch(_){}location.reload();};return;
}
gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2 id="sunGateTitleV3">Вход в Caterium</h2><div class="hint" id="sunGateSubtitleV3">Введите данные своего аккаунта</div></div></div><div id="sunGateRegisterFieldsV27" hidden><label>Название компании<input id="sunGateCompanyV3" autocomplete="organization" placeholder="Например, Мой Кейтеринг"></label></div><label>Email<input id="sunGateEmailV3" type="email" autocomplete="username"></label>${passwordField('sunGatePasswordV3','Пароль','current-password')}<div id="sunGateConfirmWrapV27" hidden>${passwordField('sunGatePassword2V27','Подтвердите пароль','new-password')}</div><div class="sun-cloud-auth-actions"><button class="primary" id="sunGateSubmitV3" type="button">Войти</button></div><div class="sun-cloud-auth-error" id="sunGateErrorV3"></div><p class="sun-auth-switch"><span id="sunGateSwitchTextV27">Нет аккаунта?</span> <button type="button" id="sunGateSwitchV27">Создать аккаунт</button></p><p class="sun-auth-switch"><button type="button" id="sunGateLocalOnlyV1" style="background:none;border:0;padding:0;color:inherit;opacity:.6;text-decoration:underline;cursor:pointer;font:inherit">Работать локально без интернета</button></p></div>`;document.body.appendChild(gate);bindPasswordEyes(gate);
gate.querySelector('#sunGateLocalOnlyV1').onclick=()=>{try{localStorage.setItem(LOCAL_ONLY_KEY,'1')}catch(_){}location.reload();};
const emailInput=gate.querySelector('#sunGateEmailV3'),passwordInput=gate.querySelector('#sunGatePasswordV3'),password2Input=gate.querySelector('#sunGatePassword2V27'),companyInput=gate.querySelector('#sunGateCompanyV3'),submitBtn=gate.querySelector('#sunGateSubmitV3'),gateError=gate.querySelector('#sunGateErrorV3');let registerMode=false;
const setMode=(registration)=>{registerMode=Boolean(registration);gate.querySelector('#sunGateRegisterFieldsV27').hidden=!registerMode;gate.querySelector('#sunGateConfirmWrapV27').hidden=!registerMode;gate.querySelector('#sunGateTitleV3').textContent=registerMode?'Создать аккаунт Caterium':'Вход в Caterium';gate.querySelector('#sunGateSubtitleV3').textContent=registerMode?'Заполните данные — после регистрации Caterium откроется автоматически':'Введите данные своего аккаунта';submitBtn.textContent=registerMode?'Создать аккаунт':'Войти';gate.querySelector('#sunGateSwitchTextV27').textContent=registerMode?'Уже есть аккаунт?':'Нет аккаунта?';gate.querySelector('#sunGateSwitchV27').textContent=registerMode?'Войти':'Создать аккаунт';passwordInput.autocomplete=registerMode?'new-password':'current-password';gateError.textContent='';if(registerMode)setTimeout(()=>companyInput.focus(),30);else setTimeout(()=>emailInput.focus(),30)};
async function auth(){const c=client(),email=emailInput.value.trim(),password=passwordInput.value,password2=password2Input.value,companyName=String(companyInput.value||'').trim();if(!c){gateError.textContent='Облачный сервис не подключён.';return;}if(registerMode&&!companyName){gateError.textContent='Введите название компании.';return;}if(!email||password.length<6){gateError.textContent='Введите email и пароль минимум из 6 символов.';return;}if(registerMode&&password!==password2){gateError.textContent='Пароли не совпадают.';return;}submitBtn.disabled=true;gateError.textContent=registerMode?'Создаю аккаунт…':'Выполняю вход…';try{let r;if(registerMode){savePendingRegistration(email,companyName);const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data.session){r=existing;}else{r=await c.auth.signUp({email,password,options:{data:{company_name:companyName,registration_source:'caterium_public_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error){clearPendingRegistration();throw new Error('Этот email уже зарегистрирован. Войдите в аккаунт или восстановите пароль.');}r=retry;}}}else r=await c.auth.signInWithPassword({email,password});if(r.error)throw r.error;const authUser=r.data.user||r.data.session?.user||null;const userId=authUser?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');gateError.textContent=registerMode?'Аккаунт готов. Создаю компанию…':'Вход выполнен. Загружаю рабочую базу…';setTimeout(async()=>{await cloud()?.reloadMemberships?.();if(registerMode&&workspace()){clearPendingRegistration();gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();return;}if(getPendingRegistration(email)&&!workspace()){await finishPendingRegistration(gate,email);return;}if(workspace()){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();}else{gate.remove();ensureAuthGate();}},180);}catch(err){gateError.textContent=errText(err,'Не удалось выполнить операцию.')}finally{submitBtn.disabled=false}}
gate.querySelector('#sunGateSwitchV27').onclick=()=>setMode(!registerMode);submitBtn.onclick=auth;passwordInput.addEventListener('keydown',e=>{if(e.key==='Enter'&&!registerMode)auth()});password2Input.addEventListener('keydown',e=>{if(e.key==='Enter'&&registerMode)auth()});setMode(false);
}
function onCloudChanged(){
if(applying)return;applying=true;
try{
const ws=workspace();if(ws?.id!==lastWorkspaceId){lastWorkspaceId=ws?.id||'';}
ensureAuthGate();normalizeLegacyLocalAuth();installGuards();applyNavPermissions();hideLegacyRoleCard();injectQuickPinCard();showPinLockIfNeeded();
}finally{applying=false;}
}
window.addEventListener('sun:open-cloud-users',openModal);
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(onCloudChanged,0));
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')setTimeout(()=>{hideLegacyRoleCard();injectQuickPinCard();},120);},true);
(()=>{let queued=false;const run=()=>{if(queued||applying)return;queued=true;setTimeout(()=>{queued=false;if(!applying){applyNavPermissions();hideLegacyRoleCard();}},60);};[document.querySelector('header'),document.getElementById('enterprise-settings')].filter(Boolean).forEach(root=>new MutationObserver(run).observe(root,{childList:true,subtree:true}));})();
installStyle();normalizeLegacyLocalAuth();ensureModal();
let tries=0;const boot=setInterval(()=>{tries++;if(window.SunCloudV2){clearInterval(boot);setTimeout(onCloudChanged,80);}else if(tries>80)clearInterval(boot);},100);
setInterval(()=>{if(document.hidden)return;try{ensureAuthGate();if(workspace()&&session()?.user){applyNavPermissions();injectQuickPinCard();showPinLockIfNeeded();}}catch(_){}},5000);
window.SunAdminRBACV3={VERSION,openUsers:openModal,apply:applyNavPermissions,hasPermission:has};
})();
;
/* ===== MODULE: order-payment-ui-v1.js ===== */
(()=>{
'use strict';
if(window.__sunOrderPaymentUIV1)return;
window.__sunOrderPaymentUIV1=true;
const KEY='sunOrderPaymentColorsV1';
const DEFAULTS={
unpaid:'#d84a4a',
prepaid:'#35a46b',
paid:'#3b82f6',
cancelled:'#9aa0aa'
};
const LABELS={
unpaid:'Без предоплаты',
prepaid:'Предоплата внесена',
paid:'Оплачен полностью',
cancelled:'Отменён'
};
const $=id=>document.getElementById(id);
const hex=value=>/^#[0-9a-f]{6}$/i.test(String(value||''))?String(value).toLowerCase():null;
const readColors=()=>{
let saved={};
try{saved=JSON.parse(localStorage.getItem(KEY)||'{}')||{}}catch(_){saved={}}
const next={};
Object.keys(DEFAULTS).forEach(key=>next[key]=hex(saved[key])||DEFAULTS[key]);
return next;
};
const saveColors=colors=>{
const safe={};Object.keys(DEFAULTS).forEach(key=>safe[key]=hex(colors[key])||DEFAULTS[key]);
localStorage.setItem(KEY,JSON.stringify(safe));
decorateOrders();
return safe;
};
const liveOrders=()=>{try{return Array.isArray(orders)?orders:JSON.parse(localStorage.getItem('sunOrders')||'[]')}catch(_){return[]}};
const byId=id=>liveOrders().find(order=>String(order?.id)===String(id));
const orderTotal=order=>{
try{if(typeof window.orderTotalValue==='function'){const value=Number(window.orderTotalValue(order));if(Number.isFinite(value))return Math.max(0,value)}}catch(_){}
const direct=Number(order?.total);return Number.isFinite(direct)?Math.max(0,direct):0;
};
function paymentState(order){
if(String(order?.status||'').trim()==='Отменён')return'cancelled';
const paid=Math.max(0,Number(order?.prepayment||0));
const total=orderTotal(order);
if(total>0&&paid>=total-0.5)return'paid';
if(paid>0)return'prepaid';
return'unpaid';
}
window.sunOrderPaymentState=paymentState;
window.sunOrderPaymentColors=()=>({...readColors()});
function readableText(color){
const value=hex(color)||'#000000';
const r=parseInt(value.slice(1,3),16),g=parseInt(value.slice(3,5),16),b=parseInt(value.slice(5,7),16);
return ((r*299+g*587+b*114)/1000)>168?'#252a32':'#ffffff';
}
function shadowColor(color,alpha=.24){
const value=hex(color)||'#000000';
const r=parseInt(value.slice(1,3),16),g=parseInt(value.slice(3,5),16),b=parseInt(value.slice(5,7),16);
return `rgba(${r},${g},${b},${alpha})`;
}
function paintDateTile(tile,order){
if(!tile||!order)return;
const state=paymentState(order),colors=readColors(),color=colors[state],text=readableText(color);
tile.dataset.paymentState=state;
tile.title=LABELS[state];
tile.setAttribute('aria-label',`${tile.textContent.trim()}: ${LABELS[state]}`);
tile.style.setProperty('background',color,'important');
tile.style.setProperty('border-color',color,'important');
tile.style.setProperty('box-shadow',`0 5px 14px ${shadowColor(color)}`,'important');
tile.style.setProperty('color',text,'important');
tile.querySelectorAll('strong,span,small').forEach(node=>node.style.setProperty('color',text,'important'));
}
function decorateOrders(){
document.querySelectorAll('#ordersList .sun-order-payment-marker,#sunOrderCards .sun-order-payment-marker').forEach(marker=>marker.remove());
document.querySelectorAll('#ordersList .sun-order-number-cell').forEach(cell=>cell.classList.remove('sun-order-number-cell'));
document.querySelectorAll('#sunOrderCards .sun-order-number-with-payment').forEach(host=>host.classList.remove('sun-order-number-with-payment'));
document.querySelectorAll('#sunOrderCards .sun-order-card').forEach(card=>{
const order=byId(card.dataset.orderId),tile=card.querySelector('.sun-order-card-date');
if(order&&tile)paintDateTile(tile,order);
});
}
let decorateQueued=false;
function queueDecorate(){if(decorateQueued)return;decorateQueued=true;requestAnimationFrame(()=>{decorateQueued=false;decorateOrders()})}
const priorRenderOrders=window.renderOrders;
if(typeof priorRenderOrders==='function')window.renderOrders=function(...args){const result=priorRenderOrders.apply(this,args);queueDecorate();return result};
// One authoritative deletion path for list, cards and the current order form.
window.deleteOrderById=function(id){
const list=liveOrders();
const order=list.find(item=>String(item?.id)===String(id));
if(!order)return false;
if(!confirm(`Удалить заказ №${order.id}${order.event?` «${order.event}»`:''}?`))return false;
try{
if(Array.isArray(orders)){
const index=orders.findIndex(item=>String(item?.id)===String(id));
if(index>=0)orders.splice(index,1);
}else{
localStorage.setItem('sunOrders',JSON.stringify(list.filter(item=>String(item?.id)!==String(id))));
}
if(typeof persist==='function')persist();
else localStorage.setItem('sunOrders',JSON.stringify(Array.isArray(orders)?orders:list.filter(item=>String(item?.id)!==String(id))));
}catch(error){
console.error('Order deletion failed',error);
window.SunEnterprise?.toast?.('Не удалось удалить заказ.','error',5000);
return false;
}
try{window.SunEnterprise?.recordAudit?.('Удалён заказ',`${order.id} · ${order.event||'Заказ'}`)}catch(_){}
const currentId=(()=>{try{return draft?.id}catch(_){return null}})();
if(String(currentId)===String(id)&&typeof window.resetDraft==='function')window.resetDraft();
try{window.renderOrders?.()}catch(_){}
try{window.renderStats?.()}catch(_){}
try{window.sunRenderOrderCards?.()}catch(_){}
window.dispatchEvent(new CustomEvent('sun:order-deleted',{detail:{id:order.id}}));
window.SunEnterprise?.toast?.(`Заказ №${order.id} удалён.`,'success');
return true;
};
window.deleteOrder=function(){
const id=(()=>{try{return draft?.id}catch(_){return null}})();
if(!id)return false;
const deleted=window.deleteOrderById(id);
if(!deleted)return false;
const button=[...document.querySelectorAll('header nav button')].find(btn=>String(btn.dataset.navLabel||btn.textContent||'').trim()==='Заказы');
if(button)button.click();
else if(typeof window.show==='function')try{window.show('orders',document.querySelectorAll('header nav button')[1])}catch(_){}
return true;
};
function settingsCard(){
const view=$('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');
if(!grid)return null;
let card=$('sunOrderPaymentColorsCard');
if(card)return card;
card=document.createElement('section');
card.className='enterprise-card';card.id='sunOrderPaymentColorsCard';
grid.appendChild(card);
renderSettingsCard(card);
return card;
}
function renderSettingsCard(card=settingsCard()){
if(!card)return;
const colors=readColors();
card.innerHTML=`<h2>Цвета оплаты заказов</h2><p class="hint">Цвет плашки с датой в разделе «Заказы» показывает состояние оплаты. Цвет каждого состояния можно выбрать самостоятельно.</p><div class="sun-payment-color-settings">${Object.keys(DEFAULTS).map(key=>`<label><span><i class="sun-payment-settings-dot" style="background:${colors[key]}"></i>${LABELS[key]}</span><input type="color" data-payment-color="${key}" value="${colors[key]}" aria-label="Цвет: ${LABELS[key]}"></label>`).join('')}</div><div class="actions"><button class="outline" id="sunResetPaymentColors" type="button">Вернуть стандартные цвета</button></div><p class="hint">Правила: 0 ₽ предоплаты — «Без предоплаты»; предоплата меньше суммы — «Предоплата внесена»; оплачена вся сумма — «Оплачен полностью»; статус «Отменён» — отдельный цвет.</p>`;
card.querySelectorAll('[data-payment-color]').forEach(input=>input.addEventListener('input',()=>{
const next=readColors();next[input.dataset.paymentColor]=input.value;saveColors(next);
const dot=input.closest('label')?.querySelector('.sun-payment-settings-dot');if(dot)dot.style.background=input.value;
}));
$('sunResetPaymentColors')?.addEventListener('click',()=>{localStorage.setItem(KEY,JSON.stringify(DEFAULTS));renderSettingsCard(card);decorateOrders();window.SunEnterprise?.toast?.('Стандартные цвета восстановлены.','success')});
}
function ensureSettingsCard(){const card=settingsCard();if(card)renderSettingsCard(card)}
const style=document.createElement('style');style.id='sun-order-payment-ui-v1-style';style.textContent=`
#orderForm .sun-order-line-price input{width:100%!important;max-width:104px!important;min-width:78px!important;padding:2px 7px!important;font-variant-numeric:tabular-nums!important;letter-spacing:.01em!important}
.sun-payment-color-settings{display:grid;gap:8px;margin-top:10px}
.sun-payment-color-settings label{display:grid;grid-template-columns:minmax(0,1fr) 52px;gap:10px;align-items:center;padding:8px 9px;border:1px solid #e3e6e9;border-radius:9px;background:#fafbfc}
.sun-payment-color-settings label>span{display:flex;align-items:center;gap:8px;color:#3d4652;font-weight:700;font-size:12.5px}
.sun-payment-color-settings input[type="color"]{width:48px;height:32px;padding:2px;border:1px solid #cfd5da;border-radius:7px;background:#fff;cursor:pointer}
.sun-payment-settings-dot{display:inline-block;width:13px;height:13px;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12)}
`;document.head.appendChild(style);
document.addEventListener('click',event=>{
const nav=event.target.closest('header nav button');
if(nav&&String(nav.dataset.navLabel||nav.textContent||'').trim()==='Настройки')setTimeout(ensureSettingsCard,60);
},true);
const ordersHost=$('orders');if(ordersHost)new MutationObserver(queueDecorate).observe(ordersHost,{childList:true,subtree:true});
const settingsHost=$('enterprise-settings');if(settingsHost)new MutationObserver(()=>{if(settingsHost.classList.contains('on'))setTimeout(()=>{if(!$('sunOrderPaymentColorsCard'))ensureSettingsCard()},0)}).observe(settingsHost,{childList:true,subtree:true});
window.addEventListener('sun:cloud-state-applied',()=>{queueDecorate();if($('enterprise-settings')?.classList.contains('on'))setTimeout(ensureSettingsCard,0)});
window.addEventListener('storage',event=>{if(event.key===KEY)queueDecorate()});
queueDecorate();
setTimeout(()=>{queueDecorate();if($('enterprise-settings')?.classList.contains('on'))ensureSettingsCard()},140);
})();
;
/* ===== MODULE: order-crm-v1.js ===== */
(()=>{
'use strict';
if(window.__sunOrderCrmV1)return;window.__sunOrderCrmV1=true;
const $=id=>document.getElementById(id),qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=window.SunSafe.escapeHTML;
const SOURCE_KEY='sunLeadSourcesV1';
const DEFAULT_SOURCES=['Заявка с сайта','CaterMe','Avito','Рекомендация','Повторный клиент','Соцсети','Другое'];
const getDraft=()=>{try{return draft}catch(_){return null}};
const getOrders=()=>{try{return Array.isArray(orders)?orders:[]}catch(_){try{const x=JSON.parse(localStorage.getItem('sunOrders')||'[]');return Array.isArray(x)?x:[]}catch(__){return[]}}};
const money=v=>typeof window.money==='function'?window.money(v):`${Math.round(Number(v||0)).toLocaleString('ru-RU')}`;
const toast=(m,t='success')=>{try{if(window.SunEnterprise?.toast)return window.SunEnterprise.toast(m,t)}catch(_){} if(t==='warn'||t==='error')console.warn(m)};
const style=document.createElement('style');style.id='sun-order-crm-v1-style';style.textContent=`
/* The precise-address fields existed in an older module but were hidden by a later polish layer. */
#details .sun-address-precision-title{display:block!important;grid-column:1/-1;margin:10px 0 0;font-size:13px;font-weight:850;color:#241d45}
#details .sun-delivery-region,#details .sun-delivery-locality,#details .sun-delivery-district{display:flex!important}
#details .sun-delivery-district{grid-column:1/-1!important}
#details .sun-lead-source-field,#details .sun-known-addresses-field{grid-column:1/-1}
#details .sun-client-match{grid-column:1/-1;display:none;padding:9px 11px;border:1px solid #d7d0e7;border-radius:11px;background:#f7f5fb;color:#514b68;font-size:12px;line-height:1.35}
#details .sun-client-match.on{display:block}
#details .sun-geo-confirm{display:none;margin-top:10px;padding:11px;border:1px solid #dfd9eb;border-radius:12px;background:#faf9fd}
#details .sun-geo-confirm.on{display:block}
#details .sun-geo-confirm strong{display:block;color:#241d45;font-size:12px;margin-bottom:5px}
#details .sun-geo-confirm p{margin:0 0 9px;color:#666078;font-size:11px;line-height:1.4}
#details .sun-geo-confirm .actions{display:flex;gap:7px;flex-wrap:wrap;margin:0}
#details .sun-geo-confirm.ok{border-color:#b8dcc4;background:#f3fbf5}
#details .sun-geo-confirm.ok strong{color:#28683d}
.sun-crm-stats-table{width:100%;border-collapse:collapse;font-size:12px}
.sun-crm-stats-table th,.sun-crm-stats-table td{padding:9px 8px;border-bottom:1px solid #ece9f2;text-align:right;white-space:nowrap}
.sun-crm-stats-table th:first-child,.sun-crm-stats-table td:first-child{text-align:left;white-space:normal}
.sun-crm-stats-table th{color:#817a94;font-size:10px;text-transform:uppercase;letter-spacing:.04em}
.sun-crm-stats-table td b{color:#241d45}
.sun-lead-settings-list{display:grid;gap:7px;margin:10px 0}
.sun-lead-settings-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px}
@media(max-width:700px){.sun-crm-stats-table{font-size:10px}.sun-crm-stats-table th,.sun-crm-stats-table td{padding:7px 4px}.sun-crm-hide-mobile{display:none}}
`;document.head.appendChild(style);
function readSources(){
try{const a=JSON.parse(localStorage.getItem(SOURCE_KEY)||'null');if(Array.isArray(a)){const clean=a.map(x=>String(x||'').trim()).filter(Boolean);if(clean.length)return [...new Set(clean)]}}catch(_){}
return DEFAULT_SOURCES.slice();
}
function saveSources(list){
const clean=[...new Set((list||[]).map(x=>String(x||'').trim()).filter(Boolean))];
localStorage.setItem(SOURCE_KEY,JSON.stringify(clean.length?clean:DEFAULT_SOURCES));
refreshSourceSelect();renderCrmStats();
}
function setSelectOptions(select,values,current=''){
if(!select)return;const cur=String(current||select.value||'');
const all=values.slice();if(cur&&!all.includes(cur))all.push(cur);
select.innerHTML=`<option value="">Не указано</option>`+all.map(v=>`<option value="${esc(v)}">${esc(v)}</option>`).join('');select.value=cur;
}
function ensureSourceField(){
const grid=document.querySelector('#details .sun-delivery-details');if(!grid)return null;
let select=$('orderLeadSource');if(!select){
const label=document.createElement('label');label.className='sun-lead-source-field';label.innerHTML='<span>Узнали из</span><select id="orderLeadSource"></select>';
const loyalty=$('orderLoyaltyCard')?.closest('label'),phone=$('phone')?.closest('label');
(loyalty||phone)?.insertAdjacentElement('afterend',label);if(!label.isConnected)window.SunSafe.insertBefore(grid,label,grid.firstChild);
select=label.querySelector('select');select.addEventListener('change',()=>{const d=getDraft();if(d)d.leadSource=select.value});
}
setSelectOptions(select,readSources(),getDraft()?.leadSource||select.value);return select;
}
function refreshSourceSelect(){const s=ensureSourceField();if(s)setSelectOptions(s,readSources(),getDraft()?.leadSource||s.value)}
function ensureSettingsCard(){
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;
let card=$('sunLeadSourcesSettingsCard');
if(!card){card=document.createElement('section');card.id='sunLeadSourcesSettingsCard';card.className='enterprise-card';card.innerHTML=`<h2>Источники клиентов</h2><p class="hint">Список для поля «Узнали из» в деталях заказа. Эти данные используются в статистике.</p><div class="sun-lead-settings-list" id="sunLeadSourcesRows"></div><div class="actions"><button class="outline" id="sunLeadSourceAdd" type="button">Добавить источник</button><button class="primary" id="sunLeadSourcesSave" type="button">Сохранить список</button></div>`;
const eventCard=$('sunEventTypesSettingsCard');if(eventCard)eventCard.insertAdjacentElement('afterend',card);else grid.appendChild(card);
$('sunLeadSourceAdd').onclick=()=>addSourceSettingsRow('');$('sunLeadSourcesSave').onclick=()=>saveSources(qa('#sunLeadSourcesRows input').map(x=>x.value));
}
const rows=$('sunLeadSourcesRows');if(rows&&!rows.dataset.ready){rows.dataset.ready='1';renderSourceSettingsRows()}
}
function addSourceSettingsRow(value=''){
const rows=$('sunLeadSourcesRows');if(!rows)return;const row=document.createElement('div');row.className='sun-lead-settings-row';row.innerHTML=`<input value="${esc(value)}" placeholder="Название источника"><button class="outline" type="button">Убрать</button>`;row.querySelector('button').onclick=()=>row.remove();rows.appendChild(row);row.querySelector('input').focus();
}
function renderSourceSettingsRows(){const rows=$('sunLeadSourcesRows');if(!rows)return;rows.innerHTML='';readSources().forEach(addSourceSettingsRow)}
function normalizePhone(raw){
let d=String(raw||'').replace(/\D/g,'');if(d.length===11&&d[0]==='8')d='7'+d.slice(1);else if(d.length===10)d='7'+d;return d;
}
function orderStamp(o){return `${o?.date||''}T${o?.time||''}|${String(o?.id||'').padStart(12,'0')}`}
function structuredAddress(o){
const keys=['deliveryRegion','deliveryLocality','deliveryDistrict','deliveryStreet','deliveryHouse','deliveryCorpus','deliveryEntrance','deliveryFloor','deliveryApartment','deliveryOffice'];
const data={};keys.forEach(k=>data[k]=String(o?.[k]||'').trim());data.address=String(o?.address||'').trim();
data.structured=keys.some(k=>data[k]);return data;
}
function profileFromOrders(list){
const sorted=list.slice().sort((a,b)=>orderStamp(b).localeCompare(orderStamp(a)));if(!sorted.length)return null;
const latest=sorted[0],seen=new Set(),addresses=[];
sorted.forEach(o=>{const a=structuredAddress(o),key=a.structured?JSON.stringify(a):a.address;if(key&&!seen.has(key)){seen.add(key);addresses.push(a)}});
return {name:String(latest.contact||''),phone:String(latest.phone||''),ordersCount:sorted.length,latest,addresses};
}
function profileByPhone(raw){const n=normalizePhone(raw);if(n.length<10)return null;return profileFromOrders(getOrders().filter(o=>normalizePhone(o.phone)===n))}
function readLoyaltyStore(){try{const v=JSON.parse(localStorage.getItem('sunClientLoyaltyV1')||'{}');return v&&typeof v==='object'?v:{}}catch(_){return{}}}
function profileByCard(raw){
const card=String(raw||'').trim().toLowerCase();if(!card)return null;
let direct=getOrders().filter(o=>String(o.loyaltyCardNumber||o.loyaltyCard||'').trim().toLowerCase()===card);if(direct.length)return profileFromOrders(direct);
const store=readLoyaltyStore(),entry=Object.entries(store).find(([,v])=>String(v?.cardNumber||'').trim().toLowerCase()===card);if(!entry)return null;
const key=entry[0];if(key.startsWith('p:'))return profileByPhone(key.slice(2));
if(key.startsWith('n:')){const name=key.slice(2).trim().toLowerCase();return profileFromOrders(getOrders().filter(o=>String(o.contact||'').trim().toLowerCase()===name))}
return null;
}
const addrIds=['deliveryRegion','deliveryLocality','deliveryDistrict','deliveryStreet','deliveryHouse','deliveryCorpus','deliveryEntrance','deliveryFloor','deliveryApartment','deliveryOffice'];
function setSelectValue(el,value){if(!el)return;const v=String(value||'');if(v&&el.tagName==='SELECT'&&![...el.options].some(o=>o.value===v))el.add(new Option(v,v));el.value=v}
function composeFullAddress(){
const val=id=>String($(id)?.value||'').trim();const address=$('address');if(!address)return'';
const parts=[val('deliveryRegion'),val('deliveryLocality'),val('deliveryDistrict'),val('deliveryStreet'),val('deliveryHouse')?`д. ${val('deliveryHouse')}`:'',val('deliveryCorpus')?`корп. ${val('deliveryCorpus')}`:'',val('deliveryEntrance')?`подъезд ${val('deliveryEntrance')}`:'',val('deliveryFloor')?`этаж ${val('deliveryFloor')}`:'',val('deliveryApartment')?`кв. ${val('deliveryApartment')}`:'',val('deliveryOffice')?`офис ${val('deliveryOffice')}`:''].filter(Boolean);
const full=parts.join(', ');address.value=full;const d=getDraft();if(d){addrIds.forEach(id=>d[id]=val(id));d.address=full}return full;
}
window.sunComposePreciseDeliveryAddress=composeFullAddress;
function fillAddress(a){
if(!a)return;const has=Boolean(a.structured);
addrIds.forEach(id=>{const el=$(id);if(!el)return;const v=has?String(a[id]||''):(id==='deliveryStreet'?String(a.address||''):'');if(id==='deliveryRegion')setSelectValue(el,v);else el.value=v});
composeFullAddress();resetGeoConfirmation(true);
}
function ensureClientUi(){
const grid=document.querySelector('#details .sun-delivery-details');if(!grid)return;
if(!$('sunClientMatch')){const box=document.createElement('div');box.id='sunClientMatch';box.className='sun-client-match';const lead=$('orderLeadSource')?.closest('label');(lead||grid.firstElementChild)?.insertAdjacentElement('afterend',box);if(!box.isConnected)window.SunSafe.insertBefore(grid,box,grid.firstChild)}
if(!$('sunKnownAddresses')){const label=document.createElement('label');label.className='sun-known-addresses-field';label.style.display='none';label.innerHTML='<span>Предыдущие адреса клиента</span><select id="sunKnownAddresses"></select>';const office=$('deliveryOffice')?.closest('label');office?.insertAdjacentElement('afterend',label);if(!label.isConnected)grid.appendChild(label);label.querySelector('select').onchange=e=>{const a=e.target._addresses?.[Number(e.target.value)];if(a)fillAddress(a)}}
}
function showProfile(profile,reason){
ensureClientUi();const box=$('sunClientMatch'),known=$('sunKnownAddresses'),knownLabel=known?.closest('label');if(!profile){box?.classList.remove('on');if(knownLabel)knownLabel.style.display='none';return false}
const contact=$('contact'),phone=$('phone');if(contact&&profile.name)contact.value=profile.name;if(phone&&profile.phone)phone.value=profile.phone;
if(profile.addresses[0])fillAddress(profile.addresses[0]);
if(known&&knownLabel){known._addresses=profile.addresses;known.innerHTML=profile.addresses.map((a,i)=>`<option value="${i}">${esc(a.address||[a.deliveryLocality,a.deliveryStreet,a.deliveryHouse].filter(Boolean).join(', ')||`Адрес ${i+1}`)}</option>`).join('');knownLabel.style.display=profile.addresses.length>1?'flex':'none'}
if(box){box.innerHTML=`Клиент найден по ${reason}: <b>${esc(profile.name||profile.phone||'клиент')}</b> · заказов: ${profile.ordersCount}${profile.addresses.length>1?` · сохранено адресов: ${profile.addresses.length}`:''}. Данные заполнены автоматически, адрес можно изменить.`;box.classList.add('on')}
const d=getDraft();if(d){d.contact=contact?.value||'';d.phone=phone?.value||''}return true;
}
let lookupTimer=0;
function schedulePhoneLookup(){clearTimeout(lookupTimer);lookupTimer=setTimeout(()=>{const d=getDraft();if(d?.id)return;const p=profileByPhone($('phone')?.value);if(p)showProfile(p,'телефону')},260)}
function lookupCard(){const d=getDraft();if(d?.id)return;const field=$('orderLoyaltyCard'),p=profileByCard(field?.value);if(p)showProfile(p,'карте лояльности')}
function totalFor(o){if(Number.isFinite(Number(o?.total))&&Number(o.total)>0)return Number(o.total);try{return typeof window.orderTotalValue==='function'?Number(window.orderTotalValue(o)||0):0}catch(_){return 0}}
function statsPeriodOrders(){
const period=$('statsPeriod')?.value||'year',now=new Date();now.setHours(12,0,0,0);let from=null,to=null;
if(period==='month'){from=new Date(now.getFullYear(),now.getMonth(),1,12);to=new Date(now.getFullYear(),now.getMonth()+1,1,12)}
else if(period==='90'){from=new Date(now);from.setDate(from.getDate()-89);to=new Date(now);to.setDate(to.getDate()+1)}
else if(period==='year'){from=new Date(now.getFullYear(),now.getMonth()-11,1,12);to=new Date(now.getFullYear(),now.getMonth()+1,1,12)}
return getOrders().filter(o=>{if(String(o.status||'Новый')==='Отменён')return false;if(!from)return true;const d=o.date?new Date(`${o.date}T12:00:00`):null;return d&&!Number.isNaN(d.getTime())&&d>=from&&d<to});
}
function groupedStats(list,keyFn){
const m=new Map();list.forEach(o=>{const name=String(keyFn(o)||'').trim()||'Не указано',r=m.get(name)||{name,count:0,sum:0};r.count++;r.sum+=totalFor(o);m.set(name,r)});return [...m.values()].map(r=>({...r,avg:r.count?r.sum/r.count:0,share:list.length?r.count/list.length*100:0})).sort((a,b)=>b.count-a.count||b.sum-a.sum||a.name.localeCompare(b.name,'ru'));
}
function statTable(rows){return rows.length?`<div style="overflow:auto"><table class="sun-crm-stats-table"><thead><tr><th>Категория</th><th>Заказов</th><th>Доля</th><th>Выручка</th><th class="sun-crm-hide-mobile">Средний чек</th></tr></thead><tbody>${rows.map(r=>`<tr><td><b>${esc(r.name)}</b></td><td>${r.count}</td><td>${r.share.toLocaleString('ru-RU',{maximumFractionDigits:1})}%</td><td>${esc(money(r.sum))}</td><td class="sun-crm-hide-mobile">${esc(money(r.avg))}</td></tr>`).join('')}</tbody></table></div>`:'<div class="stats-empty">Данных пока нет.</div>'}
function renderCrmStats(){
const grid=$('statsList')?.querySelector('.stats-grid');if(!grid)return;grid.querySelectorAll('.sun-crm-stats-panel').forEach(x=>x.remove());const list=statsPeriodOrders();
const eventPanel=document.createElement('section');eventPanel.className='stats-panel wide sun-crm-stats-panel';eventPanel.innerHTML=`<div class="stats-panel-head"><h2>Типы мероприятий</h2><small>количество · доля · выручка · средний чек</small></div>${statTable(groupedStats(list,o=>o.event))}`;
const sourcePanel=document.createElement('section');sourcePanel.className='stats-panel wide sun-crm-stats-panel';sourcePanel.innerHTML=`<div class="stats-panel-head"><h2>Откуда приходят заказы</h2><small>по полю «Узнали из»</small></div>${statTable(groupedStats(list,o=>o.leadSource))}`;
grid.append(eventPanel,sourcePanel);
}
function resetGeoConfirmation(clear=false){const d=getDraft();if(d?.geo){if(clear)d.geo=null;else{delete d.geo.confirmed;delete d.geo.confirmedAt}}syncGeoConfirmUi()}
function ensureGeoConfirmUi(){
const box=$('orderGeoBox');if(!box)return null;let ui=$('sunGeoConfirm');if(!ui){ui=document.createElement('div');ui.id='sunGeoConfirm';ui.className='sun-geo-confirm';ui.innerHTML='<strong id="sunGeoFoundTitle">Найденный адрес</strong><p id="sunGeoFoundText"></p><div class="actions"><button type="button" class="primary" id="sunGeoConfirmOk">Адрес верный</button><button type="button" class="outline" id="sunGeoConfirmEdit">Уточнить адрес</button></div>';box.appendChild(ui);$('sunGeoConfirmOk').onclick=()=>{const d=getDraft();if(!d?.geo)return;d.geo.confirmed=true;d.geo.confirmedAt=new Date().toISOString();if(d.id){const saved=getOrders().find(o=>String(o.id)===String(d.id));if(saved)saved.geo=structuredClone(d.geo);try{persist()}catch(_){}}syncGeoConfirmUi();toast('Адрес подтверждён.','success')};$('sunGeoConfirmEdit').onclick=()=>{const d=getDraft();if(d)d.geo=null;const locality=$('deliveryLocality')||$('deliveryStreet');locality?.focus();syncGeoConfirmUi()}}
return ui;
}
function syncGeoConfirmUi(){
const ui=ensureGeoConfirmUi(),d=getDraft();if(!ui)return;const geo=d?.geo;if(!geo||!Number.isFinite(Number(geo.lat))||!Number.isFinite(Number(geo.lon))){ui.classList.remove('on','ok');return}ui.classList.add('on');ui.classList.toggle('ok',geo.confirmed===true);$('sunGeoFoundTitle').textContent=geo.confirmed?'Адрес подтверждён':'Проверьте найденный адрес';$('sunGeoFoundText').textContent=String(geo.formatted||d.address||'');$('sunGeoConfirmOk').style.display=geo.confirmed?'none':'';
}
function persistPreciseSaved(){
const d=getDraft();if(!d)return;const full=composeFullAddress();if(!d.id)return;const saved=getOrders().find(o=>String(o.id)===String(d.id));if(!saved)return;addrIds.forEach(id=>saved[id]=String($(id)?.value||'').trim());saved.address=full;saved.leadSource=$('orderLeadSource')?.value||'';if(d.geo)saved.geo=structuredClone(d.geo);try{persist()}catch(_){try{localStorage.setItem('sunOrders',JSON.stringify(getOrders()))}catch(__){}}
}
function init(){
ensureSourceField();ensureClientUi();ensureGeoConfirmUi();
const mapSensitive=new Set(['deliveryRegion','deliveryLocality','deliveryDistrict','deliveryStreet','deliveryHouse','deliveryCorpus']);
addrIds.forEach(id=>$(id)?.addEventListener('input',()=>setTimeout(()=>{composeFullAddress();if(mapSensitive.has(id))resetGeoConfirmation(true)},0)));
$('deliveryRegion')?.addEventListener('change',()=>setTimeout(()=>{composeFullAddress();resetGeoConfirmation(true)},0));
$('phone')?.addEventListener('input',schedulePhoneLookup);$('phone')?.addEventListener('blur',schedulePhoneLookup);
$('orderLoyaltyCard')?.addEventListener('input',()=>{clearTimeout(lookupTimer);lookupTimer=setTimeout(lookupCard,260)});$('orderLoyaltyCard')?.addEventListener('blur',lookupCard);
$('orderGeoCreate')?.addEventListener('click',()=>setTimeout(syncGeoConfirmUi,200));
const geoBox=$('orderGeoBox');if(geoBox)new MutationObserver(()=>setTimeout(syncGeoConfirmUi,0)).observe(geoBox,{childList:true,subtree:true,characterData:true});
const stats=$('statsList');if(stats)new MutationObserver(()=>setTimeout(renderCrmStats,0)).observe(stats,{childList:true,subtree:false});
$('statsPeriod')?.addEventListener('change',()=>setTimeout(renderCrmStats,0));renderCrmStats();
}
const priorSave=window.saveOrder;
if(typeof priorSave==='function')window.saveOrder=function(...args){
const d=getDraft(),source=ensureSourceField();if(d&&source)d.leadSource=source.value;composeFullAddress();
const street=String($('deliveryStreet')?.value||'').trim(),house=String($('deliveryHouse')?.value||'').trim(),locality=String($('deliveryLocality')?.value||'').trim();
if(street&&house&&!locality&&!confirm('Не указан город / населённый пункт. Похожие адреса могут существовать в разных местах. Сохранить заказ без уточнения?'))return false;
if(street&&house&&d?.geo&&d.geo.confirmed!==true&&!confirm('Точка уже найдена на карте, но адрес не подтверждён. Сохранить без подтверждения?'))return false;
const r=priorSave.apply(this,args),finish=()=>{persistPreciseSaved();renderCrmStats();syncGeoConfirmUi();return undefined};if(r&&typeof r.then==='function')return r.then(v=>{finish();return v});finish();return r;
};
const priorOpen=window.openOrder;
if(typeof priorOpen==='function')window.openOrder=function(...args){const r=priorOpen.apply(this,args);setTimeout(()=>{const s=ensureSourceField();if(s)s.value=String(getDraft()?.leadSource||'');composeFullAddress();ensureClientUi();syncGeoConfirmUi();$('sunClientMatch')?.classList.remove('on');const l=$('sunKnownAddresses')?.closest('label');if(l)l.style.display='none'},0);return r};
const priorReset=window.resetDraft;
if(typeof priorReset==='function')window.resetDraft=function(...args){const r=priorReset.apply(this,args);setTimeout(()=>{const s=ensureSourceField();if(s)s.value='';$('sunClientMatch')?.classList.remove('on');const l=$('sunKnownAddresses')?.closest('label');if(l)l.style.display='none';syncGeoConfirmUi()},0);return r};
const priorStats=window.renderStats;
if(typeof priorStats==='function')window.renderStats=function(...args){const r=priorStats.apply(this,args);setTimeout(renderCrmStats,0);return r};
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const label=String(b.dataset.navLabel||b.textContent||'').trim();if(label==='Настройки')setTimeout(()=>{ensureSettingsCard();renderSourceSettingsRows()},90);if(label==='Статистика')setTimeout(renderCrmStats,80)},true);
const settings=$('enterprise-settings');if(settings)new MutationObserver(()=>{if(settings.classList.contains('on'))setTimeout(ensureSettingsCard,0)}).observe(settings,{childList:true,subtree:true});
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(()=>{refreshSourceSelect();renderCrmStats()},50));
init();setTimeout(()=>{if($('enterprise-settings')?.classList.contains('on'))ensureSettingsCard();syncGeoConfirmUi()},200);
})();
;
/* ===== MODULE: brand-theme-v1.js ===== */
/* Sun Catering theme editor v4: simple color presets, staged preview, reliable sidebar apply */
(()=>{
'use strict';
if(window.__sunBrandThemeV4)return;window.__sunBrandThemeV4=true;
const KEY='sunBrandThemeV1';
const BRAND={sun:'#FFC107',accent:'#FF8A00',text:'#1A1A1A',background:'#FFF7E6',surface:'#F3E8D3',fresh:'#A8C783',success:'#6D8F6A',trust:'#6C7A89',warm:'#C7744D',border:'#E7E7E7'};
const UI_FIRM={background:'#FFF7E6',card:'#FFFFFF',primary:'#1A1A1A',accent:'#FFC107',text:'#1A1A1A',muted:'#6C7A89',border:'#E7E7E7',input:'#FFFFFF',buttonText:'#FFFFFF'};
const UI_STANDARD={background:'#F5F6FA',card:'#FFFFFF',primary:'#15364C',accent:'#C99A32',text:'#203040',muted:'#68717A',border:'#D9DDE0',input:'#FFFFFF',buttonText:'#FFFFFF'};
const SIDEBAR_STANDARD={background:'#32255D',background2:'#171D38',glow:'#9574FF',border:'#4B3C77',icons:'#E9E8F6',iconBackground:'#3A2F68',text:'#E9E8F6',muted:'#A9A8C6',activeBackground:'#463A74',activeText:'#FFFFFF',activeIcon:'#FFE48D',indicator:'#E4B844',toolsBackground:'#3A315F',toolsBorder:'#544A7B',collapseBackground:'#FFFFFF',collapseText:'#433D68',badgeBackground:'#FF6C7C',badgeText:'#FFFFFF'};
const SIDEBAR_FIRM={background:'#1A1A1A',background2:'#2A2A2A',glow:'#FFC107',border:'#3A372F',icons:'#FFC107',iconBackground:'#2A281D',text:'#FFF7E6',muted:'#C9B98E',activeBackground:'#34301F',activeText:'#FFC107',activeIcon:'#FFC107',indicator:'#FF8A00',toolsBackground:'#242424',toolsBorder:'#414141',collapseBackground:'#FFFFFF',collapseText:'#1A1A1A',badgeBackground:'#C7744D',badgeText:'#FFFFFF'};
const SIDEBAR_LIGHT={background:'#FFF7E6',background2:'#F3E8D3',glow:'#FFC107',border:'#E2D5BC',icons:'#C7744D',iconBackground:'#FFFDF9',text:'#1A1A1A',muted:'#6C7A89',activeBackground:'#FFFFFF',activeText:'#C7744D',activeIcon:'#C7744D',indicator:'#FF8A00',toolsBackground:'#FFFFFF',toolsBorder:'#E2D5BC',collapseBackground:'#1A1A1A',collapseText:'#FFFFFF',badgeBackground:'#C7744D',badgeText:'#FFFFFF'};
const SIDEBAR_EMERALD={background:'#123C30',background2:'#071F19',glow:'#FFC107',border:'#285849',icons:'#FFC107',iconBackground:'#173E33',text:'#FFF7E6',muted:'#A8C783',activeBackground:'#1C4C3D',activeText:'#FFF7E6',activeIcon:'#FFC107',indicator:'#FF8A00',toolsBackground:'#16382F',toolsBorder:'#2B5A4B',collapseBackground:'#FFF7E6',collapseText:'#123C30',badgeBackground:'#C7744D',badgeText:'#FFFFFF'};
const SIDEBAR_SUNSET={background:'#49301B',background2:'#1A1A1A',glow:'#FF8A00',border:'#69462A',icons:'#FFC107',iconBackground:'#3A2C1E',text:'#FFF7E6',muted:'#D6BD91',activeBackground:'#62401D',activeText:'#FFF7E6',activeIcon:'#FFC107',indicator:'#FF8A00',toolsBackground:'#34281F',toolsBorder:'#5A4632',collapseBackground:'#FFF7E6',collapseText:'#49301B',badgeBackground:'#C7744D',badgeText:'#FFFFFF'};
const UI_LABELS={background:'Фон приложения',card:'Карточки и окна',primary:'Основные кнопки',accent:'Акцент / обводка',text:'Основной текст',muted:'Вторичный текст',border:'Границы',input:'Поля ввода',buttonText:'Текст основных кнопок'};
const SIDE_LABELS={background:'Основной цвет панели',background2:'Второй цвет / низ панели',glow:'Свечение панели',border:'Граница панели',icons:'Цвет значков',iconBackground:'Фон значков',text:'Основной текст панели',muted:'Подписи групп',activeBackground:'Фон активного пункта',activeText:'Текст активного пункта',activeIcon:'Активный значок',indicator:'Полоса активного пункта',toolsBackground:'Поиск / уведомления',toolsBorder:'Граница служебных кнопок',collapseBackground:'Кнопка свернуть',collapseText:'Стрелка сворачивания',badgeBackground:'Уведомление',badgeText:'Текст уведомления'};
const BRAND_LABELS={sun:'Солнечный',accent:'Оранжевый',text:'Графит',background:'Кремовый фон',surface:'Бежевый',fresh:'Свежий зелёный',success:'Фирменный зелёный',trust:'Серо-синий',warm:'Терракота',border:'Светлая граница'};
const esc=window.SunSafe.escapeHTML;
const clone=o=>JSON.parse(JSON.stringify(o));
const isHex=v=>/^#[0-9a-f]{6}$/i.test(String(v||'').trim());
const norm=(v,f)=>isHex(v)?String(v).toUpperCase():f;
const hexRgba=(hex,a=.25)=>{const h=norm(hex,'#000000').slice(1);return `rgba(${parseInt(h.slice(0,2),16)},${parseInt(h.slice(2,4),16)},${parseInt(h.slice(4,6),16)},${a})`};
const defaults=()=>({version:4,palette:{...BRAND},ui:{...UI_FIRM},sidebar:{...SIDEBAR_FIRM}});
const standardState=()=>({version:4,palette:{...BRAND},ui:{...UI_STANDARD},sidebar:{...SIDEBAR_STANDARD}});
const presetState=name=>{
if(name==='standard')return standardState();
const sidebar=name==='light'?SIDEBAR_LIGHT:name==='emerald'?SIDEBAR_EMERALD:name==='sunset'?SIDEBAR_SUNSET:SIDEBAR_FIRM;
return{version:4,palette:{...BRAND},ui:{...UI_FIRM},sidebar:{...sidebar}};
};
function read(){
let raw={};try{raw=JSON.parse(localStorage.getItem(KEY)||'{}')||{}}catch(_){raw={}}
const base=defaults(),palette={...base.palette,...(raw.palette||{})},ui={...base.ui,...(raw.ui||{})},sidebar={...base.sidebar,...(raw.sidebar||{})};
Object.keys(BRAND).forEach(k=>palette[k]=norm(palette[k],BRAND[k]));
Object.keys(UI_FIRM).forEach(k=>ui[k]=norm(ui[k],UI_FIRM[k]));
Object.keys(SIDEBAR_FIRM).forEach(k=>sidebar[k]=norm(sidebar[k],SIDEBAR_FIRM[k]));
if(String(ui.buttonText).toUpperCase()===String(ui.primary).toUpperCase())ui.buttonText='#FFFFFF';
return{version:4,palette,ui,sidebar};
}
let savedState=read(),previewState=clone(savedState),dirty=false;
const persist=()=>localStorage.setItem(KEY,JSON.stringify(savedState));
const style=document.createElement('style');style.id='sun-brand-theme-style-v4';style.textContent=`
:root{--sun-ui-bg:#FFF7E6;--sun-ui-card:#fff;--sun-ui-primary:#1A1A1A;--sun-ui-accent:#FFC107;--sun-ui-text:#1A1A1A;--sun-ui-muted:#6C7A89;--sun-ui-border:#E7E7E7;--sun-ui-input:#fff;--sun-ui-button-text:#fff;--sun-sidebar-bg:#32255D;--sun-sidebar-bg2:#171D38;--sun-sidebar-glow:#9574FF;--sun-sidebar-border:#4B3C77;--sun-sidebar-icons:#E9E8F6;--sun-sidebar-icon-bg:#3A2F68;--sun-sidebar-text:#E9E8F6;--sun-sidebar-muted:#A9A8C6;--sun-sidebar-active-bg:#463A74;--sun-sidebar-active-text:#fff;--sun-sidebar-active-icon:#FFE48D;--sun-sidebar-indicator:#E4B844;--sun-sidebar-tools-bg:#3A315F;--sun-sidebar-tools-border:#544A7B;--sun-sidebar-collapse-bg:#fff;--sun-sidebar-collapse-text:#433D68;--sun-sidebar-badge-bg:#FF6C7C;--sun-sidebar-badge-text:#fff}
body{background:var(--sun-ui-bg)!important;color:var(--sun-ui-text)!important}:root{--n:var(--sun-ui-primary)!important;--g:var(--sun-ui-accent)!important;--p:var(--sun-ui-bg)!important;--l:var(--sun-ui-border)!important}
.card,.page,.enterprise-card,.dialog,.sun-order-lines-table,.dash3-panel{background:var(--sun-ui-card)!important;color:var(--sun-ui-text)!important;border-color:var(--sun-ui-border)!important}.page h1,.catalog h1,.enterprise-card h2,.enterprise-card h3{color:var(--sun-ui-primary)!important}.hint,label,.enterprise-card .hint{color:var(--sun-ui-muted)!important}input,select,textarea{background:var(--sun-ui-input)!important;color:var(--sun-ui-text)!important;border-color:var(--sun-ui-border)!important}
button.primary,.primary{background:var(--sun-ui-primary)!important;border-color:var(--sun-ui-primary)!important;color:var(--sun-ui-button-text)!important}.primary svg,.primary *{color:inherit!important}.outline{border-color:var(--sun-ui-accent)!important;color:var(--sun-ui-primary)!important;background:var(--sun-ui-card)!important}
@media(min-width:901px){.sun-enterprise-sidebar header .brand{color:var(--sun-sidebar-text)!important;border-bottom-color:color-mix(in srgb,var(--sun-sidebar-text) 12%,transparent)!important}.sun-enterprise-sidebar .sun-nav-group{border-bottom-color:color-mix(in srgb,var(--sun-sidebar-text) 10%,transparent)!important}.sun-enterprise-sidebar .sun-nav-group::before{color:var(--sun-sidebar-muted)!important}.sun-enterprise-sidebar header nav .sun-nav-button{color:var(--sun-sidebar-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button::before{background:var(--sun-sidebar-icons)!important;color:var(--sun-sidebar-icons)!important}.sun-enterprise-sidebar header nav .sun-nav-button:hover{background:color-mix(in srgb,var(--sun-sidebar-active-bg) 72%,transparent)!important;color:var(--sun-sidebar-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button.on{background:var(--sun-sidebar-active-bg)!important;color:var(--sun-sidebar-active-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button.on::before{background:var(--sun-sidebar-active-icon)!important;color:var(--sun-sidebar-active-icon)!important}.sun-enterprise-sidebar header nav .sun-nav-button.on::after{background:var(--sun-sidebar-indicator)!important;box-shadow:0 0 10px color-mix(in srgb,var(--sun-sidebar-indicator) 42%,transparent)!important}.sun-side-tools .sun-side-action{color:var(--sun-sidebar-text)!important;border-color:var(--sun-sidebar-tools-border)!important;background:var(--sun-sidebar-tools-bg)!important}.sun-current-user,#sunLogoutBtn{color:var(--sun-sidebar-muted)!important}.sun-sidebar-collapse{background:var(--sun-sidebar-collapse-bg)!important;color:var(--sun-sidebar-collapse-text)!important;border-color:var(--sun-sidebar-border)!important}#sunNotificationsBtn .badge{background:var(--sun-sidebar-badge-bg)!important;color:var(--sun-sidebar-badge-text)!important;border-color:var(--sun-sidebar-bg)!important}}
@media(max-width:900px){.sun-enterprise-sidebar header,.sun-enterprise-sidebar header nav{background:linear-gradient(165deg,var(--sun-sidebar-bg),var(--sun-sidebar-bg2))!important;color:var(--sun-sidebar-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button{color:var(--sun-sidebar-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button::before{background:var(--sun-sidebar-icons)!important}.sun-enterprise-sidebar header nav .sun-nav-button.on{background:var(--sun-sidebar-active-bg)!important;color:var(--sun-sidebar-active-text)!important}.sun-enterprise-sidebar header nav .sun-nav-button.on::before{background:var(--sun-sidebar-active-icon)!important}}
.sun-brand-settings-card{overflow:hidden}.sun-theme-toolbar{display:flex;gap:8px;flex-wrap:wrap;margin:11px 0 14px}.sun-theme-sections{display:grid;gap:13px}.sun-theme-box{border:1px solid var(--sun-ui-border);border-radius:12px;padding:13px;background:color-mix(in srgb,var(--sun-ui-card) 96%,var(--sun-ui-bg))}.sun-theme-box h3{margin:0 0 9px}.sun-theme-panel-main{display:grid;grid-template-columns:minmax(250px,.8fr) minmax(340px,1.2fr);gap:14px;align-items:start}.sun-theme-preview{min-height:190px;border-radius:16px;background:radial-gradient(circle at 10% 0,color-mix(in srgb,var(--sun-sidebar-glow) 35%,transparent),transparent 35%),linear-gradient(150deg,var(--sun-sidebar-bg),var(--sun-sidebar-bg2));padding:15px;color:var(--sun-sidebar-text);border:1px solid var(--sun-sidebar-border)}.sun-theme-preview-head{display:flex;align-items:center;gap:10px;padding-bottom:12px;border-bottom:1px solid color-mix(in srgb,var(--sun-sidebar-text) 13%,transparent)}.sun-theme-preview-logo{width:35px;height:35px;border-radius:50%;background:var(--sun-sidebar-icons);box-shadow:0 0 18px color-mix(in srgb,var(--sun-sidebar-glow) 45%,transparent)}.sun-theme-preview-menu{display:grid;gap:7px;margin-top:13px}.sun-theme-preview-row{height:38px;border-radius:10px;display:flex;align-items:center;gap:10px;padding:0 10px;color:var(--sun-sidebar-text)}.sun-theme-preview-row.on{background:var(--sun-sidebar-active-bg);color:var(--sun-sidebar-active-text);box-shadow:inset 3px 0 0 var(--sun-sidebar-indicator)}.sun-theme-preview-row i{width:25px;height:25px;display:grid;place-items:center;color:var(--sun-sidebar-icons);font-style:normal}.sun-theme-preview-row.on i{color:var(--sun-sidebar-active-icon)}.sun-theme-preview small{color:var(--sun-sidebar-muted)}
.sun-sidebar-core-colors{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:12px}.sun-brand-setting-grid{display:grid;grid-template-columns:repeat(3,minmax(185px,1fr));gap:8px}.sun-brand-color-field{display:grid;grid-template-columns:34px minmax(0,1fr) 82px;gap:7px;align-items:center;padding:7px 8px;border:1px solid var(--sun-ui-border);border-radius:9px;background:var(--sun-ui-card)}.sun-brand-color-field label{font-size:10.5px;line-height:1.2}.sun-brand-color-field input[type=color]{width:32px!important;height:30px!important;padding:2px!important;border:1px solid var(--sun-ui-border)!important;border-radius:7px!important}.sun-brand-color-field input[type=text]{width:82px!important;height:30px!important;padding:4px 6px!important;border-radius:7px!important;font:700 10px ui-monospace,monospace!important;text-transform:uppercase}
.sun-brand-palette-toggle{margin-top:10px}.sun-brand-palette-toggle summary{cursor:pointer;font-weight:750;color:var(--sun-ui-primary)}.sun-brand-palette{display:grid;grid-template-columns:repeat(5,minmax(90px,1fr));gap:8px;margin-top:10px}.sun-brand-swatch{border:1px solid var(--sun-ui-border);border-radius:10px;background:var(--sun-ui-card);overflow:hidden}.sun-brand-swatch-top{height:38px;background:var(--swatch)}.sun-brand-swatch-body{padding:6px 7px}.sun-brand-swatch-body b{display:block;font-size:10px}.sun-brand-swatch-body small{font:700 9px ui-monospace,monospace;color:var(--sun-ui-muted)}.sun-theme-savebar{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;justify-content:space-between;gap:10px;margin:15px -13px -13px;padding:12px 13px;background:color-mix(in srgb,var(--sun-ui-card) 94%,transparent);border-top:1px solid var(--sun-ui-border);backdrop-filter:blur(10px)}.sun-theme-savebar .actions{display:flex;gap:8px;flex-wrap:wrap}.sun-theme-dirty{font-size:11px;color:var(--sun-ui-muted)}.sun-theme-dirty strong{color:var(--sun-ui-accent)}
header .brand.sun-brand-home-link{cursor:pointer!important;border-radius:10px;transition:background .15s ease}.sun-enterprise-sidebar header .brand.sun-brand-home-link:hover{background:rgba(255,255,255,.06)!important}.sun-brand-home-link:focus-visible{outline:2px solid var(--sun-sidebar-indicator);outline-offset:2px}
@media(max-width:1100px){.sun-brand-setting-grid{grid-template-columns:repeat(2,minmax(180px,1fr))}}@media(max-width:760px){.sun-theme-panel-main,.sun-sidebar-core-colors,.sun-brand-setting-grid{grid-template-columns:1fr}.sun-brand-palette{grid-template-columns:repeat(2,1fr)}.sun-theme-savebar{align-items:flex-start;flex-direction:column}}
`;document.head.appendChild(style);
function applyActual(s=savedState){
document.body.classList.remove('sun-custom-sidebar-icons');
const root=document.documentElement.style;
Object.entries(s.palette).forEach(([k,v])=>root.setProperty(`--sun-brand-${k}`,v));
Object.entries(s.ui).forEach(([k,v])=>root.setProperty(`--sun-ui-${k.replace(/[A-Z]/g,m=>'-'+m.toLowerCase())}`,v));
const map={background:'--sun-sidebar-bg',background2:'--sun-sidebar-bg2',glow:'--sun-sidebar-glow',border:'--sun-sidebar-border',icons:'--sun-sidebar-icons',iconBackground:'--sun-sidebar-icon-bg',text:'--sun-sidebar-text',muted:'--sun-sidebar-muted',activeBackground:'--sun-sidebar-active-bg',activeText:'--sun-sidebar-active-text',activeIcon:'--sun-sidebar-active-icon',indicator:'--sun-sidebar-indicator',toolsBackground:'--sun-sidebar-tools-bg',toolsBorder:'--sun-sidebar-tools-border',collapseBackground:'--sun-sidebar-collapse-bg',collapseText:'--sun-sidebar-collapse-text',badgeBackground:'--sun-sidebar-badge-bg',badgeText:'--sun-sidebar-badge-text'};
Object.entries(map).forEach(([k,v])=>root.setProperty(v,s.sidebar[k]));
const header=document.querySelector('header');
if(header){
header.style.setProperty('background',`radial-gradient(circle at 12% 0%,${hexRgba(s.sidebar.glow,.30)},transparent 31%),linear-gradient(165deg,${s.sidebar.background} 0%,${s.sidebar.background2} 100%)`,'important');
header.style.setProperty('border-right',`1px solid ${s.sidebar.border}`,'important');
header.style.setProperty('box-shadow',`9px 0 34px ${hexRgba(s.sidebar.background,.22)}`,'important');
}
const nav=document.querySelector('header nav');
if(nav&&matchMedia('(max-width:900px)').matches)nav.style.setProperty('background',`linear-gradient(165deg,${s.sidebar.background},${s.sidebar.background2})`,'important');
else nav?.style.removeProperty('background');
window.dispatchEvent(new CustomEvent('sunbrandthemechange',{detail:clone(s)}));
}
function markDirty(){dirty=JSON.stringify(previewState)!==JSON.stringify(savedState);const el=document.querySelector('[data-theme-dirty]');if(el)el.innerHTML=dirty?'<strong>Есть несохранённые изменения</strong> · пока меняется только предпросмотр':'Все изменения сохранены'}
function field(scope,key,label,value){return`<div class="sun-brand-color-field" data-color-scope="${scope}" data-color-key="${key}"><input type="color" value="${esc(value)}" aria-label="${esc(label)}"><label>${esc(label)}</label><input type="text" value="${esc(value)}" maxlength="7" spellcheck="false" aria-label="HEX ${esc(label)}"></div>`}
function paletteHtml(){return Object.keys(BRAND).map(k=>`<div class="sun-brand-swatch" style="--swatch:${esc(BRAND[k])}"><div class="sun-brand-swatch-top"></div><div class="sun-brand-swatch-body"><b>${esc(BRAND_LABELS[k])}</b><small>${esc(BRAND[k])}</small></div></div>`).join('')}
function updatePreviewNode(card){
const p=card.querySelector('.sun-theme-preview');if(!p)return;
const s=previewState.sidebar;
const vars={background:'--sun-sidebar-bg',background2:'--sun-sidebar-bg2',glow:'--sun-sidebar-glow',border:'--sun-sidebar-border',icons:'--sun-sidebar-icons',iconBackground:'--sun-sidebar-icon-bg',text:'--sun-sidebar-text',muted:'--sun-sidebar-muted',activeBackground:'--sun-sidebar-active-bg',activeText:'--sun-sidebar-active-text',activeIcon:'--sun-sidebar-active-icon',indicator:'--sun-sidebar-indicator'};
Object.entries(vars).forEach(([k,v])=>p.style.setProperty(v,s[k]));
}
function cardHtml(){
return`<h2>Цвета интерфейса</h2><p class="hint">Выберите готовый вариант или настройте цвета вручную. Изменения сначала видны в примере. Реальная левая панель меняется только после «Сохранить настройки».</p>
<div class="sun-theme-toolbar"><button class="outline" type="button" data-theme-preset="standard">Стандартная фиолетовая</button><button class="outline" type="button" data-theme-preset="firm">Фирменная тёмная</button><button class="outline" type="button" data-theme-preset="light">Фирменная светлая</button><button class="outline" type="button" data-theme-preset="emerald">Фирменная зелёная</button><button class="outline" type="button" data-theme-preset="sunset">Солнечная графитовая</button></div>
<div class="sun-theme-sections">
<section class="sun-theme-box"><h3>Левая панель</h3><div class="sun-theme-panel-main"><div class="sun-theme-preview"><div class="sun-theme-preview-head"><div class="sun-theme-preview-logo"></div><div><b>Солнце</b><small>Предпросмотр боковой панели</small></div></div><div class="sun-theme-preview-menu"><div class="sun-theme-preview-row"><i>⌂</i>Главная</div><div class="sun-theme-preview-row on"><i>▤</i>Заказы</div><div class="sun-theme-preview-row"><i>⚙</i>Настройки</div></div></div><div><h3 style="margin-top:0">Главные цвета панели</h3><div class="sun-sidebar-core-colors">${field('sidebar','background','Основной цвет панели',previewState.sidebar.background)}${field('sidebar','background2','Низ / второй цвет',previewState.sidebar.background2)}${field('sidebar','icons','Цвет значков',previewState.sidebar.icons)}</div><p class="hint">Основной цвет — это фон всей большой левой панели. Для однотонной панели поставьте одинаковый цвет в первых двух полях.</p></div></div><details style="margin-top:12px"><summary><b>Дополнительные цвета панели</b></summary><div class="sun-brand-setting-grid" style="margin-top:10px">${Object.keys(SIDEBAR_FIRM).filter(k=>!['background','background2','icons'].includes(k)).map(k=>field('sidebar',k,SIDE_LABELS[k],previewState.sidebar[k])).join('')}</div></details></section>
<section class="sun-theme-box"><h3>Основной интерфейс</h3><div class="sun-brand-setting-grid">${Object.keys(UI_FIRM).map(k=>field('ui',k,UI_LABELS[k],previewState.ui[k])).join('')}</div><details class="sun-brand-palette-toggle"><summary>Показать фирменные цвета «Солнца»</summary><div class="sun-brand-palette">${paletteHtml()}</div></details></section>
</div><div class="sun-theme-savebar"><div class="sun-theme-dirty" data-theme-dirty>${dirty?'<strong>Есть несохранённые изменения</strong> · пока меняется только предпросмотр':'Все изменения сохранены'}</div><div class="actions"><button class="outline" type="button" data-theme-cancel>Отменить изменения</button><button class="danger" type="button" data-reset-standard>Вернуть стандартную версию</button><button class="primary" type="button" data-theme-save>Сохранить настройки</button></div></div>`;
}
function rerender(card){card.innerHTML=cardHtml();bind(card);updatePreviewNode(card)}
function bind(card){
card.querySelectorAll('[data-color-scope]').forEach(row=>{const scope=row.dataset.colorScope,key=row.dataset.colorKey,c=row.querySelector('input[type=color]'),t=row.querySelector('input[type=text]');const applyValue=v=>{const fallback=scope==='ui'?UI_FIRM[key]:SIDEBAR_FIRM[key];const val=norm(v,fallback);previewState[scope][key]=val;if(c)c.value=val;if(t)t.value=val;updatePreviewNode(card);markDirty()};c?.addEventListener('input',()=>applyValue(c.value));t?.addEventListener('input',()=>{if(isHex(t.value))applyValue(t.value)});t?.addEventListener('blur',()=>{if(!isHex(t.value))t.value=previewState[scope][key]})});
card.querySelectorAll('[data-theme-preset]').forEach(b=>b.addEventListener('click',()=>{previewState=presetState(b.dataset.themePreset);markDirty();rerender(card)}));
card.querySelector('[data-theme-save]')?.addEventListener('click',()=>{savedState=clone(previewState);savedState.version=4;persist();dirty=false;applyActual(savedState);rerender(card);window.SunEnterprise?.toast?.('Настройки цветов сохранены.','success')});
card.querySelector('[data-theme-cancel]')?.addEventListener('click',()=>{previewState=clone(savedState);dirty=false;rerender(card)});
card.querySelector('[data-reset-standard]')?.addEventListener('click',()=>{if(!confirm('Вернуть стандартную фиолетовую панель и стандартные цвета интерфейса?'))return;savedState=standardState();previewState=clone(savedState);dirty=false;persist();applyActual(savedState);rerender(card);window.SunEnterprise?.toast?.('Стандартная версия восстановлена.','success')});
}
function ensureCard(){const view=document.getElementById('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');if(!grid)return;let card=document.getElementById('sunBrandThemeSettingsCard');if(card&&grid.contains(card)){if(!card.dataset.bound){rerender(card);card.dataset.bound='1'}return}card=document.createElement('section');card.id='sunBrandThemeSettingsCard';card.className='enterprise-card wide sun-brand-settings-card';card.innerHTML=cardHtml();const history=[...grid.children].find(el=>(el.querySelector(':scope > h2')?.textContent||'').trim()==='История изменений');if(history)window.SunSafe.insertBefore(grid,card,history);else grid.appendChild(card);bind(card);updatePreviewNode(card);card.dataset.bound='1'}
function bindBrandHome(){const brand=document.querySelector('header .brand');if(!brand||brand.dataset.sunHomeBound)return;brand.dataset.sunHomeBound='1';brand.classList.add('sun-brand-home-link');brand.setAttribute('role','link');brand.setAttribute('tabindex','0');brand.setAttribute('title','Новый заказ');const go=()=>{const btn=[...document.querySelectorAll('header nav button')].find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Новый заказ')||document.querySelector('header nav .nav-new');btn?.click()};brand.addEventListener('click',go);brand.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();go()}})}
function syncFromStorage(){savedState=read();if(!dirty)previewState=clone(savedState);applyActual(savedState);const card=document.getElementById('sunBrandThemeSettingsCard');if(card&&!dirty)rerender(card)}
applyActual(savedState);previewState=clone(savedState);
const boot=()=>{applyActual(savedState);ensureCard();bindBrandHome();const settings=document.getElementById('enterprise-settings');if(settings)new MutationObserver(()=>queueMicrotask(()=>{if(settings.classList.contains('on')&&!document.getElementById('sunBrandThemeSettingsCard'))ensureCard();bindBrandHome()})).observe(settings,{childList:true,subtree:true});document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const name=String(b.dataset.navLabel||b.textContent||'').trim();if(name!=='Настройки'&&dirty){previewState=clone(savedState);dirty=false;const card=document.getElementById('sunBrandThemeSettingsCard');if(card)rerender(card)}},true);window.addEventListener('resize',()=>applyActual(savedState));window.addEventListener('storage',e=>{if(e.key===KEY)syncFromStorage()});window.addEventListener('suncloudsync',syncFromStorage);window.addEventListener('sun:cloud-state-applied',syncFromStorage)};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
window.SunBrandTheme={get:()=>clone(savedState),preview:()=>clone(previewState),save:()=>{savedState=clone(previewState);persist();dirty=false;applyActual(savedState)},resetStandard:()=>{savedState=standardState();previewState=clone(savedState);persist();dirty=false;applyActual(savedState);ensureCard()},applyPreset:name=>{previewState=presetState(name);dirty=true;ensureCard()}};
})();
;
/* ===== MODULE: offer-template-v1.js ===== */
/* Sun Catering: selectable client-offer templates */
(()=>{
'use strict';
if(window.__sunOfferTemplateV1)return;
window.__sunOfferTemplateV1=true;
const KEY='sunOfferTemplateV1';
const DEFAULT_ID='cream-elegance';
const TEMPLATES=[
{id:'cream-elegance',name:'Кремовая классика',desc:'Светлый премиальный дизайн: крупная обложка, круглые иконки категорий и аккуратная сетка меню.',thumb:''},
{id:'neon-menu',name:'Неоновое меню',desc:'Тёмный дизайн с неоновыми акцентами, нумерованной фотосеткой блюд и карточками категорий.',thumb:''},
{id:'emerald-circles',name:'Изумрудные круги',desc:'Тёмно-изумрудная презентация с круглыми фото блюд и списком категорий.',thumb:''},
{id:'midnight-checklist',name:'Тёмный чек-лист',desc:'Компактный тёмно-зелёный макет: нумерованное меню по категориям и фотополоса блюд.',thumb:''},
{id:'gourmet-hero',name:'Гастро-витрина',desc:'Крупный фото-хиро в духе лендинга, вкладки категорий и карточки блюд.',thumb:''},
{id:'diamond-gold',name:'Изумруд и золото',desc:'Тёмно-зелёный дизайн с золотыми акцентами, нумерованной фотосеткой и блоком скидки.',thumb:''},
{id:'premium-dark',name:'Премиум тёмный',desc:'Строгий тёмный дизайн с золотыми иконками-бейджами дат и гостей, минималистичным списком меню.',thumb:''},
{id:'premium-emerald',name:'Премиум изумрудный',desc:'Тот же строгий тёмный дизайн с иконками-бейджами, но в изумрудно-зелёной палитре.',thumb:''}
];
const esc=window.SunSafe.escapeHTML;
function parse(){
try{
const raw=JSON.parse(localStorage.getItem(KEY)||'{}')||{};
const id=TEMPLATES.some(x=>x.id===raw.id)?raw.id:DEFAULT_ID;
return {version:1,id};
}catch(_){return {version:1,id:DEFAULT_ID}}
}
let state=parse();
function selected(){return TEMPLATES.find(x=>x.id===state.id)||TEMPLATES[0]}
function save(id){
if(!TEMPLATES.some(x=>x.id===id))id=DEFAULT_ID;
state={version:1,id};
localStorage.setItem(KEY,JSON.stringify(state));
window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{...state,template:selected()}}));
}
const style=document.createElement('style');
style.id='sun-offer-template-settings-style';
style.textContent=`
.sun-offer-template-card{grid-column:1/-1!important}
.sun-offer-template-card .hint{max-width:980px}
.sun-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(200px,1fr));gap:14px;margin-top:14px}
.sun-offer-template-option{position:relative;border:1px solid #e3e6e9;border-radius:16px;background:#fff;padding:10px;cursor:pointer;transition:.16s ease;overflow:hidden;box-shadow:0 4px 16px rgba(30,35,40,.035)}
.sun-offer-template-option:hover{transform:translateY(-1px);box-shadow:0 8px 22px rgba(30,35,40,.08)}
.sun-offer-template-option.on{border-color:var(--sun-brand-sun,#FFC107);box-shadow:0 0 0 2px color-mix(in srgb,var(--sun-brand-sun,#FFC107) 30%,transparent),0 9px 24px rgba(30,35,40,.09)}
.sun-offer-template-thumb{display:block;width:100%;height:245px;object-fit:cover;object-position:top center;border-radius:11px;background:#eef0f2;border:1px solid #eceef0}
.sun-offer-template-info{padding:10px 3px 2px;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px 10px;align-items:start}
.sun-offer-template-info strong{font-size:14px;color:#24282d}.sun-offer-template-info p{grid-column:1/-1;margin:0;color:#6c737c;font-size:11px;line-height:1.42}
.sun-offer-template-check{width:25px;height:25px;border-radius:50%;border:1px solid #d8dde2;display:grid;place-items:center;color:transparent;font-weight:900;background:#fff}
.sun-offer-template-option.on .sun-offer-template-check{background:var(--sun-brand-sun,#FFC107);border-color:var(--sun-brand-sun,#FFC107);color:#1a1a1a}
.sun-offer-template-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px;padding-top:12px;border-top:1px solid #eceef1}
.sun-offer-template-toolbar .current{font-size:12px;color:#66707a}.sun-offer-template-toolbar .current b{color:#263238}
@media(max-width:1100px){.sun-offer-template-grid{grid-template-columns:repeat(2,minmax(210px,1fr))}}
@media(max-width:680px){.sun-offer-template-grid{grid-template-columns:1fr}.sun-offer-template-thumb{height:290px}}
`;
document.head.appendChild(style);
function offerTemplateMini(id){
if(id==='editorial-grid')return '<i class="mini-head"></i><i class="mini-editorial-hero"></i><i class="mini-editorial-stack a"></i><i class="mini-editorial-stack b"></i><i class="mini-editorial-grid"></i>';
if(id==='midnight-glass')return '<i class="mini-head"></i><i class="mini-midnight-hero"></i><i class="mini-midnight-total"></i><i class="mini-midnight-left"></i><i class="mini-midnight-right"></i>';
if(id==='emerald-gold')return '<i class="mini-head"></i><i class="mini-emerald-hero"></i><i class="mini-emerald-gold"></i><i class="mini-emerald-info"></i><i class="mini-emerald-menu"></i>';
return '<i class="mini-head"></i><i class="mini-light-hero"></i><i class="mini-light-row a"></i><i class="mini-light-row b"></i><i class="mini-light-row c"></i><i class="mini-light-total"></i>';
}
function cardHtml(){
const cur=selected();
return `<h2>Шаблон предложения клиенту</h2>
<p class="hint">Выберите внешний вид, который будет использоваться в «Предложении клиенту» и в PDF. Реальные фотографии боксов и данные заказа остаются одинаковыми — меняется только дизайн.</p>
<div class="sun-offer-template-grid">${TEMPLATES.map(t=>`<article class="sun-offer-template-option ${t.id===state.id?'on':''}" data-offer-template="${esc(t.id)}" tabindex="0" role="radio" aria-checked="${t.id===state.id?'true':'false'}"><div class="sun-offer-template-thumb sun-offer-template-mini sun-offer-template-mini-${esc(t.id)}" data-template-mini="${esc(t.id)}" aria-hidden="true">${offerTemplateMini(t.id)}</div><div class="sun-offer-template-info"><strong>${esc(t.name)}</strong><span class="sun-offer-template-check">✓</span><p>${esc(t.desc)}</p></div></article>`).join('')}</div>
<div class="sun-offer-template-toolbar"><span class="current">Сейчас выбран: <b>${esc(cur.name)}</b></span><button type="button" class="outline" data-offer-template-reset>Вернуть светлый фирменный</button></div>`;
}
function bind(card){
const choose=(el)=>{
const id=el?.dataset?.offerTemplate;if(!id)return;
save(id);card.innerHTML=cardHtml();bind(card);
try{window.SunEnterprise?.toast?.(`Шаблон «${selected().name}» выбран.`)}catch(_){ }
};
card.querySelectorAll('[data-offer-template]').forEach(el=>{
el.addEventListener('click',()=>choose(el));
el.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();choose(el)}});
});
card.querySelector('[data-offer-template-reset]')?.addEventListener('click',()=>{save(DEFAULT_ID);card.innerHTML=cardHtml();bind(card)});
}
function ensureCard(){
const view=document.getElementById('enterprise-settings');if(!view)return;
const grid=view.querySelector('.enterprise-grid');if(!grid)return;
let card=document.getElementById('sunOfferTemplateSettingsCard');
if(card&&grid.contains(card))return;
card=document.createElement('section');card.id='sunOfferTemplateSettingsCard';card.className='enterprise-card wide sun-offer-template-card';card.innerHTML=cardHtml();
const brand=document.getElementById('sunBrandThemeSettingsCard');
if(brand&&brand.parentElement===grid)brand.insertAdjacentElement('afterend',card);else{
const history=[...grid.children].find(el=>(el.querySelector(':scope > h2')?.textContent||'').trim()==='История изменений');
if(history)window.SunSafe.insertBefore(grid,card,history);else grid.appendChild(card);
}
bind(card);
}
function boot(){
ensureCard();
const settings=document.getElementById('enterprise-settings');
if(settings)new MutationObserver(()=>queueMicrotask(ensureCard)).observe(settings,{childList:true,subtree:true});
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;const label=(b.dataset.navLabel||b.textContent||'').trim();if(label==='Настройки')setTimeout(ensureCard,30)},true);
window.addEventListener('storage',e=>{if(e.key===KEY){state=parse();ensureCard();window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{...state,template:selected()}}));}});
window.addEventListener('suncloudsync',()=>{state=parse();setTimeout(ensureCard,25);window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{...state,template:selected()}}));});
window.addEventListener('sun:cloud-state-applied',()=>{state=parse();setTimeout(ensureCard,25);window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{...state,template:selected()}}));});
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
window.SunOfferTemplate={
key:KEY,
list:()=>TEMPLATES.map(x=>({...x})),
get:()=>({...state,template:{...selected()}}),
set:id=>save(id)
};
})();
;
/* ===== MODULE: stability-v17.js ===== */
(() => {
'use strict';
if (window.SunStabilityV17) return;
const VERSION = '17.7.3';
const RELEASE = 'v17.7.3 Clients Server Read';
const DB_NAME = 'SunStabilityV17';
const DB_VERSION = 1;
const DAILY_KEY = 'sunV17DailyBackup';
const ERROR_DEDUPE_MS=5*60*1000;
const STABILITY_RPC_TIMEOUT_MS=10000;
const NETWORK_BACKOFF_MAX_MS=5*60*1000;
const errorSeen=new Map();
let flushBusy=false,mirrorBusy=false,backupBusy=false,initialBusy=false,networkFailures=0,nextNetworkAttemptAt=0;
const $ = id => document.getElementById(id);
const networkish=e=>/failed to fetch|networkerror|load failed|network|timeout|время ожидания/i.test(String(e?.message||e||''));
function noteNetworkFailure(){networkFailures=Math.min(networkFailures+1,8);nextNetworkAttemptAt=Date.now()+Math.min(NETWORK_BACKOFF_MAX_MS,5000*Math.pow(2,networkFailures-1));}
function noteNetworkSuccess(){networkFailures=0;nextNetworkAttemptAt=0;}
function networkReady(){return navigator.onLine&&Date.now()>=nextNetworkAttemptAt;}
async function stabilityRpc(promise,label){let timer=0;try{return await Promise.race([Promise.resolve(promise),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error(label+': timeout')),STABILITY_RPC_TIMEOUT_MS)})]);}finally{if(timer)clearTimeout(timer)}}
const esc=window.SunSafe.escapeHTML;
const toast=(t,k='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(t,k,ms)}catch(_){} console.log('[V17]',t)};
const cloud=()=>window.SunCloudV2;
const client=()=>cloud()?.getClient?.()||null;
const workspace=()=>cloud()?.getWorkspace?.()||null;
const clientId=()=>cloud()?.getClientId?.()||'local';
let lastMirrorAt='';
let conflictCount=0;
let booted=false;
function openDb(){
return new Promise((resolve,reject)=>{
const r=indexedDB.open(DB_NAME,DB_VERSION);
r.onupgradeneeded=()=>{
const db=r.result;
if(!db.objectStoreNames.contains('outbox'))db.createObjectStore('outbox',{keyPath:'key'});
if(!db.objectStoreNames.contains('errors'))db.createObjectStore('errors',{keyPath:'id'});
if(!db.objectStoreNames.contains('meta'))db.createObjectStore('meta',{keyPath:'key'});
};
r.onsuccess=()=>resolve(r.result);r.onerror=()=>reject(r.error);
});
}
async function idbPut(store,value){try{const db=await openDb();await new Promise((res,rej)=>{const tx=db.transaction(store,'readwrite');tx.objectStore(store).put(value);tx.oncomplete=res;tx.onerror=()=>rej(tx.error)});return true}catch(_){return false}}
async function idbGet(store,key){try{const db=await openDb();return await new Promise((res,rej)=>{const r=db.transaction(store,'readonly').objectStore(store).get(key);r.onsuccess=()=>res(r.result||null);r.onerror=()=>rej(r.error)})}catch(_){return null}}
async function idbDelete(store,key){try{const db=await openDb();await new Promise((res,rej)=>{const tx=db.transaction(store,'readwrite');tx.objectStore(store).delete(key);tx.oncomplete=res;tx.onerror=()=>rej(tx.error)});return true}catch(_){return false}}
async function idbAll(store){try{const db=await openDb();return await new Promise((res,rej)=>{const r=db.transaction(store,'readonly').objectStore(store).getAll();r.onsuccess=()=>res(r.result||[]);r.onerror=()=>rej(r.error)})}catch(_){return[]}}
async function markPending(reason='change'){
const ws=workspace()?.id||'local';
const old=await idbGet('outbox',`sync:${ws}`);
await idbPut('outbox',{key:`sync:${ws}`,workspaceId:ws,reason:String(reason),createdAt:old?.createdAt||Date.now(),updatedAt:Date.now(),attempts:Number(old?.attempts||0)+1});
refreshCardSoon();
}
async function markSynced(revision){
const ws=workspace()?.id||'local';
await idbDelete('outbox',`sync:${ws}`);
await idbPut('meta',{key:`lastSync:${ws}`,at:Date.now(),revision:Number(revision||0)});
refreshCardSoon();
}
async function recordConflict(message){
conflictCount++;
await idbPut('meta',{key:'lastConflict',at:Date.now(),message:String(message||''),count:conflictCount});
refreshCardSoon();
}
function context(){
return {url:location.href,online:navigator.onLine,userAgent:navigator.userAgent,release:RELEASE,cloudStatus:cloud()?.status?.()||null};
}
async function queueError(message,stack='',level='error',extra={}){
const text=String(message||'Unknown error').slice(0,4000),wid=workspace()?.id||null,key=level+'|'+(wid||'')+'|'+text,now=Date.now();
const seen=errorSeen.get(key)||0;if(now-seen<ERROR_DEDUPE_MS)return false;errorSeen.set(key,now);
const item={id:crypto.randomUUID?crypto.randomUUID():Date.now()+'-'+Math.random(),workspaceId:wid,clientId:clientId(),version:VERSION,level,message:text,stack:String(stack||'').slice(0,12000),context:{...context(),...extra},createdAt:now};
await idbPut('errors',item);if(networkReady())flushErrors();refreshCardSoon();return true;
}
async function flushErrors(){
const c=client(),ws=workspace();if(flushBusy||!c||!networkReady())return;flushBusy=true;
try{const items=await idbAll('errors');for(const item of items.slice(0,20)){try{const {error}=await stabilityRpc(c.rpc('sun_v17_log_error',{p_workspace:item.workspaceId||ws?.id||null,p_client_id:item.clientId,p_app_version:item.version,p_level:item.level,p_message:item.message,p_stack:item.stack,p_context:item.context}),'Журнал ошибок');if(error)throw error;await idbDelete('errors',item.id);noteNetworkSuccess();}catch(e){if(networkish(e))noteNetworkFailure();break;}}}
finally{flushBusy=false}
}
async function mirror(payload){
const c=client(),ws=workspace();if(mirrorBusy||!c||!ws?.id||!payload||!networkReady())return null;mirrorBusy=true;
try{const {data,error}=await stabilityRpc(c.rpc('sun_v17_mirror_legacy',{p_workspace:ws.id,p_payload:payload,p_client_id:clientId()}),'Зеркалирование v17');if(error)throw error;noteNetworkSuccess();lastMirrorAt=new Date().toISOString();await idbPut('meta',{key:'mirror:'+ws.id,at:Date.now(),result:data||{}});return data;}
catch(e){if(networkish(e))noteNetworkFailure();await queueError('Ошибка зеркалирования v17: '+(e.message||e),e.stack||'','warn');return null}
finally{mirrorBusy=false}
}
async function ensureDailyBackup(){
const c=client(),ws=workspace();if(backupBusy||!c||!ws?.id||!networkReady())return;backupBusy=true;
try{const today=new Date().toISOString().slice(0,10);let state={};try{state=JSON.parse(localStorage.getItem(DAILY_KEY)||'{}')||{}}catch(_){}if(state[ws.id]===today)return;const {error}=await stabilityRpc(c.rpc('sun_v17_create_backup',{p_workspace:ws.id,p_kind:'daily',p_label:'Автоматическая копия '+today}),'Ежедневная резервная копия');if(error)throw error;await stabilityRpc(c.rpc('sun_v17_prune_backups',{p_workspace:ws.id}),'Очистка старых резервных копий');noteNetworkSuccess();state[ws.id]=today;localStorage.setItem(DAILY_KEY,JSON.stringify(state));}
catch(e){if(networkish(e))noteNetworkFailure();await queueError('Не удалось создать ежедневную резервную копию: '+(e.message||e),e.stack||'','warn')}
finally{backupBusy=false}
}
async function onCloudSync(ev){
noteNetworkSuccess();
const payload=ev?.detail?.payload;
if(payload)await mirror(payload);
await ensureDailyBackup();await flushErrors();refreshCardSoon();
}
function installErrorHooks(){
window.addEventListener('error',e=>{if(e?.error?.__sunV17Reported)return;queueError(e?.message||e?.error?.message||'Window error',e?.error?.stack||'', 'error',{source:e?.filename||'',line:e?.lineno||0,column:e?.colno||0});});
window.addEventListener('unhandledrejection',e=>{const r=e.reason;queueError(`Unhandled promise: ${r?.message||r||'unknown'}`,r?.stack||'','error');});
}
async function diagnostics(){
const ws=workspace(),c=client();const outbox=await idbGet('outbox',`sync:${ws?.id||'local'}`);const errors=(await idbAll('errors')).length;
let normalized=null,backups=[];
if(c&&ws?.id&&navigator.onLine){
try{const r=await c.rpc('sun_v17_entity_snapshot',{p_workspace:ws.id});if(!r.error)normalized=r.data}catch(_){}
try{const r=await c.rpc('sun_v17_list_backups',{p_workspace:ws.id,p_limit:30});if(!r.error)backups=r.data||[]}catch(_){}
}
let localOrders=0,localCatalog=0;try{localOrders=JSON.parse(localStorage.getItem('sunOrders')||'[]').length}catch(_){}try{localCatalog=JSON.parse(localStorage.getItem('sunBoxes')||'[]').length}catch(_){}
return {ws,c,outbox,errors,normalized,backups,localOrders,localCatalog,cloudStatus:cloud()?.status?.()||{}};
}
async function createManualBackup(){
const c=client(),ws=workspace();if(!c||!ws?.id)return toast('Сначала войдите в облачную рабочую базу.','warn');
try{const {error}=await c.rpc('sun_v17_create_backup',{p_workspace:ws.id,p_kind:'manual',p_label:`Ручная копия · ${new Date().toLocaleString('ru-RU')}`});if(error)throw error;toast('Резервная копия создана.','success');await renderCard()}catch(e){toast(e.message||String(e),'error')}
}
async function restoreBackup(id){
const c=client(),ws=workspace();if(!c||!ws?.id||!id)return;
if(!confirm('Восстановить выбранную резервную копию? Перед восстановлением будет автоматически создана ещё одна копия текущего состояния.'))return;
try{const {error}=await c.rpc('sun_v17_restore_backup',{p_workspace:ws.id,p_backup:id});if(error)throw error;toast('Копия восстановлена. Загружаю данные…','success');await cloud()?.pull?.();setTimeout(()=>location.reload(),500)}catch(e){toast(e.message||String(e),'error')}
}
function emergencyExport(){
const data={format:'sun-v17-emergency-local',version:17,createdAt:new Date().toISOString(),storage:{}};
for(let i=0;i<localStorage.length;i++){const k=localStorage.key(i);if(k?.startsWith('sun'))data.storage[k]=localStorage.getItem(k)}
const blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`sun-emergency-${new Date().toISOString().replace(/[:.]/g,'-')}.json`;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000);
}
function findSettingsGrid(){return document.querySelector('#enterprise-settings .enterprise-grid')}
function installStyle(){if($('sunV17Style'))return;const s=document.createElement('style');s.id='sunV17Style';s.textContent=`
.sun-v17-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px}.sun-v17-stat{border:1px solid #e1e6e8;border-radius:10px;padding:10px;background:#fbfcfc}.sun-v17-stat small{display:block;color:#77838b;margin-bottom:4px}.sun-v17-stat b{font-size:15px;color:#15364c}.sun-v17-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.sun-v17-backups{margin-top:12px;border-top:1px solid #e4e8ea;padding-top:10px}.sun-v17-backup{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;padding:7px 0;border-bottom:1px dashed #e5e8ea}.sun-v17-backup small{display:block;color:#7a858c}.sun-v17-health{font-weight:800}.sun-v17-health.good{color:#347154}.sun-v17-health.warn{color:#9a7016}.sun-v17-health.bad{color:#a64040}
@media(max-width:900px){.sun-v17-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){.sun-v17-grid{grid-template-columns:1fr}}
`;document.head.appendChild(s)}
async function renderCard(){
// Backup/stability controls are intentionally hidden from ordinary Settings.
$('sunV17StabilityCard')?.remove();
}
let refreshTimer=null;function refreshCardSoon(){clearTimeout(refreshTimer);refreshTimer=setTimeout(()=>{if($('enterprise-settings')?.classList.contains('on'))renderCard()},250)}
function observeSettings(){
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;if(String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')setTimeout(renderCard,120)},true);
const root=$('enterprise-settings');if(root)new MutationObserver(()=>{if(root.classList.contains('on')&&!$('sunV17StabilityCard'))setTimeout(renderCard,80)}).observe(root,{childList:true,subtree:true,attributes:true,attributeFilter:['class']});
}
async function initialMirrorIfPossible(){
const c=client(),ws=workspace();if(initialBusy||!c||!ws?.id||!networkReady())return;initialBusy=true;
try{const payload=await cloud()?.snapshotLocal?.();if(payload)await mirror(payload);await ensureDailyBackup();await flushErrors()}catch(e){await queueError(e.message||String(e),e.stack||'','warn')}
finally{initialBusy=false}
}
function boot(){if(booted)return;booted=true;installStyle();installErrorHooks();observeSettings();window.addEventListener('sun:cloud-sync-complete',onCloudSync);window.addEventListener('online',()=>{noteNetworkSuccess();flushErrors();setTimeout(initialMirrorIfPossible,900)});window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(initialMirrorIfPossible,900));setTimeout(initialMirrorIfPossible,2600);setInterval(()=>{if(networkReady())flushErrors()},60000)}
window.SunStabilityV17={VERSION,RELEASE,markPending,markSynced,recordConflict,queueError,renderCard,diagnostics,createBackup:createManualBackup,emergencyExport};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
;
/* ===== MODULE: saas-v16.js ===== */
/* Sun Catering SaaS subscriptions, plan entitlements and platform admin v16 */
(()=>{
'use strict';
if(window.SunSaaSV16)return;
const VERSION='2026.08.30-saas-v16';
const $=id=>document.getElementById(id);
const qa=(s,r=document)=>[...r.querySelectorAll(s)];
const esc=window.SunSafe.escapeHTML;
const cloud=()=>window.SunCloudV2||null;
const client=()=>cloud()?.getClient?.()||null;
const workspace=()=>cloud()?.getWorkspace?.()||null;
const session=()=>cloud()?.getSession?.()||null;
const toast=(text,type='info',timeout=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,timeout)}catch(_){console.log('[SaaS]',text)}};
const PLAN_LABELS={basic:'Базовый',professional:'Профессиональный',full:'Полный'};
const FEATURE_LABELS={
orders:'Заказы',calendar:'Календарь',map:'Карта',clients:'Клиенты',catalog_view:'Каталог',catalog_edit:'Редактирование боксов',
production:'Производство',shopping:'Закупки',stock:'Склад',routes:'Маршруты',mailings:'Рассылки',money:'Деньги',
stats_basic:'Статистика',stats_advanced:'Расширенная статистика',team:'Команда',suppliers:'Поставщики',print:'Печать',settings:'Настройки',
branding:'Фирменное оформление',client_offers:'Предложения клиенту',offer_templates:'Шаблоны предложений',backups:'Резервные копии',audit:'История изменений',users_manage:'Сотрудники и права'
};
const NAV_FEATURE={
'Главная':'orders','Новый заказ':'orders','Заказы':'orders','Календарь':'calendar','Карта':'map','Производство':'production','Закупки':'shopping','Закупка':'shopping',
'Склад':'stock','Клиенты':'clients','Рассылки':'mailings','Деньги':'money','Команда':'team','Поставщики':'suppliers','Статистика':'stats_basic','Печать':'print','Маршруты':'routes','Настройки':'settings'
};
const WRITE_FN_FEATURE={
saveBox:'catalog_edit',removeBox:'catalog_edit',editBox:'catalog_edit',openManager:'catalog_edit',
sunOpenClientOffer:'client_offers',sunOpenCurrentClientOffer:'client_offers'
};
const PLATFORM_FEATURES=['client_offers','offer_templates','catalog_edit','branding','users_manage','money','stock','production','routes','mailings','suppliers','audit','backups'];
let snapshot=null;
let lastWorkspaceId='';
let loading=false;
let guardedFns=new Map();
let adminRows=[];
function hasFeature(key){
if(!key)return true;
if(!snapshot)return true; // do not flash-lock before the server snapshot arrives
return snapshot.features?.[key]===true;
}
function isWritable(){if(cloud()?.isSupportMode?.())return false;return !snapshot||snapshot.access_mode==='full';}
function accessLabel(mode){return mode==='full'?'Активен':mode==='read_only'?'Только просмотр':'Заблокирован';}
function statusLabel(status){return ({trialing:'Пробный период',active:'Активна',past_due:'Оплата просрочена',canceled:'Отменена',expired:'Истекла'})[status]||status||'—';}
function formatDate(v){if(!v)return 'без срока';const d=new Date(v);if(Number.isNaN(d.getTime()))return '—';return d.toLocaleDateString('ru-RU');}
function endDate(){return snapshot?.status==='trialing'?snapshot?.trial_ends_at:snapshot?.current_period_end;}
function daysLeft(){const end=endDate();if(!end)return null;return Math.ceil((new Date(end).getTime()-Date.now())/86400000);}
async function refreshSnapshot(force=false){
const c=client(),ws=workspace();
if(!c||!ws?.id){snapshot=null;lastWorkspaceId='';removeSubscriptionUi();return null;}
if(loading)return snapshot;
if(!force&&lastWorkspaceId===ws.id&&snapshot)return snapshot;
loading=true;
try{
const {data,error}=await c.rpc('sun_subscription_snapshot',{p_workspace:ws.id});
if(error)throw error;
snapshot=Array.isArray(data)?(data[0]||null):data;
lastWorkspaceId=ws.id;
applyAll();
window.dispatchEvent(new CustomEvent('sun:subscription-changed',{detail:snapshot}));
return snapshot;
}catch(err){console.error('[SaaS] subscription snapshot',err);snapshot=null;}
finally{loading=false;}
return snapshot;
}
function installStyle(){
if($('sunSaaSV16Style'))return;
const s=document.createElement('style');s.id='sunSaaSV16Style';s.textContent=`
.sun-plan-locked{position:relative!important;opacity:.62!important;filter:saturate(.65)}
header nav button.sun-plan-locked::after{content:'🔒';font-size:10px;margin-left:auto;padding-left:6px}
.sun-side-collapsed #sunSaaSBadgeV16{display:none!important}
.sun-saas-plan-card-locked{position:relative;opacity:.62}
.sun-saas-plan-card-locked::after{content:'Доступно на другом тарифе';position:absolute;inset:auto 12px 12px auto;background:#241f3b;color:#fff;border-radius:999px;padding:5px 9px;font-size:11px;font-weight:800;z-index:4}
.sun-saas-badge{margin:8px 10px 0;border:1px solid #ffffff24;border-radius:11px;padding:8px 9px;color:#fff;background:#ffffff10;font-size:11px;line-height:1.35}
.sun-saas-badge b{display:block;font-size:12px}.sun-saas-badge .warn{color:#ffd36b}
.sun-saas-readonly-banner{position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:9800;background:#fff6df;border:1px solid #e2bd55;color:#6a5216;border-radius:12px;padding:9px 14px;box-shadow:0 8px 30px #0002;font-weight:800;max-width:min(680px,calc(100vw - 24px));text-align:center}
.sun-saas-blocked{position:fixed;inset:0;z-index:12000;background:#122b3cf2;display:grid;place-items:center;padding:20px}.sun-saas-blocked-card{width:min(520px,100%);background:#fff;border-radius:18px;padding:24px;box-shadow:0 30px 90px #0007;text-align:center}.sun-saas-blocked-card img{width:72px;height:72px;object-fit:contain}.sun-saas-blocked-card h2{margin:8px 0;color:#17384d}.sun-saas-blocked-card p{color:#596773;line-height:1.5}
.sun-saas-modal{position:fixed;inset:0;z-index:11500;background:#0d2130c9;display:grid;place-items:center;padding:18px}.sun-saas-modal-card{width:min(900px,100%);max-height:92vh;overflow:auto;background:#fff;border-radius:18px;padding:20px;box-shadow:0 30px 90px #0007}.sun-saas-modal-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.sun-saas-modal-head h2{margin:0;color:#17384d}.sun-saas-modal-head button{border:0;background:#eef1f2;border-radius:50%;width:34px;height:34px;font-size:20px}.sun-saas-plans{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}.sun-saas-plan{border:1px solid #dfe5e8;border-radius:14px;padding:15px;background:#fafcfc}.sun-saas-plan.current{border:2px solid #ffb400;background:#fffaf0}.sun-saas-plan h3{margin:0 0 5px;color:#18384b}.sun-saas-plan ul{padding-left:18px;color:#4f5b64;font-size:12px;line-height:1.55}.sun-saas-plan .muted{color:#89939a}.sun-saas-upgrade{margin-top:12px;background:#eef4f6;border-radius:12px;padding:12px;color:#52606a}
.sun-saas-settings-card .sun-saas-status-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px;margin-top:12px}.sun-saas-kpi{border:1px solid #e2e7ea;border-radius:11px;padding:10px;background:#fafcfc}.sun-saas-kpi small{display:block;color:#7a858d;margin-bottom:4px}.sun-saas-kpi b{color:#203846}
#sun-saas-admin{padding:20px}.sun-saas-admin-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}.sun-saas-admin-table{display:grid;gap:10px;margin-top:16px}.sun-saas-company{border:1px solid #dfe5e8;border-radius:14px;padding:14px;background:#fff}.sun-saas-company-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}.sun-saas-company h3{margin:0;color:#18384b}.sun-saas-company small{color:#78828a}.sun-saas-company-actions{display:grid;grid-template-columns:1fr 110px auto auto;gap:8px;margin-top:11px;align-items:end}.sun-saas-company-actions label{font-size:11px;color:#64717a}.sun-saas-company-actions select{width:100%}.sun-saas-company-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}.sun-saas-pill{border-radius:999px;padding:5px 8px;background:#eef3f5;font-size:11px}.sun-saas-pill.good{background:#eaf7ef;color:#347153}.sun-saas-pill.warn{background:#fff5dc;color:#8a681b}.sun-saas-pill.bad{background:#ffebe9;color:#993f3d}
.sun-saas-onboarding-tabs{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.sun-saas-onboarding-choice{border:1px solid #dfe5e8;background:#fafcfc;border-radius:12px;padding:12px;text-align:left}.sun-saas-onboarding-choice b{display:block;color:#17384d;margin-bottom:3px}.sun-saas-onboarding-choice small{color:#6f7980;line-height:1.35}
@media(max-width:760px){.sun-saas-plans{grid-template-columns:1fr}.sun-saas-settings-card .sun-saas-status-grid{grid-template-columns:1fr 1fr}.sun-saas-company-actions{grid-template-columns:1fr 1fr}.sun-saas-company-actions button{width:100%}}
`;document.head.appendChild(s);
}
function removeSubscriptionUi(){
$('sunSaaSBadgeV16')?.remove();$('sunSaaSReadonlyV16')?.remove();$('sunSaaSBlockedV16')?.remove();$('sunSaaSSettingsCardV16')?.remove();
qa('[data-sun-plan-locked]').forEach(el=>{el.removeAttribute('data-sun-plan-locked');el.classList.remove('sun-plan-locked')});
}
function featureForNav(btn){return NAV_FEATURE[String(btn.dataset.navLabel||btn.textContent||'').trim()]||null;}
function markElement(el,feature){
if(!el)return;
if(feature&&!hasFeature(feature)){el.dataset.sunPlanLocked=feature;el.classList.add('sun-plan-locked');el.title=`${FEATURE_LABELS[feature]||feature} недоступно на тарифе «${snapshot?.plan_name||''}»`;}
else{el.removeAttribute('data-sun-plan-locked');el.classList.remove('sun-plan-locked');}
}
function applyFeatureGates(){
if(!snapshot)return;
qa('header nav button').forEach(btn=>{if(btn.id==='sunSaaSAdminNavV16')return;markElement(btn,featureForNav(btn));});
markElement($('sunClientOfferButton'),'client_offers');markElement($('sunAutoOfferButton'),'client_offers');markElement($('sunCloudManageUsersV3'),'users_manage');markElement($('sunCloudCreateInviteV2'),'users_manage');
qa('#new .catalog-head button,#new .tile.add').forEach(el=>markElement(el,'catalog_edit'));
const cards=[
['sunClientOfferSettingsCard','client_offers'],['sunOfferTemplateSettingsCard','offer_templates'],['sunBrandThemeSettingsCard','branding']
];
cards.forEach(([id,feature])=>{const el=$(id);if(!el)return;el.dataset.sunFeature=feature;el.classList.toggle('sun-saas-plan-card-locked',!hasFeature(feature));});
qa('#enterprise-settings section').forEach(card=>{
const h=(card.querySelector(':scope > h2')?.textContent||'').trim();let f=null;
if(/Резервн/i.test(h))f='backups';else if(/История изменений/i.test(h))f='audit';
if(f){card.dataset.sunFeature=f;card.classList.toggle('sun-saas-plan-card-locked',!hasFeature(f));}
});
}
function guardFunctions(){
Object.entries(WRITE_FN_FEATURE).forEach(([name,feature])=>{
const fn=window[name];if(typeof fn!=='function')return;
if(fn.__sunSaasGuard)return;
const wrapped=function(...args){
if(!isWritable())return showUpgrade(feature,'Подписка сейчас работает в режиме просмотра.');
if(!hasFeature(feature))return showUpgrade(feature);
return fn.apply(this,args);
};
wrapped.__sunSaasGuard=true;wrapped.__sunSaasOriginal=fn;window[name]=wrapped;guardedFns.set(name,wrapped);
});
}
function injectBadge(){
if(!snapshot)return;
let badge=$('sunSaaSBadgeV16');
const brand=document.querySelector('header .brand');
if(!brand)return;
if(!badge){badge=document.createElement('div');badge.id='sunSaaSBadgeV16';badge.className='sun-saas-badge';brand.insertAdjacentElement('afterend',badge);}
const d=daysLeft(), trial=snapshot.status==='trialing';
badge.innerHTML=`<b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id]||'Тариф')}</b><span class="${snapshot.access_mode==='full'?'':'warn'}">${trial?`Пробный период${d!=null?` · ${Math.max(0,d)} дн.`:''}`:accessLabel(snapshot.access_mode)}</span>`;
badge.onclick=()=>showPlans();badge.title='Тариф и подписка';badge.style.cursor='pointer';
}
function applyAccessMode(){
$('sunSaaSReadonlyV16')?.remove();$('sunSaaSBlockedV16')?.remove();
if(!snapshot)return;
applyReadOnlyControls();
if(snapshot.access_mode==='read_only'){
const b=document.createElement('div');b.id='sunSaaSReadonlyV16';b.className='sun-saas-readonly-banner';b.textContent=`Подписка закончилась. До ${formatDate(snapshot.grace_until)} база доступна только для просмотра.`;document.body.appendChild(b);
}else if(snapshot.access_mode==='blocked'){
const x=document.createElement('div');x.id='sunSaaSBlockedV16';x.className='sun-saas-blocked';x.innerHTML=`<div class="sun-saas-blocked-card"><img src="sun-logo.png" alt=""><h2>Доступ к компании приостановлен</h2><p>Подписка закончилась. Все данные сохранены и будут доступны сразу после продления.</p><div class="actions" style="justify-content:center"><button class="primary" type="button" data-saas-show-plans>Посмотреть тарифы</button><button class="outline" type="button" data-saas-signout>Выйти</button></div></div>`;document.body.appendChild(x);
x.querySelector('[data-saas-show-plans]').onclick=()=>showPlans();x.querySelector('[data-saas-signout]').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut()}catch(_){}location.reload();};
}
}
function applyReadOnlyControls(){
qa('[data-saas-readonly-disabled]').forEach(el=>{el.disabled=false;el.removeAttribute('data-saas-readonly-disabled')});
if(snapshot?.access_mode!=='read_only'&&!cloud()?.isSupportMode?.())return;
qa('#new input,#new textarea,#new select,#manager input,#manager textarea,#manager select,#editor input,#editor textarea,#editor select,#orders select,#enterprise-settings input,#enterprise-settings textarea,#enterprise-settings select').forEach(el=>{if(!el.disabled){el.disabled=true;el.dataset.saasReadonlyDisabled='1';}});
}
function showPlans(feature=null,note=''){
$('sunSaaSPlansModalV16')?.remove();
const m=document.createElement('div');m.id='sunSaaSPlansModalV16';m.className='sun-saas-modal';
const cards=[
{id:'basic',name:'Базовый',members:'1 пользователь',inc:['Заказы, календарь и клиенты','Каталог для работы с заказом','Базовая статистика и печать'],out:['Редактирование боксов','Предложения клиенту','Финансы и склад']},
{id:'professional',name:'Профессиональный',members:'до 3 пользователей',inc:['Всё из Базового','Производство, склад и закупки','Финансы, поставщики и маршруты','Фирменное оформление'],out:['Редактирование боксов','Предложения клиенту']},
{id:'full',name:'Полный',members:'без лимита',inc:['Все функции приложения','Редактирование каталога и боксов','Предложения клиенту и шаблоны','Команда, права и расширенные настройки'],out:[]}
];
m.innerHTML=`<div class="sun-saas-modal-card"><div class="sun-saas-modal-head"><div><h2>Тарифы</h2><div class="hint">${feature?`Для функции «${esc(FEATURE_LABELS[feature]||feature)}» нужен другой тариф.`:'Выберите набор функций для компании.'}</div></div><button type="button" data-close>×</button></div>${note?`<div class="sun-saas-upgrade">${esc(note)}</div>`:''}<div class="sun-saas-plans">${cards.map(p=>`<div class="sun-saas-plan ${snapshot?.plan_id===p.id?'current':''}"><h3>${p.name}</h3><div class="hint">${p.members}</div><ul>${p.inc.map(x=>`<li>${esc(x)}</li>`).join('')}${p.out.map(x=>`<li class="muted">— ${esc(x)}</li>`).join('')}</ul>${snapshot?.plan_id===p.id?'<b>Текущий тариф</b>':'<span class="hint">Подключение оплаты добавим на следующем этапе.</span>'}</div>`).join('')}</div><div class="sun-saas-upgrade">Сейчас тариф можно изменить из вашего SaaS-кабинета. Автоматическую оплату подключим отдельным этапом.</div></div>`;
document.body.appendChild(m);m.onclick=e=>{if(e.target===m||e.target.closest('[data-close]'))m.remove();};
}
function injectSettingsCard(){
if(!snapshot)return;
const settings=$('enterprise-settings');if(!settings)return;
let card=$('sunSaaSSettingsCardV16');if(!card){card=document.createElement('section');card.id='sunSaaSSettingsCardV16';card.className='enterprise-card wide sun-saas-settings-card';const first=settings.querySelector('.enterprise-grid')||settings;first.prepend(card);}
const end=endDate(),d=daysLeft();
card.innerHTML=`<h2>Тариф и подписка</h2><p class="hint">Подписка относится ко всей компании. Данные этой рабочей базы изолированы от других компаний.</p><div class="sun-saas-status-grid"><div class="sun-saas-kpi"><small>Тариф</small><b>${esc(snapshot.plan_name||PLAN_LABELS[snapshot.plan_id])}</b></div><div class="sun-saas-kpi"><small>Статус</small><b>${esc(statusLabel(snapshot.status))}</b></div><div class="sun-saas-kpi"><small>${snapshot.status==='trialing'?'Пробный период до':'Оплачено до'}</small><b>${formatDate(end)}${d!=null&&d>=0?` · ${d} дн.`:''}</b></div><div class="sun-saas-kpi"><small>Сотрудники</small><b>${Number(snapshot.member_count||0)} / ${snapshot.max_members==null?'∞':snapshot.max_members}</b></div></div><div class="actions" style="margin-top:12px"><button class="outline" type="button" data-saas-plans>Сравнить тарифы</button>${snapshot.platform_admin?'<button class="primary" type="button" data-saas-admin>Управление SaaS</button>':''}</div>`;
card.querySelector('[data-saas-plans]').onclick=()=>showPlans();card.querySelector('[data-saas-admin]')?.addEventListener('click',openPlatformAdmin);
}
function ensureAdminView(){
if($('sun-saas-admin'))return $('sun-saas-admin');
const v=document.createElement('section');v.id='sun-saas-admin';v.className='view page';document.body.appendChild(v);return v;
}
function ensureAdminNav(){
const nav=document.querySelector('header nav');if(!nav||!snapshot?.platform_admin)return;
if(window.SunDeveloperV22){$('sunSaaSAdminNavV16')?.remove();window.SunDeveloperV22.ensureNav?.();return;}
let b=$('sunSaaSAdminNavV16');if(!b){b=document.createElement('button');b.id='sunSaaSAdminNavV16';b.type='button';b.dataset.navLabel='SaaS';b.textContent='SaaS';nav.appendChild(b);b.onclick=()=>openPlatformAdmin(b);}
b.hidden=false;b.style.display='';
}
async function openPlatformAdmin(btn=null){
if(window.SunDeveloperV22?.open)return window.SunDeveloperV22.open(btn);
if(!snapshot?.platform_admin)return;
const v=ensureAdminView();qa('.view').forEach(x=>x.classList.remove('on'));v.classList.add('on');qa('header nav button').forEach(x=>x.classList.remove('on'));(btn||$('sunSaaSAdminNavV16'))?.classList.add('on');
v.innerHTML='<div class="sun-saas-admin-head"><div><h1>SaaS · компании</h1><p class="hint">Тарифы, сроки и доступы всех компаний.</p></div><button class="outline" type="button" data-reload>Обновить</button></div><p class="hint">Загружаю компании…</p>';
v.querySelector('[data-reload]').onclick=()=>loadAdminRows();await loadAdminRows();
}
async function loadAdminRows(){
const v=ensureAdminView(),c=client();if(!c)return;
const keepHead=v.querySelector('.sun-saas-admin-head')?.outerHTML||'';v.innerHTML=keepHead+'<p class="hint">Загружаю компании…</p>';v.querySelector('[data-reload]')?.addEventListener('click',loadAdminRows);
const {data,error}=await c.rpc('sun_dev_list_companies');if(error){v.insertAdjacentHTML('beforeend',`<p class="hint">${esc(error.message)}</p>`);return;}
adminRows=data||[];
const list=document.createElement('div');list.className='sun-saas-admin-table';list.innerHTML=adminRows.map(r=>adminRowHtml(r)).join('')||'<p class="hint">Компаний пока нет.</p>';v.appendChild(list);bindAdminRows(list);
}
function adminRowHtml(r){
const access=r.access_mode,cls=access==='full'?'good':access==='read_only'?'warn':'bad';
return `<section class="sun-saas-company" data-ws="${esc(r.workspace_id)}"><div class="sun-saas-company-top"><div><h3>${esc(r.workspace_name)}</h3><small>${esc(r.workspace_id)}</small></div><div class="sun-saas-company-meta"><span class="sun-saas-pill ${cls}">${esc(accessLabel(access))}</span><span class="sun-saas-pill">${esc(PLAN_LABELS[r.plan_id]||r.plan_name||'—')}</span><span class="sun-saas-pill">${Number(r.member_count||0)} / ${r.max_members==null?'∞':r.max_members} сотрудников</span><span class="sun-saas-pill">до ${formatDate(r.trial_ends_at||r.current_period_end)}</span></div></div><div class="sun-saas-company-actions"><label>Тариф<select data-plan><option value="basic" ${r.plan_id==='basic'?'selected':''}>Базовый</option><option value="professional" ${r.plan_id==='professional'?'selected':''}>Профессиональный</option><option value="full" ${r.plan_id==='full'?'selected':''}>Полный</option></select></label><label>Срок<select data-days><option value="30">30 дней</option><option value="90">90 дней</option><option value="365">1 год</option></select></label><button class="primary" type="button" data-activate>Активировать</button><button class="outline" type="button" data-expire>Заблокировать</button></div><details style="margin-top:10px"><summary>Индивидуальная функция</summary><div class="sun-saas-company-actions" style="grid-template-columns:1fr 120px auto"><label>Функция<select data-feature>${PLATFORM_FEATURES.map(f=>`<option value="${f}">${esc(FEATURE_LABELS[f]||f)}</option>`).join('')}</select></label><label>Срок<select data-feature-days><option value="30">30 дней</option><option value="90">90 дней</option><option value="365">1 год</option></select></label><button class="outline" type="button" data-feature-enable>Выдать доступ</button></div></details></section>`;
}
function bindAdminRows(root){
qa('.sun-saas-company',root).forEach(row=>{
const ws=row.dataset.ws;
row.querySelector('[data-activate]').onclick=async()=>{const plan=row.querySelector('[data-plan]').value,days=Number(row.querySelector('[data-days]').value);await setSubscription(ws,plan,days,'active');};
row.querySelector('[data-expire]').onclick=async()=>{if(!confirm('Заблокировать доступ этой компании? Данные не удалятся.'))return;await setSubscription(ws,row.querySelector('[data-plan]').value,0,'expired');};
row.querySelector('[data-feature-enable]').onclick=async()=>{const feature=row.querySelector('[data-feature]').value,days=Number(row.querySelector('[data-feature-days]').value);const r=await client().rpc('sun_dev_set_feature_override',{p_workspace:ws,p_feature:feature,p_enabled:true,p_days:days,p_note:'Выдано из SaaS-кабинета'});if(r.error)return toast(r.error.message,'error');toast('Индивидуальный доступ выдан.','success');};
});
}
async function setSubscription(ws,plan,days,status){
const {error}=await client().rpc('sun_dev_set_subscription',{p_workspace:ws,p_plan:plan,p_days:days,p_status:status});if(error)return toast(error.message,'error');toast('Подписка обновлена.','success');await loadAdminRows();if(workspace()?.id===ws)await refreshSnapshot(true);
}
function clearWorkspaceLocalData(){
const preserve=new Set(['sunCloudV2Config','sunCloudClientIdV2','sunStaticMapGeocodeCacheV2']);
const keys=[];for(let i=0;i<localStorage.length;i++){const k=localStorage.key(i);if(k?.startsWith('sun')&&!preserve.has(k))keys.push(k);}keys.forEach(k=>localStorage.removeItem(k));
}
function seedWorkspace(mode){
clearWorkspaceLocalData();
if(mode==='demo'){
const demoBoxes=[
{id:'demo-box-1',name:'Демо · Рыбные канапе',category:0,catalogSection:'Боксы',price:4500,weight:520,guests:10,photo:'catalog/001.jpg',composition:['лосось','креветка','крем-чиз'],ingredients:[['Канапе рыбные',30,'шт.']]},
{id:'demo-box-2',name:'Демо · Мясные закуски',category:0,catalogSection:'Боксы',price:4200,weight:500,guests:10,photo:'catalog/002.jpg',composition:['ростбиф','пастрами','овощи'],ingredients:[['Канапе мясные',30,'шт.']]},
{id:'demo-box-3',name:'Демо · Десертный бокс',category:0,catalogSection:'Боксы',price:3900,weight:430,guests:10,photo:'catalog/003.jpg',composition:['мини-десерты','ягоды'],ingredients:[['Мини-десерты',20,'шт.']]}
];
const demoOrders=[{id:1,event:'Демо · День рождения',date:new Date(Date.now()+7*86400000).toISOString().slice(0,10),time:'18:00',contact:'Анна',phone:'+7 900 000-00-00',address:'Демо-адрес',status:'Новый',guestsCount:20,prepayment:0,lines:[{id:'demo-box-1',qty:2},{id:'demo-box-2',qty:2}]}];
localStorage.setItem('sunBoxes',JSON.stringify(demoBoxes));localStorage.setItem('sunOrders',JSON.stringify(demoOrders));
}else{
let starter=[];try{if(Array.isArray(window.SUN_OFFICIAL_CATALOG))starter=structuredClone(window.SUN_OFFICIAL_CATALOG)}catch(_){}
localStorage.setItem('sunBoxes',JSON.stringify(starter));localStorage.setItem('sunOrders','[]');
if(window.SUN_OFFICIAL_CATALOG_VERSION)localStorage.setItem('sunOfficialCatalogVersion',String(window.SUN_OFFICIAL_CATALOG_VERSION));
}
}
function enhanceOnboardingGate(){
const gate=$('sunCloudAuthGateV3'),ss=session(),ws=workspace(),st=cloud()?.status?.();if(window.SunDeveloperV22?.isPlatformAdmin?.())return;if(!gate||!ss?.user||ws||st?.membershipsLoading||st?.membershipsLoaded===false||gate.dataset.saasEnhanced==='1')return;
gate.dataset.saasEnhanced='1';const card=gate.querySelector('.sun-cloud-auth-card');if(!card)return;
card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="sun-logo.png" alt=""><div><h2>Создайте компанию</h2><div class="hint">${esc(ss.user.email||'Аккаунт авторизован')} · 14 дней Полного тарифа бесплатно</div></div></div><p class="hint">У этого аккаунта пока нет рабочей базы. Приглашения временно отключены на период локального тестирования.</p><label>Название компании<input id="sunSaaSCompanyNameV16" value="Моя кейтеринговая компания" autocomplete="organization"></label><div class="sun-saas-onboarding-tabs"><button class="sun-saas-onboarding-choice" type="button" data-create="empty"><b>Новая компания</b><small>Готовый каталог боксов, но без заказов, клиентов и рабочих данных.</small></button><button class="sun-saas-onboarding-choice" type="button" data-create="demo"><b>Загрузить демо</b><small>Примеры боксов и один тестовый заказ.</small></button></div><div class="sun-cloud-auth-actions" style="margin-top:14px"><button class="outline" type="button" data-signout>Выйти</button></div><div class="sun-cloud-auth-error" id="sunSaaSGateErrorV16"></div>`;
const err=$('sunSaaSGateErrorV16');
qa('[data-create]',card).forEach(b=>b.onclick=async()=>{
const name=String($('sunSaaSCompanyNameV16')?.value||'').trim();if(!name){err.textContent='Введите название компании.';return;}b.disabled=true;err.textContent='Создаю рабочую базу…';
try{
const r=await client().rpc('sun_create_workspace',{p_name:name});if(r.error)throw r.error;
const workspaceId=String(r.data||'');
seedWorkspace(b.dataset.create);await cloud().adoptCurrentLocalAsWorkspace?.(workspaceId);await cloud().reloadMemberships();err.textContent='Загружаю стартовую базу…';await cloud().migrate();location.reload();
}catch(e){err.textContent=e?.message||String(e);b.disabled=false;}
});
card.querySelector('[data-signout]').onclick=async()=>{if(window.SunCloudV2?.signOut)return window.SunCloudV2.signOut();try{await client()?.auth.signOut()}catch(_){}location.reload();};
}
function applyAll(){installStyle();injectBadge();applyAccessMode();applyFeatureGates();guardFunctions();injectSettingsCard();ensureAdminNav();}
document.addEventListener('click',e=>{
const locked=e.target.closest('[data-sun-plan-locked]');if(locked){e.preventDefault();e.stopImmediatePropagation();return showUpgrade(locked.dataset.sunPlanLocked);}
const card=e.target.closest('.sun-saas-plan-card-locked');if(card){e.preventDefault();e.stopImmediatePropagation();return showUpgrade(card.dataset.sunFeature);}
if(snapshot?.access_mode==='read_only'||cloud()?.isSupportMode?.()){
const t=e.target.closest('button');if(!t)return;
const text=(t.textContent||'').trim();const safe=/^(Закрыть|Обновить|Предварительный просмотр|Печать|Скачать|Сравнить тарифы|Выйти|Назад|Фильтр|Поиск)/i.test(text);
const mutating=/Сохран|Удал|Добав|Создать|Измен|Настро|Списать|Отправ|Активир|Заблокир|Выдать|Примен/i.test(text);
if(mutating&&!safe){e.preventDefault();e.stopImmediatePropagation();showUpgrade(null,cloud()?.isSupportMode?.()?'Режим поддержки: данные компании доступны только для просмотра.':'Льготный период: база доступна только для просмотра.');}
}
},true);
function showUpgrade(feature,note=''){showPlans(feature,note);}
function boot(){
installStyle();
const timer=setInterval(async()=>{
if(document.hidden)return;
// v17.5.27: onboarding is handled by the unified Caterium auth gate.
const ws=workspace();
if(ws?.id&&(ws.id!==lastWorkspaceId||!snapshot))await refreshSnapshot(true);
else if(ws?.id&&snapshot){applyFeatureGates();guardFunctions();injectSettingsCard();ensureAdminNav();}
else if(!ws){snapshot=null;lastWorkspaceId='';}
},5000);
window.addEventListener('sun:cloud-state-applied',()=>refreshSnapshot(true));
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(()=>{applyFeatureGates();guardFunctions();},50));
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')setTimeout(()=>{injectSettingsCard();applyFeatureGates();},100);},true);
setTimeout(()=>refreshSnapshot(true),1200);
}
window.SunSaaSV16={VERSION,getSnapshot:()=>snapshot?structuredClone(snapshot):null,refresh:()=>refreshSnapshot(true),hasFeature,isWritable,showPlans,openAdmin:openPlatformAdmin};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
;
/* ===== MODULE: settings-polish-v1.js ===== */
/* Sun Catering v17.5.4: settings layout polish */
(()=>{
'use strict';
if(window.__sunSettingsPolishV1)return;
window.__sunSettingsPolishV1=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const style=document.createElement('style');
style.id='sun-settings-polish-v1-style';
style.textContent=`
#enterprise-settings>.catalog-head{
position:sticky;top:0;z-index:45;
margin:0 -4px 14px;padding:11px 4px 12px;
background:linear-gradient(180deg,rgba(255,255,255,.99) 0%,rgba(255,255,255,.96) 80%,rgba(255,255,255,0) 100%);
backdrop-filter:blur(9px);
}
#enterprise-settings .sun-settings-cloud-tail{grid-column:1/-1!important}
`;
document.head.appendChild(style);
function settingsNavButton(){
return qa('header nav button').find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки')||null;
}
function scrollSettingsTop(){
const view=$('enterprise-settings');
try{window.scrollTo({top:0,left:0,behavior:'auto'})}catch(_){window.scrollTo(0,0)}
try{document.scrollingElement&&(document.scrollingElement.scrollTop=0)}catch(_){}
try{view&&(view.scrollTop=0)}catch(_){}
view?.querySelector('.catalog-head')?.scrollIntoView?.({block:'start',inline:'nearest'});
}
function auditCard(grid){
return qa(':scope > .enterprise-card',grid).find(card=>(card.querySelector(':scope > h2')?.textContent||'').trim()==='История изменений')||null;
}
function cloudCard(grid){
return $('sunCloudV2Card')||qa(':scope > .enterprise-card',grid).find(card=>/^(Облачная синхронизация|Облако Supabase)$/.test((card.querySelector(':scope > h2')?.textContent||'').trim()))||null;
}
function moveBefore(node,anchor){
if(!node||!anchor||node===anchor||node.parentElement!==anchor.parentElement)return false;
if(node.nextElementSibling===anchor)return false;
window.SunSafe.insertBefore(anchor.parentElement,node,anchor);return true;
}
let ordering=false;
function orderSettingsCards(){
if(ordering)return;
const grid=document.querySelector('#enterprise-settings .enterprise-grid');if(!grid)return;
ordering=true;
try{
const audit=auditCard(grid);
if(!audit)return;
const requisites=$('sunReceiptSettingsCard');
const qr=$('sunDefaultQrCard');
const blank=$('sunBlankSettingsCardV3');
const cloud=cloudCard(grid);
if(cloud)cloud.classList.add('sun-settings-cloud-tail','wide');
// Bottom operational block: company requisites -> QR -> order blank -> cloud -> history.
// Reinsert in the desired order so dynamically injected cards settle into one stable tail block.
[requisites,qr,blank,cloud].forEach(card=>{if(card&&grid.contains(card))moveBefore(card,audit)});
}finally{ordering=false;}
}
let timer=0;
function scheduleOrder(delay=40){clearTimeout(timer);timer=setTimeout(orderSettingsCards,delay)}
function boot(){
const btn=settingsNavButton();
btn?.addEventListener('click',()=>{
scrollSettingsTop();
setTimeout(scrollSettingsTop,0);
setTimeout(scrollSettingsTop,100);
scheduleOrder(160);
scheduleOrder(420);
},true);
const view=$('enterprise-settings');
if(view){
new MutationObserver(()=>scheduleOrder(30)).observe(view,{childList:true,subtree:true});
if(view.classList.contains('on')){scrollSettingsTop();scheduleOrder(120)}
}
window.addEventListener('sun:cloud-state-applied',()=>scheduleOrder(80));
window.addEventListener('suncloudsync',()=>scheduleOrder(80));
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
;
/* ===== MODULE: address-verify-v1.js ===== */
/* Sun Catering v17.5.4: explicit precise-address verification */
(()=>{
'use strict';
if(window.__sunAddressVerifyV1)return;
window.__sunAddressVerifyV1=true;
const $=id=>document.getElementById(id);
const esc=window.SunSafe.escapeHTML;
const val=id=>String($(id)?.value||'').trim();
const CACHE_KEY='sunPreciseAddressSearchCacheV1';
let lastRequest=0,selected=null,searchSeq=0,applying=false;
const style=document.createElement('style');
style.id='sun-address-verify-v1-style';
style.textContent=`
.sun-address-verify{margin-top:10px;border-top:1px solid #e5e8eb;padding-top:10px}
.sun-address-verify-note{font-size:11px;line-height:1.45;color:#6d7782;margin:0 0 9px}
.sun-address-candidates{display:grid;gap:7px}
.sun-address-candidate{width:100%;text-align:left;border:1px solid #dfe4e8;border-radius:10px;background:#fff;padding:9px 10px;cursor:pointer;color:#2a3541}
.sun-address-candidate:hover{border-color:#b9c7d0;background:#fbfcfd}
.sun-address-candidate.on{border-color:#d2a438;box-shadow:0 0 0 2px rgba(210,164,56,.15);background:#fffdf7}
.sun-address-candidate b{display:block;font-size:12px;line-height:1.35}.sun-address-candidate small{display:block;margin-top:3px;color:#75808a;line-height:1.35}
.sun-address-candidate .sun-address-chips{display:flex;gap:5px;flex-wrap:wrap;margin-top:6px}.sun-address-candidate .sun-address-chips span{font-size:9px;font-weight:800;padding:3px 6px;border-radius:999px;background:#eef2f4;color:#53616c}
.sun-address-selected{margin-top:9px;padding:9px 10px;border:1px solid #e4e7e9;border-radius:10px;background:#fafbfc;font-size:11px;color:#59656f;line-height:1.45}
#orderGeoConfirm{display:none;margin-top:9px;width:100%;justify-content:center}.sun-address-verify.busy{opacity:.7;pointer-events:none}
`;
document.head.appendChild(style);
function readCache(){try{return JSON.parse(localStorage.getItem(CACHE_KEY)||'{}')||{}}catch(_){return{}}}
function writeCache(cache){try{localStorage.setItem(CACHE_KEY,JSON.stringify(cache))}catch(_){} }
function cleanAddressParts(a={}){
return {
region:String(a.state||a.region||'').trim(),
locality:String(a.city||a.town||a.village||a.municipality||a.hamlet||a.isolated_dwelling||'').trim(),
district:String(a.city_district||a.state_district||a.county||a.borough||a.suburb||'').trim(),
street:String(a.road||a.pedestrian||a.residential||a.footway||a.path||'').trim(),
house:String(a.house_number||'').trim(),
postcode:String(a.postcode||'').trim()
};
}
function queryParts(){
const region=val('deliveryRegion')==='Другой регион'?'':val('deliveryRegion');
return [region,val('deliveryLocality'),val('deliveryDistrict'),val('deliveryStreet'),val('deliveryHouse')?`д. ${val('deliveryHouse')}`:'',val('deliveryCorpus')?`корп. ${val('deliveryCorpus')}`:''].filter(Boolean);
}
function queryText(){return queryParts().join(', ')}
function mapAddress(){return queryText()}
async function fetchJson(url,timeout=6500){
const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),timeout);
try{const r=await fetch(url,{method:'GET',mode:'cors',credentials:'omit',cache:'default',signal:controller.signal});if(!r.ok)throw new Error(`HTTP ${r.status}`);return await r.json();}
finally{clearTimeout(timer)}
}
async function searchNominatim(query){
const wait=Math.max(0,1100-(Date.now()-lastRequest));if(wait)await new Promise(r=>setTimeout(r,wait));lastRequest=Date.now();
const region=val('deliveryRegion');
const params=new URLSearchParams({format:'jsonv2',addressdetails:'1',limit:'5',countrycodes:'ru','accept-language':'ru',q:query});
if(region==='Санкт-Петербург'||region==='Ленинградская область'){
params.set('viewbox','27.3,61.4,34.5,58.0');params.set('bounded','1');
}else if(!region){params.set('viewbox','27.3,61.4,34.5,58.0');}
const data=await fetchJson(`https://nominatim.openstreetmap.org/search?${params.toString()}`);
return (Array.isArray(data)?data:[]).filter(r=>Number.isFinite(Number(r.lat))&&Number.isFinite(Number(r.lon))).map(r=>({
lat:Number(r.lat),lon:Number(r.lon),formatted:String(r.display_name||query),provider:'nominatim',address:r.address||{},parts:cleanAddressParts(r.address||{}),importance:Number(r.importance||0)
}));
}
async function searchPhoton(query){
const data=await fetchJson(`https://photon.komoot.io/api/?limit=5&lang=ru&q=${encodeURIComponent(query)}`);
return (Array.isArray(data?.features)?data.features:[]).map(f=>{
const c=f?.geometry?.coordinates,p=f?.properties||{};if(!Array.isArray(c)||!Number.isFinite(Number(c[0]))||!Number.isFinite(Number(c[1])))return null;
const address={state:p.state||'',city:p.city||'',town:p.city||'',county:p.county||'',road:p.street||p.name||'',house_number:p.housenumber||'',postcode:p.postcode||''};
return {lat:Number(c[1]),lon:Number(c[0]),formatted:[p.name,p.housenumber,p.street,p.district,p.city,p.county,p.state,p.postcode].filter(Boolean).join(', ')||query,provider:'photon',address,parts:cleanAddressParts(address),importance:0};
}).filter(Boolean);
}
async function searchCandidates(query){
const key=query.toLowerCase(),cache=readCache();
if(Array.isArray(cache[key])&&cache[key].length)return cache[key];
let rows=[];try{rows=await searchNominatim(query)}catch(_){rows=[]}
if(!rows.length){try{rows=await searchPhoton(query)}catch(_){rows=[]}}
if(rows.length){cache[key]=rows.slice(0,5);const keys=Object.keys(cache);if(keys.length>80)keys.slice(0,keys.length-80).forEach(k=>delete cache[k]);writeCache(cache)}
return rows.slice(0,5);
}
function candidateTitle(c){
const p=c.parts||{};
return [p.street,p.house?`д. ${p.house}`:'',p.locality].filter(Boolean).join(', ')||c.formatted;
}
function candidateMeta(c){
const p=c.parts||{};
return [p.district,p.region,p.postcode].filter(Boolean).join(' · ')||c.formatted;
}
function renderCandidates(rows){
const host=$('sunAddressCandidates');if(!host)return;
if(!rows.length){host.innerHTML='<div class="sun-address-selected">Точный вариант не найден. Добавьте населённый пункт или район и попробуйте ещё раз.</div>';return}
host.innerHTML=rows.map((c,i)=>`<button type="button" class="sun-address-candidate" data-address-candidate="${i}"><b>${esc(candidateTitle(c))}</b><small>${esc(candidateMeta(c))}</small><span class="sun-address-chips">${c.parts?.locality?`<span>${esc(c.parts.locality)}</span>`:''}${c.parts?.district?`<span>${esc(c.parts.district)}</span>`:''}${c.parts?.region?`<span>${esc(c.parts.region)}</span>`:''}</span></button>`).join('');
host.querySelectorAll('[data-address-candidate]').forEach(btn=>btn.addEventListener('click',()=>selectCandidate(rows[Number(btn.dataset.addressCandidate)],btn)));
}
function setExistingStatus(text,kind='warn'){
const s=$('orderGeoStatus');if(s){s.className=`geo-order-status ${kind}`;const span=s.querySelector('span');if(span)span.textContent=text}
}
function selectCandidate(candidate,button){
selected=candidate;
document.querySelectorAll('.sun-address-candidate').forEach(x=>x.classList.toggle('on',x===button));
const confirm=$('orderGeoConfirm');if(confirm){confirm.style.display='inline-flex';confirm.disabled=false}
const selectedBox=$('sunAddressSelected');if(selectedBox)selectedBox.innerHTML=`<b>Выбран вариант:</b><br>${esc(candidate.formatted)}<br><span>Проверьте точку ниже и нажмите «Адрес верный — использовать».</span>`;
const mini=$('orderGeoMini');if(mini&&window.SunStaticMap?.mediaHtml)mini.innerHTML=window.SunStaticMap.mediaHtml(candidate,'Проверка точного адреса')+'<div class="static-map-credit">© OpenStreetMap contributors</div>';
const precision=$('orderGeoPrecision');if(precision)precision.textContent=`${candidate.lat.toFixed(5)}, ${candidate.lon.toFixed(5)}`;
setExistingStatus('Проверьте выбранную точку','warn');
}
function setField(id,value,{onlyIf=false}={}){const el=$(id);if(!el||!value)return;if(onlyIf&&String(el.value||'').trim())return;el.value=value;el.dispatchEvent(new Event(el.tagName==='SELECT'?'change':'input',{bubbles:true}))}
function normalizeRegion(region){
if(/санкт[- ]?петербург/i.test(region))return 'Санкт-Петербург';
if(/ленинград/i.test(region))return 'Ленинградская область';
return region||'';
}
function composeFullAddress(){
if(typeof window.sunComposeDeliveryAddress==='function')return window.sunComposeDeliveryAddress();
const parts=[val('deliveryRegion'),val('deliveryLocality'),val('deliveryDistrict'),val('deliveryStreet'),val('deliveryHouse')?`д. ${val('deliveryHouse')}`:'',val('deliveryCorpus')?`корп. ${val('deliveryCorpus')}`:'',val('deliveryEntrance')?`подъезд ${val('deliveryEntrance')}`:'',val('deliveryFloor')?`этаж ${val('deliveryFloor')}`:'',val('deliveryApartment')?`кв. ${val('deliveryApartment')}`:'',val('deliveryOffice')?`офис ${val('deliveryOffice')}`:''].filter(Boolean);
const a=parts.join(', ');if($('address'))$('address').value=a;return a;
}
function getDraft(){try{return draft}catch(_){return null}}
function getOrders(){try{return orders}catch(_){return[]}}
function confirmCandidate(){
if(!selected)return;
const candidate=selected,p=candidate.parts||{};
applying=true;
const region=normalizeRegion(p.region);
if(region){
const regionSelect=$('deliveryRegion');
if(regionSelect&&[...regionSelect.options].some(o=>o.value===region||o.text===region)){regionSelect.value=region;regionSelect.dispatchEvent(new Event('change',{bubbles:true}))}
else if(regionSelect){const option=new Option(region,region);regionSelect.add(option);regionSelect.value=region;regionSelect.dispatchEvent(new Event('change',{bubbles:true}))}
}
const localityValue=(region==='Санкт-Петербург'&&/санкт[- ]?петербург/i.test(p.locality||''))?'':(p.locality||'');
const localityEl=$('deliveryLocality');if(localityEl){localityEl.value=localityValue;localityEl.dispatchEvent(new Event('input',{bubbles:true}))}
setField('deliveryDistrict',p.district||'',{onlyIf:false});
setField('deliveryStreet',p.street||val('deliveryStreet'),{onlyIf:false});
setField('deliveryHouse',p.house||val('deliveryHouse'),{onlyIf:false});
const full=composeFullAddress();
const d=getDraft();
if(d){
d.deliveryRegion=val('deliveryRegion');d.deliveryLocality=val('deliveryLocality');d.deliveryDistrict=val('deliveryDistrict');d.deliveryStreet=val('deliveryStreet');d.deliveryHouse=val('deliveryHouse');d.deliveryCorpus=val('deliveryCorpus');d.address=full;
d.geo={lat:candidate.lat,lon:candidate.lon,formatted:candidate.formatted,sourceMapAddress:mapAddress(),sourceAddress:full,verifiedAt:new Date().toISOString(),confirmedAt:new Date().toISOString(),confirmed:true,provider:candidate.provider||'nominatim',geoVersion:3};
if(d.id){const saved=getOrders().find(o=>String(o.id)===String(d.id));if(saved){Object.assign(saved,{deliveryRegion:d.deliveryRegion,deliveryLocality:d.deliveryLocality,deliveryDistrict:d.deliveryDistrict,deliveryStreet:d.deliveryStreet,deliveryHouse:d.deliveryHouse,deliveryCorpus:d.deliveryCorpus,address:full,geo:structuredClone(d.geo)});try{persist()}catch(_){}}}
}
applying=false;
if($('address'))$('address').dispatchEvent(new Event('input',{bubbles:true}));
setTimeout(()=>{
setExistingStatus('Адрес подтверждён','ok');
const precision=$('orderGeoPrecision');if(precision)precision.textContent=candidate.formatted;
const confirm=$('orderGeoConfirm');if(confirm){confirm.textContent='Адрес подтверждён ✓';confirm.disabled=true}
const create=$('orderGeoCreate');if(create)create.textContent='Проверить другой адрес';
try{window.SunEnterprise?.toast?.('Точка доставки подтверждена.','success')}catch(_){}
},20);
}
async function runSearch(){
const query=queryText();
if(!val('deliveryStreet')||!val('deliveryHouse')){try{window.SunEnterprise?.toast?.('Для точной проверки укажите улицу и дом.','warn')}catch(_){alert('Для точной проверки укажите улицу и дом.')}return}
const root=$('sunAddressVerify');if(root)root.classList.add('busy');
const seq=++searchSeq;selected=null;
const confirm=$('orderGeoConfirm');if(confirm){confirm.style.display='none';confirm.disabled=false;confirm.textContent='Адрес верный — использовать'}
const host=$('sunAddressCandidates');if(host)host.innerHTML='<div class="sun-address-selected">Ищу подходящие адреса…</div>';
setExistingStatus('Идёт поиск точного адреса','warn');
try{
const rows=await searchCandidates(query);if(seq!==searchSeq)return;renderCandidates(rows);setExistingStatus(rows.length?'Выберите точный вариант':'Адрес не найден',rows.length?'warn':'warn');
}finally{root?.classList.remove('busy')}
}
function resetVerification(){
if(applying)return;
selected=null;searchSeq++;
const host=$('sunAddressCandidates');if(host)host.innerHTML='';
const box=$('sunAddressSelected');if(box)box.innerHTML='';
const confirm=$('orderGeoConfirm');if(confirm){confirm.style.display='none';confirm.disabled=false;confirm.textContent='Адрес верный — использовать'}
const d=getDraft();if(d?.geo?.confirmed){d.geo=null}
}
function boot(){
const geo=$('orderGeoBox'),create=$('orderGeoCreate');if(!geo||!create)return;
let root=$('sunAddressVerify');
if(!root){
root=document.createElement('div');root.id='sunAddressVerify';root.className='sun-address-verify';
root.innerHTML='<p class="sun-address-verify-note">Чтобы не перепутать одинаковые улицы в разных населённых пунктах, нажмите «Проверить адрес», выберите полный вариант и подтвердите точку на карте.</p><div id="sunAddressCandidates" class="sun-address-candidates"></div><div id="sunAddressSelected" class="sun-address-selected" style="display:none"></div><div style="margin-top:8px;font-size:10px;color:#7a838a">Поиск адреса: OpenStreetMap/Nominatim. © OpenStreetMap contributors.</div>';
const mini=$('orderGeoMini');if(mini)window.SunSafe.insertBefore(geo,root,mini);else geo.appendChild(root);
const selectedBox=$('sunAddressSelected');new MutationObserver(()=>{selectedBox.style.display=selectedBox.textContent.trim()?'block':'none'}).observe(selectedBox,{childList:true,subtree:true,characterData:true});
}
let confirm=$('orderGeoConfirm');
if(!confirm){confirm=document.createElement('button');confirm.id='orderGeoConfirm';confirm.type='button';confirm.className='primary';confirm.textContent='Адрес верный — использовать';$('orderGeoMini')?.insertAdjacentElement('afterend',confirm);confirm.addEventListener('click',confirmCandidate)}
create.textContent='Проверить адрес';
// Capture before the legacy handler so the app no longer silently accepts the first geocoder result.
create.addEventListener('click',e=>{e.preventDefault();e.stopImmediatePropagation();runSearch()},true);
['deliveryRegion','deliveryLocality','deliveryDistrict','deliveryStreet','deliveryHouse','deliveryCorpus'].forEach(id=>$(id)?.addEventListener(id==='deliveryRegion'?'change':'input',resetVerification));
$('address')?.addEventListener('input',()=>setTimeout(()=>{const d=getDraft();if(create&&!d?.geo?.confirmed)create.textContent='Проверить адрес'},0));
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(boot,50),{once:true});else setTimeout(boot,50);
})();
;
/* ===== MODULE: catalog-photo-resolver-v17.5.14.js ===== */
(function(){
'use strict';
if(window.__sunCatalogPhotoResolver17514)return;window.__sunCatalogPhotoResolver17514=true;
const SOURCE_URLS=["https://static.tildacdn.com/stor3131-6532-4762-a534-353638663730/11f4ce48f250257ec824a12e1c541150.jpg","https://static.tildacdn.com/stor6634-3466-4635-a164-386637346334/e94521aba11d1824218728cee4312c03.jpg","https://static.tildacdn.com/stor3034-3036-4334-b233-303363326236/d72f45ce6d6a044d8d892a0718f29d89.jpg","https://static.tildacdn.com/stor3662-3435-4561-b031-663437646233/efd5e71d2a8654b0afa26a6dd7a0595a.jpg","https://static.tildacdn.com/stor6133-6537-4533-a166-666430373166/014bff38be088e524c0a4fd2ee1daab3.jpg","https://static.tildacdn.com/stor6161-3163-4032-b338-363935363839/7b9abb2bc27c854236e234383f044c20.jpg","https://static.tildacdn.com/stor3065-6462-4666-b134-376564613831/527188ad5747060eaa88cdd6b033e1b7.jpg","https://static.tildacdn.com/stor6238-3635-4936-a361-343062343130/6c2841340110a32dee16481cf5776858.jpg","https://static.tildacdn.com/stor6231-3837-4666-b564-656661306534/a9b0efe3696fa0cd6530e3100cce2447.jpg","https://static.tildacdn.com/stor3162-6131-4337-b731-376266363535/6998754c49e80fca1df239d01e91b239.jpg","https://static.tildacdn.com/stor6264-3139-4939-b331-623963306665/c62d84a2c9de7fd115f9878617709a99.jpg","https://static.tildacdn.com/stor3134-6638-4635-b032-623233383830/413674a63540f26bbcded51a2f37c3a7.jpg","https://static.tildacdn.com/stor6438-6236-4831-b038-346437643037/b7305f8e759e161407a9996e0218cf83.jpg","https://static.tildacdn.com/stor3134-6638-4635-b032-623233383830/413674a63540f26bbcded51a2f37c3a7.jpg","https://static.tildacdn.com/stor3536-6463-4535-a135-653763366634/3b6eec9a6310eea531c8cb70b00955a9.jpg","https://static.tildacdn.com/stor3339-6634-4634-b537-363366353636/7ef8a4de7cfc055f633ab29ad7e5fca4.jpg","https://static.tildacdn.com/stor3030-3766-4661-b364-313536363731/f7a5bb4329e864b12eb5aed656c13c2b.jpg","https://static.tildacdn.com/stor3865-3463-4832-a531-326663623661/0ab2f6a1959adeb68f6c64e21fc52e6c.jpg","https://static.tildacdn.com/stor3834-6339-4234-a638-656236303531/d70c6ff9606b8f29f4d0a334643c6fa5.jpg","https://static.tildacdn.com/stor3038-3736-4564-a635-333961643763/ae631a3930ee087c993f0eb5ae477388.jpg","https://static.tildacdn.com/stor3638-3139-4562-b430-356363633431/a0eae79a99b4f1ca59be8a445d975f94.jpg","https://static.tildacdn.com/stor3536-3766-4033-b235-643062373262/1e521931b938e17b0ebe397a447067bb.jpg","https://static.tildacdn.com/stor3966-6635-4139-b965-666363393466/df421e8c29457bb6a660810c14d4dbca.jpg","https://static.tildacdn.com/stor3133-6661-4839-b565-376433313839/471052a775dfa8fae813f1b043171e21.jpg","https://static.tildacdn.com/stor3539-3831-4031-b532-323963396137/235a566973dc035faa18ef9a57b4bef6.jpg","https://static.tildacdn.com/stor6362-6234-4063-b636-373636373465/6c868c53823c2c2799bacbef8b7ec5dc.jpg","https://static.tildacdn.com/stor3439-6237-4364-a566-666531346532/a368a54d5dd3ef381bdb587f81dd3953.jpg","https://static.tildacdn.com/stor6664-6363-4635-b632-383362383935/f5779eef4eaff67dec0cb8a9868fe912.jpg","https://static.tildacdn.com/stor6166-3066-4535-b333-356263356363/25951fa13b57555854635d8fe1a1ecc9.jpg","https://static.tildacdn.com/stor3131-3637-4837-b039-386331346435/c4fd5306cc629e81fe974d0e1238eb06.jpg","https://static.tildacdn.com/stor3235-3535-4639-b164-663664323936/c3dc9dc2931b0f950034495dd8cc6b46.jpg","https://static.tildacdn.com/stor3235-3661-4038-b035-613032303361/6f2c3ac522ad9d7e73d14f3fc4c47630.jpg","https://static.tildacdn.com/stor6166-3066-4535-b333-356263356363/25951fa13b57555854635d8fe1a1ecc9.jpg","https://static.tildacdn.com/stor3836-3438-4835-a132-663962396637/fac8bc386bd98959d75c6b71cd83471b.jpg","https://static.tildacdn.com/stor3334-6361-4633-a266-643132323266/74b63ae226e1ebfbd116a822be4f642e.jpg","https://static.tildacdn.com/stor3136-3834-4562-b037-396264363263/e8c1bbdff0574f52a2184a33d6f2115c.jpg","https://static.tildacdn.com/stor6334-3534-4266-a664-356162323565/c13d0896543e32d12ccad0c0a108dac5.jpg","https://static.tildacdn.com/stor3364-6237-4561-b432-326230363433/3233d197bebae6174651a7732ec8ca3e.jpg","https://static.tildacdn.com/stor3239-6231-4130-a363-643732356464/1a72939cda10b9fc447157193a2c69cf.jpg","https://static.tildacdn.com/stor3832-3732-4635-b732-636135393265/3e414b433c0aab18c0a1131d256c92e5.jpg","https://static.tildacdn.com/stor6431-3634-4935-a638-323064343638/8d7c3b547b6e5abd09fe498c2e0b3853.jpg","https://static.tildacdn.com/stor3362-3331-4631-b738-646138333763/b3932c942568e83ab0babc98b77ee5c3.jpg","https://static.tildacdn.com/stor3565-6565-4739-b638-343639336130/ce325e928513a5c7ad2a5c9328927bd0.jpg","https://static.tildacdn.com/stor3131-3335-4135-b962-356338643761/dabf8e59f8b405c2998cc725627a832a.jpg","https://static.tildacdn.com/stor6365-6464-4263-b066-653333643438/eba899d7b96c0e9f077467bb4076cf56.jpg","https://static.tildacdn.com/stor3064-6631-4061-b231-393166393931/2e792a2bd15012a2c134a17213d64706.jpg","https://static.tildacdn.com/stor3936-6435-4438-a564-643362376266/c0d42f2444a396f1b836f4f5e2d2a2b8.jpg","https://static.tildacdn.com/stor6635-6661-4361-b031-303465633437/9c824de9c9ad08c435f64d62876ca8f2.jpg","https://static.tildacdn.com/stor6530-6533-4235-b631-613831393533/2651153cdc6a396acfe9c7c1f1ffa6e3.jpg","https://static.tildacdn.com/stor3566-6437-4361-b137-343366323736/8f1f58eb858f669ca4acea85ad8bbbce.jpg","https://static.tildacdn.com/stor3363-3539-4532-b731-343434323163/3d114d5bd96795183d342a37ef5bacb7.jpg","https://static.tildacdn.com/stor3234-6630-4664-b230-616165303866/a350de68077dc437a4a7c0d1f07db97b.jpg","https://static.tildacdn.com/stor6566-6236-4038-b038-326331333761/acfb6d855c780c92c4cd6e667229a9d7.jpg","https://static.tildacdn.com/stor3662-3963-4031-b065-646235373965/f8f3bd5b10fe9142e453766305684126.jpg","https://static.tildacdn.com/stor3461-3363-4063-a530-336136633338/951f5c4bcaa35b878fd8f0d4cdf5812a.jpg","https://static.tildacdn.com/stor6335-3638-4432-b230-393163396334/d08deb92d2102c1e81002f0ff5b1cd18.jpg","https://static.tildacdn.com/stor3163-3035-4466-b435-303832663162/2a85461ba4c7132af333c481f6974334.jpg","https://static.tildacdn.com/stor3132-6233-4466-b164-376530633465/a5159b1de5f6bd3d2dde7609c1853f82.jpg","https://static.tildacdn.com/stor3165-6138-4663-b433-393039653034/4ad56410ecf6a163824061d08cb297da.jpg","https://static.tildacdn.com/stor6437-6361-4833-a537-623832376136/29ff2e5084a5f816954ea5621004b0c0.jpg","https://static.tildacdn.com/stor3765-3765-4663-a333-623738326235/02eaafb2bae5cef3edaa761aa7cea142.jpg","https://static.tildacdn.com/stor6365-3039-4931-a333-623061333865/ceb789e801d358ff78f2a4800f79f78c.jpg","https://static.tildacdn.com/stor6262-6636-4563-b332-383166313034/73a699e67040500f90edd197152cb0b9.jpg","https://static.tildacdn.com/stor3366-3663-4664-a136-633432326530/8a056937be29f8f531767a565fc2fb45.jpg","https://static.tildacdn.com/stor3566-3862-4334-b465-376164633737/7ad059a9bf198827dfd142008f81bb28.jpg","https://static.tildacdn.com/stor3532-6333-4432-b037-373031393364/3f233fc73a9932953876810c2530779b.jpg","https://static.tildacdn.com/stor6461-6235-4134-a630-393261313034/f00b5038885f425bf4463b0f070416b2.jpg","https://static.tildacdn.com/stor3963-3634-4262-b363-393536623037/7f3160b562bdb81243fcca8b07ea73ca.jpg","https://static.tildacdn.com/stor6134-6534-4032-b863-326334303439/b6299036af0105e24b66b92fc149fceb.jpg","https://static.tildacdn.com/stor6330-3331-4534-a431-616637323265/cc72f08366e2b3bb3b2cec181fcfb6b2.jpg","https://static.tildacdn.com/stor3864-6138-4939-a263-323633343831/e41074621630fb64c6551fde54a0491a.jpg","https://static.tildacdn.com/stor3530-3337-4465-a331-393235636139/6bed2df48d65e7dfdf673cc6ab1d66f0.jpg","https://static.tildacdn.com/stor3132-6432-4637-b930-353763333565/0e6665f2fddcdc6a2538ff0e4d6714e3.jpg","https://static.tildacdn.com/stor3063-6361-4562-b864-386439643866/5a0a9e8680146e4af16b0b3a71e5e344.jpg","https://static.tildacdn.com/stor3864-3766-4132-a362-336565363634/331d7a3cb423efad3851318ebc243503.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3661-6362-4365-a166-396432366336/b3461ed10ad1d21ba6f327e14f87561a.jpg","https://static.tildacdn.com/stor6136-3763-4363-b632-613035666139/b89d5f88da96f52526ccba83cfb1e631.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3764-3435-4336-a638-386530643039/b66c7fb8fba795f74358798f008ff284.jpg","https://static.tildacdn.com/stor3530-3337-4465-a331-393235636139/6bed2df48d65e7dfdf673cc6ab1d66f0.jpg","https://static.tildacdn.com/stor3535-3432-4638-b763-636162616233/f392e1f738f4ec51be28ac85ff7f34d1.jpg","https://static.tildacdn.com/stor6263-6138-4433-a639-386465356138/915334ed7ec0a973fc528d5b1bcddeb9.jpg","https://static.tildacdn.com/stor6161-3666-4430-b437-303230613863/b6c27133c86b74e669c4d8d14cddaa98.jpg","https://static.tildacdn.com/stor6433-6530-4938-a264-366133396635/26c04c584ac3ce62893f86e3f21ef6a4.jpg","https://static.tildacdn.com/stor3438-3336-4038-b563-636464663765/26624b9790a69468292e752308984af1.jpg","https://static.tildacdn.com/stor3430-3734-4935-a666-616161393566/92e1260daf92944a35c6ce5a64ca38f3.jpg","https://static.tildacdn.com/stor3937-3731-4331-a333-333266353435/018e8f56c6b0053168f0d0cf73872966.jpg","https://static.tildacdn.com/stor3939-3434-4061-a231-636661616561/607dc1f31bc1ca66fb1a611d49407aa5.jpg","https://static.tildacdn.com/stor3836-3436-4239-a536-353332396264/232fcd6e93bf0abc918ef73529cded4b.jpg","https://static.tildacdn.com/stor3065-3632-4533-b333-636566613663/7979f348481e979905836febdf7933a6.jpg","https://static.tildacdn.com/stor3761-3162-4461-b966-323139613763/08d95a9c71b402415f9479b3f9520892.jpg","https://static.tildacdn.com/stor3033-3137-4930-b264-666532373134/69925db7e39c25877ac58869c15037a6.jpg","https://static.tildacdn.com/stor3333-3839-4537-a236-623030313665/4130c8f6bd2b58b41c4edd83eee922a9.jpg","https://static.tildacdn.com/stor6230-3133-4564-b237-646535613637/a2a99a4eeb22cb33b5b5cea2a79a17cb.jpg","https://static.tildacdn.com/stor3137-6538-4132-b739-653932333233/15a1c8db984c5e68f357b2ee12a18c2a.jpg","https://static.tildacdn.com/stor3031-6132-4334-b330-633933383437/7e711a925bef78f54c6417cfed424f7b.jpg","https://static.tildacdn.com/stor6565-3862-4536-a531-396336343530/94e933aa7cb2ca8cc2f9a6975508c69b.jpg","https://static.tildacdn.com/stor3130-6665-4166-a562-346464663365/1ace83e57a93e4bd15c4117e6ebd10f4.jpg","https://static.tildacdn.com/stor6666-6336-4235-b437-663731323432/f733be8a64d1186ac1153c3eba271d59.jpg","https://static.tildacdn.com/stor6561-6664-4163-a232-323236383637/1b4aa616a89592851de475a7633530b2.jpg","https://static.tildacdn.com/stor3364-3366-4961-a230-393136633735/bd16ccb4240c13b0408fcd3cdb263c7f.jpg","https://static.tildacdn.com/stor3133-3966-4466-a633-353062663934/19cfdd0803e1252edb0a229fe8cc10e1.jpg","https://static.tildacdn.com/stor3862-3737-4631-a637-656265333935/be0ca89c31666c8d357814c69f0c1527.jpg","https://static.tildacdn.com/stor3730-3136-4137-b861-393762656436/9b8faf6a48cff9bc8b16cef36396d144.jpg","https://static.tildacdn.com/stor6137-3965-4334-a432-316165313635/7e2aebaffa009dfc1b7a580ceac8a640.jpg","https://static.tildacdn.com/stor3937-3432-4537-a233-646339376631/d0ca8846eba6810423c5421e7060c567.jpg","https://static.tildacdn.com/stor3437-6365-4139-b134-326232643133/8d0a33ea4f3ace73aa73679aa8ac3b4d.jpg","https://static.tildacdn.com/stor6334-3463-4166-b330-643438393235/49f88576b7fb7573735b20dc4161de96.jpg","https://static.tildacdn.com/stor3139-3866-4633-a234-663139643733/5c7060058669060670ed95fb719078c3.jpg"];
const pad=n=>String(Math.max(1,Math.min(113,Number(n)||0))).padStart(3,'0');
const indexFrom=value=>{
const s=String(value||'');
let m=s.match(/current-(\d{3})\.jpg/i);if(m)return Number(m[1]);
m=s.match(/catalog-20260902-(\d{3})/i);if(m)return Number(m[1]);
const clean=s.split(/[?#]/)[0],remoteIndex=SOURCE_URLS.findIndex(url=>url===clean);
return remoteIndex>=0?remoteIndex+1:0;
};
function indexFor(itemOrPath){
if(itemOrPath&&typeof itemOrPath==='object'){
const byPhoto=indexFrom(itemOrPath.photo);if(byPhoto)return byPhoto;
const byId=indexFrom(itemOrPath.id);if(byId)return byId;
const byOrder=Number(itemOrPath.catalogOrder||0);if(byOrder>=1&&byOrder<=113)return byOrder;
}
return indexFrom(itemOrPath);
}
function candidates(itemOrPath){
const idx=indexFor(itemOrPath);
if(!idx)return [String(itemOrPath&&typeof itemOrPath==='object'?itemOrPath.photo||'':itemOrPath||'')].filter(Boolean);
const local=`catalog/current-${pad(idx)}.jpg`;
const api=(location.protocol==='http:'||location.protocol==='https:')?`/api/catalog-photo?i=${idx}`:'';
const remote=SOURCE_URLS[idx-1]||'';
const requested=String(itemOrPath&&typeof itemOrPath==='object'?itemOrPath.photo||'':itemOrPath||'');
return [...new Set([requested,local,api,remote,'sun-logo.png'].filter(Boolean))];
}
function fallbackImage(img){
if(!(img instanceof HTMLImageElement))return;
let idx=Number(img.dataset.sunCatalogPhotoIndex||0)||indexFor(img.getAttribute('src')||img.src);
if(!idx)return;
img.dataset.sunCatalogPhotoIndex=String(idx);
const list=candidates(`catalog/current-${pad(idx)}.jpg`);
let step=Number(img.dataset.sunCatalogPhotoStep||0);
const currentAbs=(()=>{try{return new URL(img.getAttribute('src')||img.src,document.baseURI).href}catch(_){return String(img.src||'')}})();
while(step<list.length){
const next=list[step++];
let nextAbs=next;try{nextAbs=new URL(next,document.baseURI).href}catch(_){}
if(nextAbs===currentAbs)continue;
img.dataset.sunCatalogPhotoStep=String(step);
img.src=next;
return;
}
img.removeAttribute('data-sun-catalog-photo-index');
img.removeAttribute('data-sun-catalog-photo-step');
if(!/sun-logo\.png(?:$|[?#])/i.test(String(img.getAttribute('src')||'')))img.src='sun-logo.png';
}
document.addEventListener('error',event=>{const img=event.target;if(img instanceof HTMLImageElement)fallbackImage(img)},true);
function repairExisting(){document.querySelectorAll('img').forEach(img=>{if(indexFor(img.getAttribute('src')||'')&&img.complete&&img.naturalWidth===0)fallbackImage(img)})}
setTimeout(repairExisting,60);setTimeout(repairExisting,650);
window.SunCatalogPhoto={sourceUrls:SOURCE_URLS,indexFor,candidates,fallbackImage,repairExisting};
})();
/* ===== MODULE: settings-tabs-v21.js ===== */
/* Sun Catering v17.5.21: grouped Settings tabs without rewriting existing settings cards. */
(()=>{
'use strict';
if(window.__sunSettingsTabsV21)return;
window.__sunSettingsTabsV21=true;
const $=id=>document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const TAB_KEY='sunSettingsActiveTabV1';
const TABS=[
{id:'account',label:'Аккаунт',hint:'Профиль, компания, тариф, пользователи и история изменений.'},
{id:'appearance',label:'Оформление',hint:'Цвета интерфейса, боковой панели и готовые цветовые варианты.'},
{id:'offer',label:'Предложение',hint:'Шаблон и содержимое предложения клиенту.'},
{id:'orders',label:'Заказы',hint:'Статусы заказов и цвета состояния оплаты.'},
{id:'references',label:'Справочники',hint:'Типы мероприятий, источники клиентов и вкладки каталога.'},
{id:'documents',label:'Документы',hint:'Реквизиты чека, QR-код и бланк заказа.'}
];
const VALID=new Set(TABS.map(x=>x.id));
let active=(()=>{try{const v=localStorage.getItem(TAB_KEY);return VALID.has(v)?v:'account'}catch(_){return'account'}})();
let scheduled=0;
const style=document.createElement('style');
style.id='sun-settings-tabs-v21-style';
style.textContent=`
#enterprise-settings .sun-settings-tabs-wrap{margin:0 0 14px;min-width:0}
#enterprise-settings .sun-settings-tabs{display:flex;gap:6px;overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-width:thin;padding:3px 2px 7px;white-space:nowrap}
#enterprise-settings .sun-settings-tab{flex:1 1 0;min-width:118px;border:1px solid #dce2e6;border-radius:10px;background:#fff;color:#53616c;padding:10px 12px;font-size:13px;font-weight:800;cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease}
#enterprise-settings .sun-settings-tab:hover{background:#f7f9fa;border-color:#c8d1d7}
#enterprise-settings .sun-settings-tab.on{background:#15364c;color:#fff;border-color:#15364c;box-shadow:0 5px 14px rgba(21,54,76,.13)}
#enterprise-settings .sun-settings-tab:focus-visible{outline:3px solid rgba(212,154,42,.28);outline-offset:2px}
#enterprise-settings .sun-settings-tab-context{display:flex;gap:7px;align-items:baseline;flex-wrap:wrap;padding:2px 4px 0;color:#6a7680;font-size:12px;line-height:1.4}
#enterprise-settings .sun-settings-tab-context b{color:#15364c;font-size:13px}
#enterprise-settings .enterprise-card{order:var(--sun-settings-order,50)}
#enterprise-settings .enterprise-card[hidden]{display:none!important}
#enterprise-settings.on #sunBlankSettingsCardV3[hidden]{display:none!important}
#enterprise-settings .enterprise-grid{align-items:start}
#enterprise-settings #sunCatalogTabsSettingsCardV21 .sun-settings-catalog-summary{display:flex;gap:7px;flex-wrap:wrap;margin:10px 0 2px}
#enterprise-settings #sunCatalogTabsSettingsCardV21 .sun-settings-catalog-chip{display:inline-flex;align-items:center;border-radius:999px;background:#f2f5f6;color:#52616c;padding:5px 8px;font-size:11px;font-weight:800}
@media(max-width:900px){
#enterprise-settings .sun-settings-tabs{margin-right:-4px;padding-right:16px}
#enterprise-settings .sun-settings-tab{flex:0 0 auto;min-width:126px}
#enterprise-settings .sun-settings-tab-context{padding-right:8px}
}
@media(max-width:520px){
#enterprise-settings .sun-settings-tabs-wrap{margin-bottom:11px}
#enterprise-settings .sun-settings-tab{min-width:116px;padding:9px 11px;font-size:12px}
#enterprise-settings .sun-settings-tab-context span{display:block;width:100%}
}
`;
document.head.appendChild(style);
function heading(card){return String(card?.querySelector(':scope > h2')?.textContent||card?.querySelector('h2')?.textContent||'').trim()}
function classify(card){
const id=String(card?.id||''),h=heading(card);
if(id==='sunCloudV2Card'||id==='sunSaaSSettingsCardV16'||/^(Профиль и аккаунт|Облачная синхронизация|Тариф и подписка|Пользователи и роли|История изменений)$/.test(h)){
const order=id==='sunCloudV2Card'||/^(Профиль и аккаунт|Облачная синхронизация)$/.test(h)?10:id==='sunSaaSSettingsCardV16'||h==='Тариф и подписка'?20:h==='Пользователи и роли'?30:90;
return['account',order];
}
if(id==='sunBrandThemeSettingsCard'||/^(Цвета интерфейса|Настройки интерфейса|Оформление интерфейса)$/.test(h))return['appearance',10];
if(id==='sunOfferTemplateSettingsCard'||h==='Шаблон предложения клиенту')return['offer',10];
if(id==='sunClientOfferSettingsCard'||h==='Настройки предложения клиенту')return['offer',20];
if(id==='sunOrderPaymentColorsCard'||h==='Цвета оплаты заказов')return['orders',10];
if(id==='sunOrderStatusFeatureCard'||h==='Статусы заказов')return['orders',20];
if(id==='sunEventTypesSettingsCard'||h==='Типы мероприятий')return['references',10];
if(id==='sunLeadSourcesSettingsCard'||h==='Источники клиентов')return['references',20];
if(id==='sunCatalogTabsSettingsCardV21'||h==='Вкладки каталога')return['references',30];
if(id==='sunReceiptSettingsCard'||h==='Настройки реквизитов товарного чека')return['documents',10];
if(id==='sunDefaultQrCard'||h==='QR-код для бланка заказа')return['documents',20];
if(id==='sunBlankSettingsCardV3'||h==='Настройки бланка')return['documents',30];
// Any future/unrecognised settings remain reachable instead of disappearing.
return['account',60];
}
function ensureCatalogTabsCard(){
const view=$('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');
if(!grid||typeof window.sunOpenCategoryManager!=='function')return;
let card=$('sunCatalogTabsSettingsCardV21');
if(!card){
card=document.createElement('section');
card.id='sunCatalogTabsSettingsCardV21';
card.className='enterprise-card wide';
grid.appendChild(card);
}
const cats=typeof window.sunCatalogCategories==='function'?window.sunCatalogCategories():[];
const total=Array.isArray(cats)?cats.length:0,visible=Array.isArray(cats)?cats.filter(x=>!x.hidden).length:0;
card.innerHTML=`<h2>Вкладки каталога</h2><p class="hint">Название, порядок, цвет и видимость разделов каталога. Скрытые разделы и их товары не удаляются.</p><div class="sun-settings-catalog-summary"><span class="sun-settings-catalog-chip">Всего: ${total}</span><span class="sun-settings-catalog-chip">Видимых: ${visible}</span></div><div class="actions"><button class="primary" id="sunSettingsCatalogTabsBtnV21" type="button">Настроить вкладки каталога</button></div>`;
$('sunSettingsCatalogTabsBtnV21')?.addEventListener('click',()=>window.sunOpenCategoryManager?.());
}
function ensureBar(){
const view=$('enterprise-settings');if(!view)return null;
let wrap=$('sunSettingsTabsWrapV21');
if(!wrap){
wrap=document.createElement('div');wrap.id='sunSettingsTabsWrapV21';wrap.className='sun-settings-tabs-wrap';
wrap.innerHTML=`<div class="sun-settings-tabs" role="tablist" aria-label="Разделы настроек">${TABS.map(t=>`<button class="sun-settings-tab" type="button" role="tab" data-settings-tab-target="${t.id}" aria-selected="false">${t.label}</button>`).join('')}</div><div class="sun-settings-tab-context" id="sunSettingsTabContextV21"></div>`;
const head=view.querySelector(':scope > .catalog-head');
if(head)head.insertAdjacentElement('afterend',wrap);else view.prepend(wrap);
qa('[data-settings-tab-target]',wrap).forEach(btn=>btn.addEventListener('click',()=>activate(btn.dataset.settingsTabTarget,true)));
}
return wrap;
}
function applyCards(){
const view=$('enterprise-settings'),grid=view?.querySelector('.enterprise-grid');if(!grid)return;
qa(':scope > .enterprise-card',grid).forEach(card=>{
const [tab,order]=classify(card);
card.dataset.settingsTab=tab;
card.style.setProperty('--sun-settings-order',String(order));
card.hidden=tab!==active;
card.setAttribute('aria-hidden',tab===active?'false':'true');
});
}
function renderBar(){
const wrap=ensureBar();if(!wrap)return;
const meta=TABS.find(x=>x.id===active)||TABS[0];
qa('[data-settings-tab-target]',wrap).forEach(btn=>{
const on=btn.dataset.settingsTabTarget===active;
btn.classList.toggle('on',on);btn.setAttribute('aria-selected',on?'true':'false');btn.tabIndex=on?0:-1;
if(on&&wrap.dataset.lastActive!==active)setTimeout(()=>btn.scrollIntoView?.({block:'nearest',inline:'nearest',behavior:'smooth'}),0);
});
wrap.dataset.lastActive=active;
const context=$('sunSettingsTabContextV21');if(context)context.innerHTML=`<b>${meta.label}</b><span>${meta.hint}</span>`;
}
function activate(tab,persist=false){
if(!VALID.has(tab))tab='account';active=tab;
if(persist){try{localStorage.setItem(TAB_KEY,active)}catch(_){}}
renderBar();applyCards();
const view=$('enterprise-settings');if(view?.classList.contains('on')){
try{view.querySelector('.sun-settings-tabs-wrap')?.scrollIntoView({block:'nearest',inline:'nearest'})}catch(_){}
}
window.dispatchEvent(new CustomEvent('sun:settings-tab-changed',{detail:{tab:active}}));
}
function refresh(){
const view=$('enterprise-settings');if(!view)return;
ensureCatalogTabsCard();ensureBar();applyCards();renderBar();
}
function schedule(delay=30){clearTimeout(scheduled);scheduled=setTimeout(refresh,delay)}
function boot(){
const view=$('enterprise-settings');if(!view)return;
const observer=new MutationObserver(()=>schedule(20));observer.observe(view,{childList:true,subtree:true});
document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(b&&String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки'){schedule(0);schedule(90);schedule(240)}},true);
window.addEventListener('sun:catalog-categories-changed',()=>schedule(20));
window.addEventListener('sun:cloud-state-applied',()=>schedule(40));
window.addEventListener('suncloudsync',()=>schedule(40));
refresh();
}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
/* ===== MODULE: developer-console-v22.js ===== */
(()=>{
'use strict';
if(window.SunDeveloperV22)return;
const VERSION='2026.09.06-developer-console-v25-auth-mfa-logo';
const $=id=>String(id||'').startsWith('#')?document.querySelector(id):document.getElementById(id);
const qa=(sel,root=document)=>[...root.querySelectorAll(sel)];
const esc=window.SunSafe.escapeHTML;
const clone=v=>v==null?v:(typeof structuredClone==='function'?structuredClone(v):JSON.parse(JSON.stringify(v)));
const cloud=()=>window.SunCloudV2||null;
const client=()=>cloud()?.getClient?.()||null;
const session=()=>cloud()?.getSession?.()||null;
const toast=(t,k='info',ms=4600)=>{try{return window.SunEnterprise?.toast?.(t,k,ms)}catch(_){};alert(t)};
const fmtDate=v=>{if(!v)return '—';try{return new Date(v).toLocaleString('ru-RU',{dateStyle:'short',timeStyle:'short'})}catch(_){return String(v)}};
const fmtDay=v=>{if(!v)return '—';try{return new Date(v).toLocaleDateString('ru-RU')}catch(_){return String(v)}};
const TAB_KEY='sunDeveloperActiveTabV22';
const TABS=[['overview','Обзор'],['companies','Компании'],['accounts','Аккаунты'],['plans','Тарифы'],['features','Функции'],['system','Система'],['backups','Резервные копии'],['journal','Журнал']];
const FEATURES={orders:'Заказы',calendar:'Календарь',map:'Карта',clients:'Клиенты',catalog_view:'Каталог · просмотр',catalog_edit:'Каталог · редактирование',production:'Производство',shopping:'Закупки',stock:'Склад',routes:'Маршруты',mailings:'Рассылки',money:'Финансы',stats_basic:'Статистика',stats_advanced:'Расширенная статистика',team:'Команда',suppliers:'Поставщики',print:'Печать',settings:'Настройки',branding:'Фирменное оформление',client_offers:'Предложение клиенту',offer_templates:'Шаблоны предложения',backups:'Резервные копии',audit:'Журнал',users_manage:'Пользователи и права'};
const PLANS={basic:'Базовый',professional:'Профессиональный',full:'Полный'};
let platformAdmin=false,checking=false,activeTab=localStorage.getItem(TAB_KEY)||'overview',lastCompanies=[],lastAdminCheckAt=0;
const DEV_ADMIN_TTL_MS=30000,DEV_ADMIN_TIMEOUT_MS=8000;
function installStyle(){if($('sunDeveloperV22Style'))return;const st=document.createElement('style');st.id='sunDeveloperV22Style';st.textContent=`
#sunDeveloperNavV22{font-weight:800}.sun-dev-view{max-width:1460px;margin:0 auto;padding:24px}.sun-dev-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:14px}.sun-dev-head h1{margin:0}.sun-dev-head-actions{display:flex;gap:8px;flex-wrap:wrap}.sun-dev-tabs{display:flex;gap:7px;overflow-x:auto;padding:4px 2px 12px;scrollbar-width:thin;border-bottom:1px solid #e7e8ec;margin-bottom:18px}.sun-dev-tabs button{white-space:nowrap;border:1px solid #d8dbe2;background:#fff;border-radius:999px;padding:9px 14px;font-weight:750;cursor:pointer}.sun-dev-tabs button.on{background:#171815;color:#fff;border-color:#171815}.sun-dev-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.sun-dev-kpi,.sun-dev-card{background:#fff;border:1px solid #e2e4e9;border-radius:16px;padding:16px;box-shadow:0 8px 28px rgba(30,32,36,.04)}.sun-dev-kpi small{color:#6f7480;display:block;margin-bottom:7px}.sun-dev-kpi b{font-size:24px}.sun-dev-card h2,.sun-dev-card h3{margin:0 0 10px}.sun-dev-toolbar{display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-bottom:12px}.sun-dev-toolbar label{min-width:180px}.sun-dev-toolbar input,.sun-dev-toolbar select,.sun-dev-card input,.sun-dev-card select,.sun-dev-card textarea{width:100%;box-sizing:border-box;padding:9px 10px;border:1px solid #d6d9df;border-radius:9px;background:#fff}.sun-dev-table-wrap{overflow:auto;border:1px solid #e2e4e9;border-radius:14px;background:#fff}.sun-dev-table{width:100%;border-collapse:collapse;min-width:900px}.sun-dev-table th,.sun-dev-table td{text-align:left;padding:11px 12px;border-bottom:1px solid #eceef2;vertical-align:top;font-size:13px}.sun-dev-table th{background:#f7f8fa;position:sticky;top:0;z-index:1}.sun-dev-actions{display:flex;gap:6px;flex-wrap:wrap}.sun-dev-pill{display:inline-flex;padding:4px 8px;border-radius:999px;background:#f0f2f5;font-size:11px;font-weight:750;margin:2px}.sun-dev-pill.ok{background:#e8f7ef;color:#14653b}.sun-dev-pill.warn{background:#fff3d9;color:#7d5300}.sun-dev-pill.bad{background:#fde8e8;color:#9d2424}.sun-dev-muted{color:#777d88;font-size:12px}.sun-dev-section-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.sun-dev-modal{position:fixed;inset:0;background:rgba(20,22,26,.52);z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px}.sun-dev-modal-card{background:#fff;border-radius:18px;max-width:620px;width:min(620px,100%);max-height:90vh;overflow:auto;padding:20px;box-shadow:0 24px 80px rgba(0,0,0,.28)}.sun-dev-modal-head{display:flex;justify-content:space-between;gap:12px;align-items:center}.sun-dev-modal-head h2{margin:0}.sun-dev-modal-close{border:0;background:#f1f2f4;border-radius:10px;width:36px;height:36px;font-size:22px;cursor:pointer}.sun-dev-mfa-qr{max-width:220px;width:100%;display:block;margin:12px auto;border:10px solid #fff}.sun-dev-support-banner{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:99995;background:#171815;color:#fff;border-radius:14px;padding:10px 12px;box-shadow:0 12px 36px rgba(0,0,0,.28);display:flex;align-items:center;gap:10px;max-width:calc(100vw - 30px)}.sun-dev-support-banner b{white-space:nowrap}.sun-dev-support-banner button{background:#fff;color:#171815;border:0;border-radius:9px;padding:7px 10px;font-weight:800;cursor:pointer}.sun-dev-danger{border:1px solid #efc8c8!important;color:#9f2424!important;background:#fff!important}.sun-dev-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;background:#f5f6f8;border-radius:8px;padding:7px;overflow-wrap:anywhere}.sun-dev-empty{padding:26px;text-align:center;color:#747a84}.sun-dev-check{display:flex;align-items:center;gap:8px}.sun-dev-check input{width:18px!important;height:18px}.sun-dev-pre{white-space:pre-wrap;word-break:break-word;font:12px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;background:#f7f8fa;padding:12px;border-radius:10px;max-height:340px;overflow:auto}
@media(max-width:950px){.sun-dev-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.sun-dev-section-grid{grid-template-columns:1fr}.sun-dev-view{padding:15px}.sun-dev-head{flex-direction:column}.sun-dev-head-actions{width:100%}}
@media(max-width:540px){.sun-dev-grid{grid-template-columns:1fr}.sun-dev-support-banner{left:15px;right:15px;transform:none;flex-wrap:wrap}.sun-dev-support-banner b{white-space:normal}}
`;document.head.appendChild(st)}
async function rpc(name,args={}){const c=client();if(!c)throw new Error('Supabase не подключён.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data}
async function log(action,workspace=null,user=null,details={}){try{await rpc('sun_dev_log_event',{p_action:action,p_workspace:workspace,p_user:user,p_details:details})}catch(_){}}
async function checkPlatformAdmin(force=false){
if(checking)return platformAdmin;const now=Date.now();if(!force&&lastAdminCheckAt&&now-lastAdminCheckAt<DEV_ADMIN_TTL_MS)return platformAdmin;
const c=client(),ss=session();if(!c||!ss?.user){platformAdmin=false;lastAdminCheckAt=now;syncChrome();return false}checking=true;let timer=0;
try{const r=await Promise.race([Promise.resolve(c.rpc('sun_is_platform_admin')),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer access timeout')),DEV_ADMIN_TIMEOUT_MS)})]);platformAdmin=!r.error&&r.data===true;}
catch(_){platformAdmin=false}finally{clearTimeout(timer);lastAdminCheckAt=Date.now();checking=false;syncChrome()}return platformAdmin
}
function isPlatformAdmin(){return platformAdmin===true}
function ensureNav(){installStyle();const nav=document.querySelector('header nav');if(!nav||!platformAdmin)return null;$('sunSaaSAdminNavV16')?.remove();let b=$('sunDeveloperNavV22');if(!b){b=document.createElement('button');b.id='sunDeveloperNavV22';b.type='button';b.dataset.navLabel='Разработчик';b.textContent='Разработчик';nav.appendChild(b);b.addEventListener('click',()=>open(b))}b.hidden=false;b.style.display='';return b}
function ensureView(){let v=$('sun-developer-console-v22');if(!v){v=document.createElement('section');v.id='sun-developer-console-v22';v.className='view page sun-dev-view';document.body.appendChild(v)}return v}
function showDevView(btn=null){const v=ensureView();qa('.view').forEach(x=>x.classList.remove('on'));v.classList.add('on');qa('header nav button').forEach(x=>x.classList.remove('on'));(btn||$('sunDeveloperNavV22'))?.classList.add('on');window.scrollTo({top:0,behavior:'instant'});return v}
function shell(){const v=ensureView();v.innerHTML=`<div class="sun-dev-head"><div><h1>Кабинет разработчика</h1><p class="hint" style="margin:6px 0 0">Управление платформой, компаниями и техническим состоянием приложения.</p></div><div class="sun-dev-head-actions"><span class="sun-dev-pill ok">Developer</span><span class="sun-dev-pill">${esc(session()?.user?.email||'')}</span><button class="outline" id="sunDevRefresh" type="button">Обновить</button></div></div><div class="sun-dev-tabs">${TABS.map(([id,label])=>`<button type="button" data-dev-tab="${id}" class="${activeTab===id?'on':''}">${label}</button>`).join('')}</div><div id="sunDevBody"><div class="sun-dev-empty">Загрузка…</div></div>`;qa('[data-dev-tab]',v).forEach(b=>b.onclick=()=>activateTab(b.dataset.devTab));$('sunDevRefresh').onclick=()=>activateTab(activeTab,true)}
async function open(btn=null){if(!await checkPlatformAdmin(true))return toast('Этот аккаунт не имеет прав разработчика.','error');if(!await ensureDeveloperMfa())return;ensureNav();showDevView(btn);shell();await activateTab(activeTab,true)}
async function activateTab(tab,force=false){if(!TABS.some(x=>x[0]===tab))tab='overview';activeTab=tab;localStorage.setItem(TAB_KEY,tab);qa('[data-dev-tab]').forEach(b=>b.classList.toggle('on',b.dataset.devTab===tab));const body=$('sunDevBody');if(!body)return;body.innerHTML='<div class="sun-dev-empty">Загрузка…</div>';try{if(tab==='overview')await renderOverview(body);else if(tab==='companies')await renderCompanies(body);else if(tab==='accounts')await renderAccounts(body);else if(tab==='plans')await renderPlans(body);else if(tab==='features')await renderFeatures(body);else if(tab==='system')await renderSystem(body);else if(tab==='backups')await renderBackups(body);else if(tab==='journal')await renderJournal(body)}catch(e){body.innerHTML=`<div class="sun-dev-card"><h3>Не удалось загрузить раздел</h3><p>${esc(e?.message||e)}</p></div>`}}
function modal(title,html){$('sunDevModalV22')?.remove();const m=document.createElement('div');m.className='sun-dev-modal';m.id='sunDevModalV22';m.innerHTML=`<div class="sun-dev-modal-card"><div class="sun-dev-modal-head"><h2>${esc(title)}</h2><button class="sun-dev-modal-close" type="button" data-close>×</button></div><div id="sunDevModalBody" style="margin-top:14px">${html}</div></div>`;document.body.appendChild(m);const close=()=>m.remove();m.addEventListener('click',e=>{if(e.target===m||e.target.closest('[data-close]'))close()});return {m,body:$('#sunDevModalBody'),close}}
function confirmDanger(text){return confirm(text)}
async function ensureDeveloperMfa(){
const c=client();if(!c?.auth?.mfa)throw new Error('Supabase MFA недоступна.');
const readAal=async()=>{const a=await c.auth.mfa.getAuthenticatorAssuranceLevel();if(a.error)throw a.error;return a.data||{}};
const waitAal2=async()=>{for(let i=0;i<12;i++){const a=await readAal();if(a.currentLevel==='aal2')return true;await new Promise(r=>setTimeout(r,180))}return false};
const initial=await readAal();if(initial.currentLevel==='aal2')return true;
const listed=await c.auth.mfa.listFactors();if(listed.error)throw listed.error;
let factor=(listed.data?.totp||[]).find(f=>f.status==='verified');let enrollment=null;
if(!factor){
for(const f of (listed.data?.totp||[]).filter(x=>x.status!=='verified')){
try{const u=await c.auth.mfa.unenroll({factorId:f.id});if(u.error&&!/not found/i.test(String(u.error.message||'')))throw u.error}catch(e){console.warn('[Developer MFA] reset factor',e)}
}
const enr=await c.auth.mfa.enroll({factorType:'totp',friendlyName:'Caterium Developer'});if(enr.error)throw enr.error;factor=enr.data;enrollment=enr.data;
}
const firstSetup=Boolean(enrollment),qr=enrollment?.totp?.qr_code||'',secret=enrollment?.totp?.secret||'';
const ui=modal(firstSetup?'Подключение 2FA разработчика':'Подтверждение разработчика',firstSetup?`<p>Отсканируйте этот QR-код приложением-аутентификатором. Затем введите текущие 6 цифр и нажмите <b>Подтвердить и открыть</b>.</p>${qr?`<img class="sun-dev-mfa-qr" src="${esc(qr)}" alt="QR для 2FA">`:''}${secret?`<p class="sun-dev-muted">Если QR не сканируется, добавьте ключ вручную:</p><div class="sun-dev-code">${esc(secret)}</div>`:''}<label style="display:block;margin-top:14px">6-значный код<input id="sunDevMfaCode" inputmode="numeric" autocomplete="one-time-code" maxlength="6" placeholder="000000"></label><div class="actions"><button class="primary" id="sunDevMfaVerify" type="button">Подтвердить и открыть</button></div><div id="sunDevMfaError" class="sun-dev-muted" style="margin-top:8px"></div>`:`<p>Введите текущий 6-значный код из приложения-аутентификатора.</p><label>Код<input id="sunDevMfaCode" inputmode="numeric" autocomplete="one-time-code" maxlength="6" placeholder="000000"></label><div class="actions"><button class="primary" id="sunDevMfaVerify" type="button">Подтвердить и открыть</button></div><div id="sunDevMfaError" class="sun-dev-muted" style="margin-top:8px"></div>`);
return await new Promise(resolve=>{
let settled=false,busy=false;
const finish=v=>{if(settled)return;settled=true;try{ui.close()}catch(_){}resolve(v)};
const showError=(e,notify=true)=>{const msg=e?.message||String(e||'Не удалось подтвердить код.');const box=$('sunDevMfaError');if(box)box.textContent=msg;if(notify)toast(msg,'error',6500)};
const verify=async()=>{
if(busy)return;
const code=String(document.getElementById('sunDevMfaCode')?.value||'').replace(/\D/g,'');
if(code.length!==6){showError(new Error('Введите ровно 6 цифр из приложения-аутентификатора.'),false);return}
busy=true;const btn=$('sunDevMfaVerify');if(btn){btn.disabled=true;btn.textContent='Проверяю…'};const box=$('sunDevMfaError');if(box)box.textContent='Проверяю код…';
try{
let vr;
if(typeof c.auth.mfa.challengeAndVerify==='function') vr=await c.auth.mfa.challengeAndVerify({factorId:factor.id,code});
else {const ch=await c.auth.mfa.challenge({factorId:factor.id});if(ch.error)throw ch.error;vr=await c.auth.mfa.verify({factorId:factor.id,challengeId:ch.data.id,code})}
if(vr?.error)throw vr.error;
if(!await waitAal2()){
try{await c.auth.refreshSession()}catch(_){}
if(!await waitAal2())throw new Error('Код принят, но сессия разработчика не получила AAL2. Выйдите из аккаунта и войдите снова, затем повторите код.');
}
toast('2FA подтверждена. Открываю кабинет разработчика…','success',2800);
finish(true);
}catch(e){busy=false;if(btn){btn.disabled=false;btn.textContent='Подтвердить и открыть'};showError(e)}
};
const b=document.getElementById('sunDevMfaVerify'),inp=document.getElementById('sunDevMfaCode');if(b)b.addEventListener('click',verify);if(inp)inp.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();verify()}});
ui.m.addEventListener('click',e=>{if((e.target===ui.m||e.target.closest('[data-close]'))&&!busy)finish(false)});
setTimeout(()=>inp?.focus(),60);
})
}
async function renderOverview(body){const d=await rpc('sun_dev_dashboard');body.innerHTML=`<div class="sun-dev-grid"><div class="sun-dev-kpi"><small>Компании</small><b>${Number(d.companies||0)}</b></div><div class="sun-dev-kpi"><small>Аккаунты</small><b>${Number(d.accounts||0)}</b></div><div class="sun-dev-kpi"><small>Активные подписки</small><b>${Number(d.active_subscriptions||0)}</b></div><div class="sun-dev-kpi"><small>Ограниченные компании</small><b>${Number(d.restricted_companies||0)}</b></div><div class="sun-dev-kpi"><small>Ошибки за 24 ч.</small><b>${Number(d.errors_24h||0)}</b></div><div class="sun-dev-kpi"><small>Копии за 24 ч.</small><b>${Number(d.backups_24h||0)}</b></div><div class="sun-dev-kpi"><small>Участники компаний</small><b>${Number(d.memberships||0)}</b></div><div class="sun-dev-kpi"><small>Размер БД</small><b style="font-size:19px">${esc(d.database_size||'—')}</b></div></div><div class="sun-dev-section-grid" style="margin-top:12px"><div class="sun-dev-card"><h3>Платформа</h3><p><b>Версия приложения:</b> <span id="sunDevReleaseVersion">17.6.0</span></p><p><b>PostgreSQL:</b> ${esc(d.postgres_version||'—')}</p><p><b>Последнее изменение рабочей базы:</b> ${fmtDate(d.last_state_at)}</p></div><div class="sun-dev-card"><h3>Безопасность</h3><p><span class="sun-dev-pill ok">MFA AAL2</span> Кабинет разработчика защищён двухфакторной авторизацией.</p><p class="sun-dev-muted">Права разработчика проверяются сервером через sun_platform_admins.</p></div></div>`;try{const r=await fetch('release-manifest.json',{cache:'no-store'});if(r.ok){const m=await r.json();$('#sunDevReleaseVersion').textContent=m.version||'17.6.0'}}catch(_){} }
async function getCompanies(){let rows=[];try{rows=await rpc('sun_dev_list_companies')}catch(_){rows=await rpc('sun_dev_list_companies')}lastCompanies=Array.isArray(rows)?rows:[];return lastCompanies}
function companyOptions(rows,selected=''){return `<option value="">Выберите компанию</option>${rows.map(r=>`<option value="${esc(r.workspace_id)}" ${r.workspace_id===selected?'selected':''}>${esc(r.workspace_name||r.name||r.workspace_id)}</option>`).join('')}`}
async function renderCompanies(body){const rows=await getCompanies();body.innerHTML=`<div class="sun-dev-toolbar"><label style="flex:1">Поиск<input id="sunDevCompanySearch" placeholder="Название или email владельца"></label><button class="primary" id="sunDevCreateCompany" type="button">Создать компанию</button></div><div class="sun-dev-table-wrap"><table class="sun-dev-table"><thead><tr><th>Компания</th><th>Владелец</th><th>Тариф</th><th>Доступ</th><th>Сотрудники</th><th>Создана</th><th>Действия</th></tr></thead><tbody id="sunDevCompaniesTbody"></tbody></table></div>`;const draw=()=>{const q=String($('#sunDevCompanySearch').value||'').trim().toLowerCase();const filtered=rows.filter(r=>`${r.workspace_name||''} ${r.owner_email||''}`.toLowerCase().includes(q));$('#sunDevCompaniesTbody').innerHTML=filtered.map(r=>`<tr data-ws="${esc(r.workspace_id)}"><td><b>${esc(r.workspace_name)}</b><div class="sun-dev-muted">${esc(r.workspace_id)}</div></td><td>${esc(r.owner_email||r.pending_owner_email||'—')}${r.pending_owner_email?'<div class="sun-dev-pill warn">ожидает регистрации</div>':''}</td><td>${esc(PLANS[r.plan_id]||r.plan_name||r.plan_id||'—')}<div class="sun-dev-muted">до ${fmtDay(r.trial_ends_at||r.current_period_end)}</div></td><td><span class="sun-dev-pill ${r.access_mode==='full'?'ok':r.access_mode==='blocked'?'bad':'warn'}">${esc(r.access_mode||r.status||'—')}</span></td><td>${Number(r.member_count||0)} / ${r.max_members==null?'∞':r.max_members}</td><td>${fmtDay(r.created_at)}</td><td><div class="sun-dev-actions"><button class="outline" data-support>Открыть как поддержка</button><button class="outline" data-diag>Диагностика</button><button class="outline" data-plan>Тариф</button><button class="outline sun-dev-danger" data-block>${r.access_mode==='blocked'?'Разблокировать':'Заблокировать'}</button></div></td></tr>`).join('')||'<tr><td colspan="7" class="sun-dev-empty">Компаний не найдено.</td></tr>';bindCompanyRows()};draw();$('#sunDevCompanySearch').oninput=draw;$('#sunDevCreateCompany').onclick=openCreateCompany}
function bindCompanyRows(){qa('#sunDevCompaniesTbody tr[data-ws]').forEach(tr=>{const ws=tr.dataset.ws,row=lastCompanies.find(x=>String(x.workspace_id)===ws);tr.querySelector('[data-support]').onclick=()=>enterSupport(row);tr.querySelector('[data-diag]').onclick=()=>showDiagnostics(row);tr.querySelector('[data-plan]').onclick=()=>showCompanyPlan(row);tr.querySelector('[data-block]').onclick=async()=>{const blocked=row.access_mode==='blocked';if(!blocked&&!confirmDanger(`Заблокировать компанию «${row.workspace_name}»? Данные не удалятся.`))return;await rpc('sun_dev_set_subscription',{p_workspace:ws,p_plan:row.plan_id||'full',p_days:blocked?30:0,p_status:blocked?'active':'expired'});await log(blocked?'company.unblock':'company.block',ws,null,{company:row.workspace_name});toast(blocked?'Компания разблокирована.':'Компания заблокирована.','success');activateTab('companies',true)}})}
function openCreateCompany(){const ui=modal('Создать компанию',`<label>Название<input id="sunDevNewCompanyName" value="Новая компания"></label><label style="display:block;margin-top:10px">Email владельца<input id="sunDevNewOwnerEmail" type="email" placeholder="owner@example.com"></label><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px"><label>Тариф<select id="sunDevNewPlan"><option value="basic">Базовый</option><option value="professional">Профессиональный</option><option value="full" selected>Полный</option></select></label><label>Срок, дней<input id="sunDevNewDays" type="number" min="1" max="3650" value="30"></label></div><p class="hint">Компания получит текущий стартовый каталог (${Number(window.SUN_OFFICIAL_CATALOG?.length||0)} позиций), а заказы и клиенты будут пустыми.</p><div class="actions"><button class="primary" id="sunDevNewCreate" type="button">Создать</button></div><div id="sunDevNewError" class="sun-dev-muted"></div>`);$('#sunDevNewCreate').onclick=async()=>{const name=String($('#sunDevNewCompanyName').value||'').trim(),email=String($('#sunDevNewOwnerEmail').value||'').trim(),plan=$('#sunDevNewPlan').value,days=Number($('#sunDevNewDays').value||30);if(!name)return $('#sunDevNewError').textContent='Введите название.';const btn=$('#sunDevNewCreate');btn.disabled=true;$('#sunDevNewError').textContent='Создаю…';try{const data=await rpc('sun_dev_create_company',{p_name:name,p_owner_email:email||null,p_plan:plan,p_days:days,p_boxes:Array.isArray(window.SUN_OFFICIAL_CATALOG)?window.SUN_OFFICIAL_CATALOG:[],p_catalog_version:String(window.SUN_OFFICIAL_CATALOG_VERSION||'')});ui.body.innerHTML=`<p><b>Компания создана:</b> ${esc(data.workspace_name||name)}</p><p>Каталог: ${Number(data.catalog_count||0)} позиций.</p>${data.owner_status==='invite'?`<p>Владелец ещё не зарегистрирован. Код приглашения сохранён на сервере.</p>`:''}<div class="actions"><button class="primary" type="button" data-close>Готово</button></div>`;setTimeout(()=>activateTab('companies',true),400)}catch(e){btn.disabled=false;$('#sunDevNewError').textContent=e?.message||String(e)}}}
function showCompanyPlan(row){const ui=modal(`Тариф · ${row.workspace_name}`,`<label>Тариф<select id="sunDevPlanSelect"><option value="basic" ${row.plan_id==='basic'?'selected':''}>Базовый</option><option value="professional" ${row.plan_id==='professional'?'selected':''}>Профессиональный</option><option value="full" ${row.plan_id==='full'?'selected':''}>Полный</option></select></label><label style="display:block;margin-top:10px">Продлить на, дней<select id="sunDevPlanDays"><option value="30">30</option><option value="90">90</option><option value="365">365</option></select></label><div class="actions"><button class="primary" id="sunDevPlanSave">Применить</button></div>`);$('#sunDevPlanSave').onclick=async()=>{await rpc('sun_dev_set_subscription',{p_workspace:row.workspace_id,p_plan:$('#sunDevPlanSelect').value,p_days:Number($('#sunDevPlanDays').value),p_status:'active'});await log('company.subscription.set',row.workspace_id,null,{plan:$('#sunDevPlanSelect').value,days:Number($('#sunDevPlanDays').value)});ui.close();toast('Тариф обновлён.','success');activateTab('companies',true)}}
async function showDiagnostics(row){const d=await rpc('sun_dev_workspace_diagnostics',{p_workspace:row.workspace_id});modal(`Диагностика · ${row.workspace_name}`,`<div class="sun-dev-grid" style="grid-template-columns:repeat(2,minmax(0,1fr))"><div class="sun-dev-kpi"><small>Заказы</small><b>${Number(d.orders||0)}</b></div><div class="sun-dev-kpi"><small>Клиенты</small><b>${Number(d.clients||0)}</b></div><div class="sun-dev-kpi"><small>Каталог</small><b>${Number(d.catalog_items||0)}</b></div><div class="sun-dev-kpi"><small>Ошибки 24 ч.</small><b>${Number(d.errors_24h||0)}</b></div></div><p><b>Revision:</b> ${Number(d.revision||0)}</p><p><b>Обновлено:</b> ${fmtDate(d.state_updated_at)}</p><p><b>Последняя копия:</b> ${fmtDate(d.latest_backup)}</p><p><b>Режим доступа:</b> ${esc(d.access_mode||'—')}</p>`)}
async function enterSupport(row){if(!row)return;await cloud().enterSupportWorkspace(row.workspace_id,row.workspace_name);await log('support.ui_enter',row.workspace_id,null,{company:row.workspace_name});ensureSupportBanner();try{window.render?.();window.renderOrders?.();window.renderStats?.()}catch(_){}const orders=qa('header nav button').find(b=>String(b.dataset.navLabel||b.textContent||'').trim()==='Заказы');if(orders)orders.click();else{qa('.view').forEach(v=>v.classList.remove('on'));$('orders')?.classList.add('on')}toast('Режим поддержки открыт. Изменения не будут сохранены.','info',6500)}
function ensureSupportBanner(){let b=$('sunDevSupportBannerV22');const sm=cloud()?.getSupportMode?.();if(!sm){b?.remove();return}if(!b){b=document.createElement('div');b.id='sunDevSupportBannerV22';b.className='sun-dev-support-banner';document.body.appendChild(b)}b.innerHTML=`<span>Поддержка:</span><b>${esc(sm.name||'Компания')}</b><span class="sun-dev-pill warn">только просмотр</span><button type="button">Выйти из компании</button>`;b.querySelector('button').onclick=async()=>{try{await log('support.exit',sm.workspaceId,null,{company:sm.name});await cloud().exitSupportWorkspace()}catch(e){toast(e?.message||String(e),'error')}}}
async function fetchAccounts(){const rows=await rpc('sun_dev_list_users');const map=new Map();for(const r of rows||[]){const id=String(r.user_id);if(!map.has(id))map.set(id,{id,email:r.email,created_at:r.created_at,last_sign_in_at:r.last_sign_in_at,email_confirmed_at:'',is_platform_admin:r.is_platform_admin===true,is_banned:false,authAdminActions:false,workspaces:[]});if(r.workspace_id)map.get(id).workspaces.push({id:r.workspace_id,name:r.workspace_name,role:r.role,is_active:r.is_active!==false})}return [...map.values()]}
async function renderAccounts(body){const users=await fetchAccounts();body.innerHTML=`<div class="sun-dev-toolbar"><label style="flex:1">Поиск<input id="sunDevUserSearch" placeholder="Email или компания"></label><span class="sun-dev-pill">${users.length} аккаунтов</span></div><div class="sun-dev-table-wrap"><table class="sun-dev-table"><thead><tr><th>Аккаунт</th><th>Компании</th><th>Регистрация</th><th>Последний вход</th><th>Статус</th><th>Действия</th></tr></thead><tbody id="sunDevUsersTbody"></tbody></table></div>`;const draw=()=>{const q=String($('#sunDevUserSearch').value||'').trim().toLowerCase();const arr=users.filter(u=>`${u.email||''} ${(u.workspaces||[]).map(w=>w.name).join(' ')}`.toLowerCase().includes(q));$('#sunDevUsersTbody').innerHTML=arr.map(u=>`<tr data-user="${esc(u.id)}"><td><b>${esc(u.email||'—')}</b>${u.is_platform_admin?'<div><span class="sun-dev-pill ok">Разработчик</span></div>':''}<div class="sun-dev-muted">${esc(u.id)}</div></td><td>${(u.workspaces||[]).length?(u.workspaces||[]).map(w=>`<span class="sun-dev-pill">${esc(w.name)} · ${esc(w.role||'')}</span>`).join(''):'—'}</td><td>${fmtDate(u.created_at)}</td><td>${fmtDate(u.last_sign_in_at)}</td><td>${u.is_banned?'<span class="sun-dev-pill bad">заблокирован</span>':'<span class="sun-dev-pill ok">активен</span>'}${u.email_confirmed_at?'<div class="sun-dev-muted">email подтверждён</div>':''}</td><td>${u.authAdminActions?`<div class="sun-dev-actions"><button class="outline" data-recovery>Восстановление</button>${u.is_platform_admin?'':`<button class="outline ${u.is_banned?'':'sun-dev-danger'}" data-ban>${u.is_banned?'Разблокировать':'Заблокировать'}</button>`}</div>`:'<span class="sun-dev-muted">Просмотр аккаунта доступен. Глобальная блокировка Auth и письмо восстановления требуют защищённой backend-функции.</span>'}</td></tr>`).join('')||'<tr><td colspan="6" class="sun-dev-empty">Ничего не найдено.</td></tr>';qa('#sunDevUsersTbody tr[data-user]').forEach(tr=>{const u=users.find(x=>String(x.id)===tr.dataset.user);tr.querySelector('[data-recovery]')?.addEventListener('click',()=>authAdminAction('send_recovery',u));tr.querySelector('[data-ban]')?.addEventListener('click',()=>authAdminAction('set_ban',u,{banned:!u.is_banned}))})};draw();$('#sunDevUserSearch').oninput=draw}
async function authAdminAction(action,u,extra={}){if(action==='set_ban'&&extra.banned&&!confirmDanger(`Заблокировать вход для ${u.email}?`))return;const r=await client().functions.invoke('caterium-platform-auth-admin',{body:{action,user_id:u.id,...extra}});if(r.error)throw r.error;if(r.data?.error)throw new Error(r.data.error);toast(action==='send_recovery'?'Письмо восстановления отправлено.':extra.banned?'Аккаунт заблокирован.':'Аккаунт разблокирован.','success');activateTab('accounts',true)}
async function renderPlans(body){const c=client();const [plansR,featuresR]=await Promise.all([c.from('sun_plans').select('id,name,description,max_members,is_active,sort_order').order('sort_order'),c.from('sun_plan_features').select('plan_id,feature_key,enabled')]);if(plansR.error)throw plansR.error;if(featuresR.error)throw featuresR.error;const plans=plansR.data||[],features=featuresR.data||[];body.innerHTML=`<div class="sun-dev-section-grid">${plans.map(p=>`<div class="sun-dev-card" data-plan-card="${esc(p.id)}"><h2>${esc(p.name)}</h2><p class="sun-dev-muted">${esc(p.description||'')}</p><label>Лимит сотрудников<input type="number" data-max min="1" placeholder="без лимита" value="${p.max_members??''}"></label><div class="actions"><button class="outline" data-save-max>Сохранить лимит</button></div><h3 style="margin-top:16px">Функции</h3><div>${Object.keys(FEATURES).map(f=>{const row=features.find(x=>x.plan_id===p.id&&x.feature_key===f);return `<label class="sun-dev-check"><input type="checkbox" data-feature="${f}" ${row?.enabled?'checked':''}><span>${esc(FEATURES[f])}</span></label>`}).join('')}</div></div>`).join('')}</div>`;qa('[data-plan-card]').forEach(card=>{const plan=card.dataset.planCard;card.querySelector('[data-save-max]').onclick=async()=>{const raw=card.querySelector('[data-max]').value;await rpc('sun_dev_set_plan_max_members',{p_plan:plan,p_max_members:raw===''?null:Number(raw)});toast('Лимит сохранён.','success')};qa('[data-feature]',card).forEach(ch=>ch.onchange=async()=>{ch.disabled=true;try{await rpc('sun_dev_set_plan_feature',{p_plan:plan,p_feature:ch.dataset.feature,p_enabled:ch.checked});toast('Матрица тарифа обновлена.','success')}catch(e){ch.checked=!ch.checked;toast(e?.message||String(e),'error')}finally{ch.disabled=false}})})}
async function renderFeatures(body){const rows=lastCompanies.length?lastCompanies:await getCompanies();body.innerHTML=`<div class="sun-dev-toolbar"><label>Компания<select id="sunDevFeatureCompany">${companyOptions(rows)}</select></label><button class="outline" id="sunDevFeatureLoad" type="button">Загрузить</button></div><div id="sunDevFeatureBody" class="sun-dev-empty">Выберите компанию.</div>`;const load=async()=>{const ws=$('#sunDevFeatureCompany').value;if(!ws)return;const data=await rpc('sun_dev_list_workspace_features',{p_workspace:ws});const root=$('sunDevFeatureBody');root.className='sun-dev-table-wrap';root.innerHTML=`<table class="sun-dev-table"><thead><tr><th>Функция</th><th>По тарифу</th><th>Индивидуально</th><th>Итог</th><th>Действия</th></tr></thead><tbody>${(data||[]).map(f=>`<tr data-feature="${esc(f.feature_key)}"><td><b>${esc(FEATURES[f.feature_key]||f.feature_key)}</b></td><td>${f.plan_enabled?'✓':'—'}</td><td>${f.override_enabled==null?'нет':f.override_enabled?'включено':'выключено'}<div class="sun-dev-muted">${f.override_expires_at?`до ${fmtDay(f.override_expires_at)}`:''}</div></td><td><span class="sun-dev-pill ${f.effective_enabled?'ok':'warn'}">${f.effective_enabled?'доступна':'закрыта'}</span></td><td><div class="sun-dev-actions"><button class="outline" data-enable>Включить 30 дн.</button><button class="outline" data-disable>Выключить 30 дн.</button><button class="outline" data-reset>По тарифу</button></div></td></tr>`).join('')}</tbody></table>`;qa('tr[data-feature]',root).forEach(tr=>{const f=tr.dataset.feature;tr.querySelector('[data-enable]').onclick=()=>setOverride(ws,f,true,30,load);tr.querySelector('[data-disable]').onclick=()=>setOverride(ws,f,false,30,load);tr.querySelector('[data-reset]').onclick=async()=>{await rpc('sun_dev_reset_feature_override',{p_workspace:ws,p_feature:f});toast('Индивидуальное правило сброшено.','success');load()}})};$('#sunDevFeatureLoad').onclick=load;$('#sunDevFeatureCompany').onchange=load}
async function setOverride(ws,f,en,days,refresh){await rpc('sun_dev_set_feature_override',{p_workspace:ws,p_feature:f,p_enabled:en,p_days:days,p_note:'Developer console v17.6.0'});await log('feature.override.set',ws,null,{feature:f,enabled:en,days});toast('Индивидуальная функция обновлена.','success');await refresh()}
async function renderSystem(body){const cfg=cloud()?.getTechnicalConfig?.()||{},d=await rpc('sun_dev_dashboard');let stability=null;try{stability=await window.SunStabilityV17?.diagnostics?.()}catch(_){}const rows=lastCompanies.length?lastCompanies:await getCompanies();body.innerHTML=`<div class="sun-dev-section-grid"><div class="sun-dev-card"><h2>Подключение Supabase</h2><label>Project URL<input id="sunDevSupabaseUrl" value="${esc(cfg.url||'')}"></label><label style="display:block;margin-top:10px">Publishable key<input id="sunDevSupabaseKey" type="password" value="${esc(cfg.key||'')}"></label><label class="sun-dev-check" style="margin-top:12px"><input id="sunDevAutoSync" type="checkbox" ${cfg.autoSync?'checked':''}> Автосинхронизация</label><div class="actions"><button class="outline" id="sunDevReconnect">Переподключить</button></div><p class="sun-dev-muted">Эти параметры скрыты от обычных пользователей.</p></div><div class="sun-dev-card"><h2>Состояние</h2><p><b>База:</b> ${esc(d.database_size||'—')}</p><p><b>PostgreSQL:</b> ${esc(d.postgres_version||'—')}</p><p><b>Последняя синхронизация:</b> ${fmtDate(cfg.lastSync)}</p><p><b>Ошибки 24 ч.:</b> ${Number(d.errors_24h||0)}</p><p><b>Client ID:</b> <span class="sun-dev-code">${esc(cloud()?.getClientId?.()||'')}</span></p></div><div class="sun-dev-card"><h2>Стартовый каталог</h2><p><b>${Number(window.SUN_OFFICIAL_CATALOG?.length||0)}</b> позиций · версия ${esc(window.SUN_OFFICIAL_CATALOG_VERSION||'—')}</p><p class="hint">Новые компании получают этот каталог автоматически. Существующие компании не меняются без явной команды.</p><label>Применить к компании<select id="sunDevCatalogCompany">${companyOptions(rows)}</select></label><div class="actions"><button class="outline sun-dev-danger" id="sunDevForceCatalog">Принудительно заменить каталог</button></div></div><div class="sun-dev-card"><h2>Диагностика клиента</h2><div class="sun-dev-pre">${esc(stability?JSON.stringify(stability,null,2):'Диагностика stability-модуля недоступна.')}</div></div></div>`;$('#sunDevAutoSync').onchange=e=>cloud().setAutoSyncDeveloper?.(e.target.checked);$('#sunDevReconnect').onclick=async()=>{if(!confirmDanger('Переподключить приложение к указанному Supabase? Неверные данные могут временно лишить приложение облака.'))return;await cloud().setTechnicalConnection($('#sunDevSupabaseUrl').value,$('#sunDevSupabaseKey').value);toast('Подключение обновлено.','success')};$('#sunDevForceCatalog').onclick=async()=>{const ws=$('#sunDevCatalogCompany').value;if(!ws)return toast('Выберите компанию.','warn');const row=rows.find(r=>r.workspace_id===ws);if(!confirmDanger(`ЗАМЕНИТЬ каталог компании «${row?.workspace_name||ws}» текущим стартовым каталогом? Собственный каталог компании будет перезаписан.`))return;const n=await rpc('sun_dev_seed_workspace_catalog',{p_workspace:ws,p_boxes:window.SUN_OFFICIAL_CATALOG||[],p_catalog_version:String(window.SUN_OFFICIAL_CATALOG_VERSION||''),p_replace:true});toast(`Каталог заменён: ${n} позиций.`,'success',6500)}}
async function renderBackups(body){const rows=lastCompanies.length?lastCompanies:await getCompanies();body.innerHTML=`<div class="sun-dev-toolbar"><label>Компания<select id="sunDevBackupCompany">${companyOptions(rows)}</select></label><button class="outline" id="sunDevBackupLoad">Загрузить</button><button class="primary" id="sunDevBackupCreate">Создать копию</button></div><div id="sunDevBackupBody" class="sun-dev-empty">Выберите компанию.</div>`;const load=async()=>{const ws=$('#sunDevBackupCompany').value;if(!ws)return;const data=await rpc('sun_v17_list_backups',{p_workspace:ws,p_limit:60});const root=$('sunDevBackupBody');root.className='sun-dev-table-wrap';root.innerHTML=`<table class="sun-dev-table"><thead><tr><th>Дата</th><th>Тип</th><th>Метка</th><th>ID</th><th>Действия</th></tr></thead><tbody>${(data||[]).map(b=>`<tr data-backup="${esc(b.id)}"><td>${fmtDate(b.created_at)}</td><td>${esc(b.kind)}</td><td>${esc(b.label||'—')}</td><td class="sun-dev-muted">${esc(b.id)}</td><td><button class="outline sun-dev-danger" data-restore>Восстановить</button></td></tr>`).join('')||'<tr><td colspan="5" class="sun-dev-empty">Копий нет.</td></tr>'}</tbody></table>`;qa('tr[data-backup]',root).forEach(tr=>tr.querySelector('[data-restore]').onclick=async()=>{if(!confirmDanger('Восстановить эту резервную копию? Перед восстановлением автоматически создастся новая копия текущего состояния.'))return;await rpc('sun_v17_restore_backup',{p_workspace:ws,p_backup:tr.dataset.backup});await log('backup.restore',ws,null,{backup_id:tr.dataset.backup});toast('Резервная копия восстановлена.','success',6500);load()})};$('#sunDevBackupLoad').onclick=load;$('#sunDevBackupCompany').onchange=load;$('#sunDevBackupCreate').onclick=async()=>{const ws=$('#sunDevBackupCompany').value;if(!ws)return toast('Выберите компанию.','warn');const id=await rpc('sun_v17_create_backup',{p_workspace:ws,p_kind:'manual',p_label:'Создано из кабинета разработчика'});await log('backup.create',ws,null,{backup_id:id});toast('Резервная копия создана.','success');load()}}
async function renderJournal(body){const [activity,errors]=await Promise.all([rpc('sun_dev_list_activity',{p_limit:150}),rpc('sun_dev_list_errors',{p_workspace:null,p_limit:100})]);body.innerHTML=`<div class="sun-dev-section-grid"><div class="sun-dev-card" style="overflow:auto"><h2>Действия разработчика</h2><div class="sun-dev-table-wrap"><table class="sun-dev-table"><thead><tr><th>Дата</th><th>Действие</th><th>Компания / аккаунт</th><th>Детали</th></tr></thead><tbody>${(activity||[]).map(a=>`<tr><td>${fmtDate(a.created_at)}</td><td><b>${esc(a.action)}</b><div class="sun-dev-muted">${esc(a.actor_email||'')}</div></td><td>${esc(a.workspace_name||a.target_email||'—')}</td><td class="sun-dev-muted">${esc(JSON.stringify(a.details||{}))}</td></tr>`).join('')||'<tr><td colspan="4">Записей пока нет.</td></tr>'}</tbody></table></div></div><div class="sun-dev-card" style="overflow:auto"><h2>Ошибки приложения</h2><div class="sun-dev-table-wrap"><table class="sun-dev-table"><thead><tr><th>Дата</th><th>Компания</th><th>Версия</th><th>Ошибка</th></tr></thead><tbody>${(errors||[]).map(e=>`<tr><td>${fmtDate(e.created_at)}</td><td>${esc(e.workspace_name||'—')}</td><td>${esc(e.app_version||'—')}</td><td><b>${esc(e.level||'error')}</b><div>${esc(e.message||'')}</div></td></tr>`).join('')||'<tr><td colspan="4">Ошибок нет.</td></tr>'}</tbody></table></div></div></div>`}
function enhanceDeveloperGate(){const gate=$('sunCloudAuthGateV3'),ss=session(),ws=cloud()?.getWorkspace?.(),st=cloud()?.status?.();if(!platformAdmin||!gate||!ss?.user||ws||st?.membershipsLoading||st?.membershipsLoaded===false)return;const card=gate.querySelector('.sun-cloud-auth-card');if(!card||card.dataset.devEnhanced==='1')return;card.dataset.devEnhanced='1';gate.dataset.saasEnhanced='1';card.innerHTML=`<div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Аккаунт разработчика</h2><div class="hint">${esc(ss.user.email||'')}</div></div></div><p class="hint">Этот аккаунт управляет платформой и не обязан иметь собственную рабочую компанию.</p><div class="sun-cloud-auth-actions"><button class="primary" type="button" data-open-dev>Открыть кабинет разработчика</button><button class="outline" type="button" data-signout>Выйти</button></div>`;card.querySelector('[data-open-dev]').onclick=()=>open();card.querySelector('[data-signout]').onclick=()=>cloud()?.signOut?.()}
function syncChrome(){if(platformAdmin){ensureNav();enhanceDeveloperGate()}else{$('sunDeveloperNavV22')?.remove()}ensureSupportBanner()}
function boot(){installStyle();let tries=0;const timer=setInterval(async()=>{tries++;if(document.hidden)return;if(client()&&session()?.user){await checkPlatformAdmin(false);enhanceDeveloperGate();ensureSupportBanner()}else if(!session()?.user){platformAdmin=false;syncChrome()}if(tries>40)clearInterval(timer)},3000);window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(()=>checkPlatformAdmin(true),120));window.addEventListener('sun:cloud-state-applied',()=>ensureSupportBanner());document.addEventListener('click',e=>{const b=e.target.closest('header nav button');if(!b)return;if(String(b.dataset.navLabel||b.textContent||'').trim()==='Настройки'&&platformAdmin)setTimeout(()=>{$('sunSaaSSettingsCardV16')?.querySelector('[data-saas-admin]')?.remove()},120)},true);setTimeout(()=>checkPlatformAdmin(true),900)}
window.SunDeveloperV22={VERSION,open,isPlatformAdmin,checkPlatformAdmin,ensureNav,activateTab,ensureSupportBanner};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();
/* ===== MODULE: chat-v31.js ===== */
(() => {
'use strict';
if (window.SunChatV31) return;
const VERSION = '2026.09.06-chat-v31';
const $ = id => document.getElementById(id);
const qa = (s,r=document) => [...r.querySelectorAll(s)];
const esc=window.SunSafe.escapeHTML;
const toast = (text,type='info',timeout=4500) => { try { if(window.SunEnterprise?.toast) return window.SunEnterprise.toast(text,type,timeout); } catch (_) {} alert(text); };
const ROLE_LABELS = {admin:'Администратор',manager:'Менеджер',kitchen:'Кухня',courier:'Курьер',viewer:'Просмотр'};
let threads = [];
let members = [];
let messages = [];
let activeThread = null;
let workspaceChannel = null;
let threadChannel = null;
let workspaceChannelId = '';
let threadChannelId = '';
let online = new Set();
let pendingFiles = [];
let loadingThreads = false;
let loadingMessages = false;
let typingStopTimer = null;
let lastTypingSent = 0;
let typingUsers = new Map();
let sidebarMode = 'general';
let chatDock = 'global';
let bootRetryTimer = null;
function cloud(){ return window.SunCloudV2 || null; }
function client(){ return cloud()?.getClient?.() || null; }
function session(){ return cloud()?.getSession?.() || null; }
function workspace(){ return cloud()?.getWorkspace?.() || null; }
function supportMode(){ return Boolean(cloud()?.isSupportMode?.()); }
function available(){ return Boolean(client() && session()?.user && workspace()?.id && !supportMode()); }
function me(){ return session()?.user?.id || ''; }
function meName(){ const ws=workspace();return ws?.display_name || session()?.user?.email?.split('@')[0] || 'Сотрудник'; }
async function rpc(name,args={}){ const c=client();if(!c)throw new Error('Supabase не подключён.');const r=await c.rpc(name,args);if(r.error)throw r.error;return r.data; }
function fmtTime(v){ if(!v)return '';try{return new Intl.DateTimeFormat('ru-RU',{hour:'2-digit',minute:'2-digit'}).format(new Date(v))}catch(_){return ''} }
function fmtDay(v){ if(!v)return '';try{return new Intl.DateTimeFormat('ru-RU',{day:'numeric',month:'long'}).format(new Date(v))}catch(_){return ''} }
function sameDay(a,b){ try{const x=new Date(a),y=new Date(b);return x.getFullYear()===y.getFullYear()&&x.getMonth()===y.getMonth()&&x.getDate()===y.getDate()}catch(_){return false} }
function bytes(n){ n=Number(n||0);if(n<1024)return `${n} Б`;if(n<1024*1024)return `${Math.round(n/1024)} КБ`;return `${(n/1024/1024).toFixed(1)} МБ`; }
function safeName(name){ return String(name||'file').replace(/[\\/:*?"<>|\u0000-\u001f]+/g,'-').replace(/\s+/g,' ').trim().slice(0,120)||'file'; }
function uid(){ try{return window.sunUUID?.() || crypto.randomUUID()}catch(_){return `${Date.now()}-${Math.random().toString(36).slice(2)}`} }
function currentOrderId(){ try{return typeof draft!=='undefined'&&draft?.id?String(draft.id):''}catch(_){return ''} }
function savedOrders(){ try{return Array.isArray(orders)?orders.slice():[]}catch(_){return []} }
function installStyle(){
if($('sun-chat-style-v31'))return;
$('sun-chat-style-v29')?.remove();
const s=document.createElement('style');s.id='sun-chat-style-v31';s.textContent=`
#sunChatNavV29{position:relative}.sun-chat-nav-badge{display:none;position:absolute;right:5px;top:5px;min-width:18px;height:18px;border-radius:999px;padding:0 5px;background:#d83b35;color:#fff;font:800 10px/18px Arial;text-align:center;box-shadow:0 0 0 2px var(--sun-sidebar-bg,#32255d)}.sun-chat-nav-badge.on{display:block}
#sun-chat-view{padding:16px;min-height:calc(100vh - 70px);background:#f3f4f7}.sun-chat-shell{max-width:1420px;margin:0 auto;height:calc(100vh - 102px);min-height:620px;background:#fff;border:1px solid #e0e4e9;border-radius:18px;overflow:hidden;display:grid;grid-template-columns:330px minmax(0,1fr);box-shadow:0 12px 40px rgba(30,40,60,.07)}
.sun-chat-side{border-right:1px solid #e6e9ed;background:#fafbfc;display:flex;flex-direction:column;min-width:0}.sun-chat-side-head{padding:17px 17px 12px;border-bottom:1px solid #e8ebef}.sun-chat-side-head h1{font-size:22px;margin:0;color:#17384d}.sun-chat-side-head p{margin:5px 0 11px;color:#7a838d;font-size:12px}.sun-chat-section-tabs{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}.sun-chat-section-tab{position:relative;border:1px solid #dfe4e8;background:#fff;color:#53616d;border-radius:10px;min-height:34px;padding:6px 5px;font-size:10.5px;font-weight:850;cursor:pointer}.sun-chat-section-tab.on{background:#17384d;color:#fff;border-color:#17384d}.sun-chat-section-badge{display:none;position:absolute;right:3px;top:2px;min-width:15px;height:15px;padding:0 4px;border-radius:999px;background:#d83b35;color:#fff;font:800 8px/15px Arial}.sun-chat-section-badge.on{display:block}.sun-chat-side-scroll{overflow:auto;padding:10px;flex:1}.sun-chat-group-title{display:flex;align-items:center;justify-content:space-between;padding:10px 8px 6px;color:#8a919a;font-size:10px;text-transform:uppercase;letter-spacing:.08em;font-weight:900}.sun-chat-list{display:grid;gap:3px}.sun-chat-thread{width:100%;border:0;background:transparent;border-radius:11px;padding:9px 10px;text-align:left;display:grid;grid-template-columns:36px minmax(0,1fr) auto;gap:9px;align-items:center;cursor:pointer;color:#223342}.sun-chat-thread:hover{background:#f0f3f5}.sun-chat-thread.on{background:#eaf1f4}.sun-chat-avatar{width:36px;height:36px;border-radius:12px;background:#e3eaee;display:grid;place-items:center;font-weight:900;color:#36566b;position:relative;flex:none}.sun-chat-avatar.company{background:#1e4057;color:#fff}.sun-chat-avatar.order{background:#f3ead3;color:#7b5d1f}.sun-chat-presence-dot{position:absolute;width:10px;height:10px;border-radius:50%;right:-2px;bottom:-2px;background:#aeb5bb;border:2px solid #fff}.sun-chat-presence-dot.on{background:#28a568}.sun-chat-thread-main{min-width:0}.sun-chat-thread-title{font-size:13px;font-weight:850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sun-chat-thread-preview{font-size:10.5px;color:#838b94;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sun-chat-unread{min-width:20px;height:20px;padding:0 6px;border-radius:999px;background:#17384d;color:#fff;font:800 10px/20px Arial;text-align:center}.sun-chat-role{font-size:9px;color:#9299a1;margin-top:2px}
.sun-chat-main{display:flex;flex-direction:column;min-width:0;min-height:0;background:#fff}.sun-chat-head{min-height:68px;border-bottom:1px solid #e7eaee;padding:10px 16px;display:flex;align-items:center;gap:12px}.sun-chat-head-back{border:0;background:#f0f2f4;border-radius:10px;padding:8px 10px;cursor:pointer;font-weight:800}.sun-chat-head-text{min-width:0;flex:1}.sun-chat-head-title{font-size:16px;font-weight:900;color:#213649;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sun-chat-head-sub{font-size:11px;color:#808891;margin-top:3px}.sun-chat-online-text{color:#22845a}.sun-chat-body{flex:1;min-height:0;overflow:auto;background:linear-gradient(#f8f9fb,#f5f7f9);padding:18px}.sun-chat-empty{height:100%;display:grid;place-items:center;text-align:center;color:#858d96;padding:30px}.sun-chat-empty b{display:block;color:#52616d;font-size:18px;margin-bottom:5px}.sun-chat-day{text-align:center;margin:10px 0 14px}.sun-chat-day span{display:inline-block;background:#e7ebee;color:#707a83;border-radius:999px;padding:4px 9px;font-size:10px}.sun-chat-msg{max-width:min(680px,82%);margin:6px 0;display:flex;gap:8px;align-items:flex-end}.sun-chat-msg.own{margin-left:auto;flex-direction:row-reverse}.sun-chat-bubble{border:1px solid #e1e5e9;background:#fff;border-radius:15px 15px 15px 4px;padding:9px 11px;box-shadow:0 3px 12px rgba(20,30,40,.035);min-width:100px}.sun-chat-msg.own .sun-chat-bubble{background:#e8f1f4;border-color:#d8e6eb;border-radius:15px 15px 4px 15px}.sun-chat-sender{font-size:10px;font-weight:850;color:#47667a;margin-bottom:4px}.sun-chat-text{font-size:13px;line-height:1.42;color:#253642;overflow-wrap:anywhere;white-space:pre-wrap}.sun-chat-meta{display:flex;gap:7px;justify-content:flex-end;align-items:center;margin-top:5px;font-size:9px;color:#8c949c}.sun-chat-read{font-weight:750;color:#718693}.sun-chat-files{display:grid;gap:6px;margin-top:7px}.sun-chat-file{border:1px solid #dfe4e8;border-radius:10px;background:rgba(255,255,255,.7);overflow:hidden;display:block;color:#2d536a;text-decoration:none;max-width:360px}.sun-chat-file-img{width:100%;max-height:260px;display:block;object-fit:cover;background:#eef1f3}.sun-chat-file-line{display:flex;align-items:center;gap:8px;padding:8px 9px}.sun-chat-file-icon{font-size:20px}.sun-chat-file-name{font-size:11px;font-weight:750;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sun-chat-file-size{font-size:9px;color:#8a929a;margin-top:2px}
.sun-chat-typing{height:22px;padding:3px 16px;color:#71808a;font-size:10px;background:#f8f9fb}.sun-chat-composer{border-top:1px solid #e4e8eb;padding:10px 12px;background:#fff}.sun-chat-pending{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 8px}.sun-chat-pending:empty{display:none}.sun-chat-pending-chip{display:flex;align-items:center;gap:6px;background:#eef2f4;border-radius:9px;padding:5px 7px;max-width:260px}.sun-chat-pending-chip span{font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sun-chat-pending-chip button{border:0;background:transparent;cursor:pointer;font-size:15px}.sun-chat-compose-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:8px;align-items:end}.sun-chat-attach,.sun-chat-send{border:0;border-radius:11px;height:40px;cursor:pointer;font-weight:850}.sun-chat-attach{width:42px;background:#eef2f4;color:#476170;font-size:20px}.sun-chat-send{padding:0 17px;background:#17384d;color:#fff}.sun-chat-send:disabled{opacity:.55;cursor:default}.sun-chat-input{width:100%;min-height:40px;max-height:130px;resize:none;border:1px solid #d7dde1;border-radius:12px;padding:10px 12px;line-height:1.35;outline:none}.sun-chat-input:focus{border-color:#7896a9;box-shadow:0 0 0 3px rgba(70,110,135,.08)}
.sun-chat-order-tab{position:relative}.sun-chat-order-tab .sun-chat-mini-badge{position:absolute;top:-2px;right:-3px;background:#d83b35;color:#fff;border-radius:999px;min-width:16px;height:16px;padding:0 4px;font:800 9px/16px Arial}.sun-order-chat-panel{display:none;min-height:570px;height:min(68vh,760px);border:1px solid #e1e5e9;border-radius:14px;overflow:hidden;margin-top:10px;background:#fff}.sun-order-chat-panel.on{display:block}.sun-order-chat-panel .sun-chat-main{height:100%;min-height:0}.sun-chat-offline-note{padding:14px;border-radius:12px;background:#fff6e2;color:#85631c;margin:18px}
@media(max-width:850px){#sun-chat-view{padding:0}.sun-chat-shell{height:calc(100vh - 94px);min-height:0;border-radius:0;border-left:0;border-right:0;grid-template-columns:1fr}.sun-chat-side{display:flex}.sun-chat-main{display:none}.sun-chat-shell.thread-open .sun-chat-side{display:none}.sun-chat-shell.thread-open .sun-chat-main{display:flex}.sun-chat-msg{max-width:92%}.sun-chat-body{padding:12px}.sun-chat-section-tab{font-size:10px}.sun-order-chat-panel{height:62vh;min-height:460px}.sun-order-chat-panel .sun-chat-main{display:flex!important}.sun-order-chat-panel .sun-chat-head{min-height:58px}}
`;document.head.appendChild(s);
}
function ensureView(){
installStyle();
let v=$('sun-chat-view');
if(!v){
v=document.createElement('section');v.id='sun-chat-view';v.className='view';v.innerHTML=`<div class="sun-chat-shell" id="sunChatShellV29"><aside class="sun-chat-side"><div class="sun-chat-side-head"><h1>Чат</h1><p id="sunChatCompanyNameV29">Команда Caterium</p><div class="sun-chat-section-tabs"><button class="sun-chat-section-tab on" type="button" data-chat-section="general">Общий<span class="sun-chat-section-badge" data-chat-section-badge="general"></span></button><button class="sun-chat-section-tab" type="button" data-chat-section="direct">Личные<span class="sun-chat-section-badge" data-chat-section-badge="direct"></span></button><button class="sun-chat-section-tab" type="button" data-chat-section="orders">Заказы<span class="sun-chat-section-badge" data-chat-section-badge="orders"></span></button></div></div><div class="sun-chat-side-scroll" id="sunChatSidebarV29"></div></aside><main class="sun-chat-main" id="sunChatMainV29"><div class="sun-chat-head" id="sunChatHeadV29"></div><div class="sun-chat-body" id="sunChatBodyV29"><div class="sun-chat-empty"><div><b>Выберите чат</b><span>Общий чат компании, сотрудника или обсуждение заказа.</span></div></div></div><div class="sun-chat-typing" id="sunChatTypingV29"></div><div class="sun-chat-composer" id="sunChatComposerV29" hidden><div class="sun-chat-pending" id="sunChatPendingV29"></div><div class="sun-chat-compose-row"><button class="sun-chat-attach" type="button" id="sunChatAttachV29" title="Добавить фото или файл"></button><textarea class="sun-chat-input" id="sunChatInputV29" rows="1" maxlength="4000" placeholder="Сообщение…"></textarea><button class="sun-chat-send" type="button" id="sunChatSendV29">Отправить</button><input type="file" id="sunChatFilesV29" hidden multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.csv,.zip"></div></div></main></div>`;
const firstModal=document.querySelector('body > .modal');if(firstModal)firstModal.before(v);else document.body.appendChild(v);
$('sunChatAttachV29').onclick=()=>$('sunChatFilesV29').click();
$('sunChatFilesV29').onchange=e=>addPendingFiles(e.target.files);
$('sunChatSendV29').onclick=sendMessage;
$('sunChatInputV29').addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();}});
$('sunChatInputV29').addEventListener('input',()=>{autoGrowInput();sendTyping(true);});
qa('[data-chat-section]',v).forEach(b=>b.addEventListener('click',()=>switchSidebarMode(b.dataset.chatSection)));
}
return v;
}
function ensureNav(){
const nav=document.querySelector('header nav');if(!nav)return null;
let b=$('sunChatNavV29');
if(!available()){b?.remove();return null;}
if(!b){
b=document.createElement('button');b.id='sunChatNavV29';b.type='button';b.dataset.navLabel='Чат';b.className='nav-chat';b.innerHTML=`Чат <span class="sun-chat-nav-badge" id="sunChatNavBadgeV29"></span>`;
b.addEventListener('click',()=>openMain({mode:'general'}));
}
if(typeof window.sunRegisterNavButton==='function'){
window.sunRegisterNavButton({button:b,group:'management',beforeLabel:'Настройки',navLabel:'Чат',className:'nav-chat'});
}else{
const group=nav.querySelector('.sun-nav-management')||nav;
if(b.parentElement!==group)group.appendChild(b);
b.classList.add('sun-nav-button','nav-chat');
}
return b;
}
function setNavBadge(n){ const b=$('sunChatNavBadgeV29');if(!b)return;n=Number(n||0);b.textContent=n>99?'99+':String(n);b.classList.toggle('on',n>0); }
function setSectionBadge(mode,n){const b=document.querySelector(`[data-chat-section-badge="${mode}"]`);if(!b)return;n=Number(n||0);b.textContent=n>99?'99+':String(n);b.classList.toggle('on',n>0);}
function setActiveNav(){qa('header nav button').forEach(x=>x.classList.remove('on'));$('sunChatNavV29')?.classList.add('on');qa('.view').forEach(x=>x.classList.remove('on'));ensureView().classList.add('on');}
function threadIcon(t){if(t.kind==='company')return ['КО','company'];if(t.kind==='order')return ['№','order'];return [String(t.title||'?').trim().slice(0,1).toUpperCase(),''];}
function threadById(id){return threads.find(t=>String(t.thread_id)===String(id))||null;}
function directByUser(id){return threads.find(t=>t.kind==='direct'&&String(t.other_user_id)===String(id))||null;}
function orderThreadByOrder(id){return threads.find(t=>t.kind==='order'&&String(t.order_id)===String(id))||null;}
function memberById(id){return members.find(m=>String(m.user_id)===String(id))||null;}
function switchSidebarMode(mode){
sidebarMode=['general','direct','orders'].includes(mode)?mode:'general';
qa('[data-chat-section]').forEach(b=>b.classList.toggle('on',b.dataset.chatSection===sidebarMode));
renderSidebar();
if(sidebarMode==='general'){
const t=threads.find(x=>x.kind==='company');
if(t&&(!activeThread||activeThread.kind!=='company'))openThread(t);
}else if(activeThread&&((sidebarMode==='direct'&&activeThread.kind!=='direct')||(sidebarMode==='orders'&&activeThread.kind!=='order'))){
activeThread=null;messages=[];unsubscribeThread();$('sunChatShellV29')?.classList.remove('thread-open');$('sunChatComposerV29').hidden=true;renderHead();renderMessages();
}
}
function renderSidebar(){
const root=$('sunChatSidebarV29');if(!root)return;
$('sunChatCompanyNameV29').textContent=workspace()?.name||'Компания';
qa('[data-chat-section]').forEach(b=>b.classList.toggle('on',b.dataset.chatSection===sidebarMode));
const general=threads.find(t=>t.kind==='company');
const others=members.filter(m=>String(m.user_id)!==String(me()));
const localOrders=savedOrders().sort((a,b)=>Number(b?.id||0)-Number(a?.id||0));
const row=t=>{const [letter,cls]=threadIcon(t);const isOn=activeThread&&String(activeThread.thread_id)===String(t.thread_id);return `<button class="sun-chat-thread ${isOn?'on':''}" type="button" data-chat-thread="${esc(t.thread_id)}"><span class="sun-chat-avatar ${cls}">${esc(letter)}${t.kind==='direct'?`<i class="sun-chat-presence-dot ${online.has(String(t.other_user_id))?'on':''}"></i>`:''}</span><span class="sun-chat-thread-main"><span class="sun-chat-thread-title">${esc(t.title||'Чат')}</span><span class="sun-chat-thread-preview">${esc(t.last_message|| (t.kind==='company'?'Сообщения всей команды':t.kind==='order'?'Обсуждение заказа':'Личная переписка'))}</span></span>${Number(t.unread_count||0)>0?`<span class="sun-chat-unread">${Number(t.unread_count)>99?'99+':Number(t.unread_count)}</span>`:'<span></span>'}</button>`};
const memberRow=m=>{const t=directByUser(m.user_id);if(t)return row(t);const isOnline=online.has(String(m.user_id));return `<button class="sun-chat-thread" type="button" data-chat-user="${esc(m.user_id)}"><span class="sun-chat-avatar">${esc(String(m.display_name||m.email||'?').trim().slice(0,1).toUpperCase())}<i class="sun-chat-presence-dot ${isOnline?'on':''}"></i></span><span class="sun-chat-thread-main"><span class="sun-chat-thread-title">${esc(m.display_name||m.email||'Сотрудник')}</span><span class="sun-chat-role">${esc(ROLE_LABELS[m.role]||m.role||'Сотрудник')} · ${isOnline?'онлайн':'не в сети'}</span></span><span></span></button>`};
const orderRow=o=>{const t=orderThreadByOrder(o.id);if(t)return row({...t,title:`${o.id}${o.event?` · ${o.event}`:''}`});return `<button class="sun-chat-thread" type="button" data-chat-order="${esc(o.id)}"><span class="sun-chat-avatar order">№</span><span class="sun-chat-thread-main"><span class="sun-chat-thread-title">№${esc(o.id)}${o.event?` · ${esc(o.event)}`:''}</span><span class="sun-chat-thread-preview">${esc([o.date,o.time].filter(Boolean).join(' · ')||'Открыть обсуждение заказа')}</span></span><span></span></button>`};
if(sidebarMode==='general')root.innerHTML=`<div class="sun-chat-group-title"><span>Общий чат компании</span></div><div class="sun-chat-list">${general?row(general):'<div class="hint" style="padding:8px">Загружаю общий чат…</div>'}</div>`;
if(sidebarMode==='direct')root.innerHTML=`<div class="sun-chat-group-title"><span>Сотрудники</span><span>${others.length}</span></div><div class="sun-chat-list">${others.length?others.map(memberRow).join(''):'<div class="hint" style="padding:8px">Добавьте сотрудников — здесь появятся личные чаты.</div>'}</div>`;
if(sidebarMode==='orders')root.innerHTML=`<div class="sun-chat-group-title"><span>Чаты заказов</span><span>${localOrders.length}</span></div><div class="sun-chat-list">${localOrders.length?localOrders.map(orderRow).join(''):'<div class="hint" style="padding:8px">Сохранённых заказов пока нет.</div>'}</div>`;
qa('[data-chat-thread]',root).forEach(b=>b.onclick=()=>openThread(threadById(b.dataset.chatThread)));
qa('[data-chat-user]',root).forEach(b=>b.onclick=()=>openDirect(b.dataset.chatUser));
qa('[data-chat-order]',root).forEach(b=>b.onclick=()=>openOrderGlobal(b.dataset.chatOrder));
updateUnreadBadges();
}
function updateUnreadBadges(){
const general=threads.filter(t=>t.kind==='company').reduce((n,t)=>n+Number(t.unread_count||0),0);
const direct=threads.filter(t=>t.kind==='direct').reduce((n,t)=>n+Number(t.unread_count||0),0);
const order=threads.filter(t=>t.kind==='order').reduce((n,t)=>n+Number(t.unread_count||0),0);
setSectionBadge('general',general);setSectionBadge('direct',direct);setSectionBadge('orders',order);setNavBadge(general+direct+order);updateOrderChatBadge();
}
function activeSubtitle(t){
if(!t)return '';
if(t.kind==='direct'){const m=memberById(t.other_user_id);return online.has(String(t.other_user_id))?'<span class="sun-chat-online-text">онлайн</span>':esc(ROLE_LABELS[m?.role]||'сотрудник');}
if(t.kind==='company'){const count=members.length,on=members.filter(m=>online.has(String(m.user_id))).length;return `${count} сотрудников · ${on} онлайн`;}
return 'Обсуждение заказа';
}
function renderHead(){
const h=$('sunChatHeadV29');if(!h)return;
if(!activeThread){h.innerHTML='';return;}
const mobileBack=chatDock==='global'?`<button class="sun-chat-head-back" type="button" data-chat-mobile-back>←</button>`:'';
const orderBack=chatDock==='order'?`<button class="sun-chat-head-back" type="button" data-chat-order-back>← Заказ</button>`:'';
h.innerHTML=`${orderBack||mobileBack}<div class="sun-chat-head-text"><div class="sun-chat-head-title">${esc(activeThread.title||'Чат')}</div><div class="sun-chat-head-sub">${activeSubtitle(activeThread)}</div></div>`;
h.querySelector('[data-chat-mobile-back]')?.addEventListener('click',()=>{activeThread=null;messages=[];unsubscribeThread();$('sunChatShellV29')?.classList.remove('thread-open');renderSidebar();renderHead();renderMessages();$('sunChatComposerV29').hidden=true;});
h.querySelector('[data-chat-order-back]')?.addEventListener('click',()=>$('orderTab')?.click());
}
function renderMessages(){
const root=$('sunChatBodyV29');if(!root)return;
if(!activeThread){root.innerHTML='<div class="sun-chat-empty"><div><b>Выберите чат</b><span>Общий чат компании, сотрудника или обсуждение заказа.</span></div></div>';return;}
if(loadingMessages){root.innerHTML='<div class="sun-chat-empty"><div><b>Загружаю сообщения…</b></div></div>';return;}
if(!messages.length){root.innerHTML='<div class="sun-chat-empty"><div><b>Пока нет сообщений</b><span>Напишите первое сообщение.</span></div></div>';return;}
let prev=null,html='';
for(const m of messages){
if(!prev||!sameDay(prev,m.created_at))html+=`<div class="sun-chat-day"><span>${esc(fmtDay(m.created_at))}</span></div>`;
prev=m.created_at;const own=String(m.sender_user_id)===String(me());const atts=Array.isArray(m.attachments)?m.attachments:[];
const files=atts.length?`<div class="sun-chat-files">${atts.map((a,i)=>`<a class="sun-chat-file" href="#" data-chat-attachment="${esc(a.path||'')}" data-chat-type="${esc(a.type||'')}" data-chat-message="${esc(m.message_id)}" data-chat-index="${i}" target="_blank" rel="noopener"><div class="sun-chat-file-preview"></div><div class="sun-chat-file-line"><span class="sun-chat-file-icon">${String(a.type||'').startsWith('image/')?'▣':'▤'}</span><span style="min-width:0"><div class="sun-chat-file-name">${esc(a.name||'Файл')}</div><div class="sun-chat-file-size">${esc(bytes(a.size))}</div></span></div></a>`).join('')}</div>`:'';
const read=own?(Number(m.read_by_count||0)>0?(activeThread.kind==='direct'?'Прочитано':`Прочитали: ${Number(m.read_by_count)}`):'Доставлено'):'';
html+=`<div class="sun-chat-msg ${own?'own':''}"><div class="sun-chat-bubble">${!own&&activeThread.kind!=='direct'?`<div class="sun-chat-sender">${esc(m.sender_name||m.sender_email||'Сотрудник')}</div>`:''}${m.body?`<div class="sun-chat-text">${esc(m.body)}</div>`:''}${files}<div class="sun-chat-meta"><span>${esc(fmtTime(m.created_at))}</span>${read?`<span class="sun-chat-read">${esc(read)}</span>`:''}</div></div></div>`;
}
root.innerHTML=html;hydrateAttachments();requestAnimationFrame(()=>{root.scrollTop=root.scrollHeight});
}
async function hydrateAttachments(){
const c=client();if(!c)return;
const links=qa('[data-chat-attachment]',$('sunChatBodyV29'));
for(const a of links){const path=a.dataset.chatAttachment;if(!path)continue;try{const r=await c.storage.from('sun-chat').createSignedUrl(path,3600);if(r.error)throw r.error;const url=r.data?.signedUrl;if(!url)continue;a.href=url;const type=a.dataset.chatType||'';if(type.startsWith('image/')){const p=a.querySelector('.sun-chat-file-preview');if(p)p.innerHTML=`<img class="sun-chat-file-img" alt="Вложение" src="${esc(url)}">`;}}catch(e){a.onclick=ev=>{ev.preventDefault();toast('Не удалось открыть вложение.','error')}}}
}
function renderTyping(){const root=$('sunChatTypingV29');if(!root)return;const names=[...typingUsers.values()].filter(x=>x&&x.user_id!==me()&&Date.now()-x.at<3500).map(x=>x.name||'Сотрудник');root.textContent=names.length?`${names.slice(0,2).join(', ')} ${names.length>1?'печатают':'печатает'}`:'';}
function renderPending(){const root=$('sunChatPendingV29');if(!root)return;root.innerHTML=pendingFiles.map((f,i)=>`<div class="sun-chat-pending-chip"><span>${esc(f.name)} · ${esc(bytes(f.size))}</span><button type="button" data-remove-file="${i}">×</button></div>`).join('');qa('[data-remove-file]',root).forEach(b=>b.onclick=()=>{pendingFiles.splice(Number(b.dataset.removeFile),1);renderPending()});}
function autoGrowInput(){const x=$('sunChatInputV29');if(!x)return;x.style.height='auto';x.style.height=Math.min(130,x.scrollHeight)+'px';}
async function loadThreads({quiet=false}={}){
if(!available()||loadingThreads)return;
loadingThreads=true;
try{
const ws=workspace();await rpc('sun_chat_get_company_thread_v29',{p_workspace:ws.id});
const [t,m]=await Promise.all([rpc('sun_chat_list_threads_v29',{p_workspace:ws.id}),rpc('sun_chat_list_members_v29',{p_workspace:ws.id})]);
threads=Array.isArray(t)?t:[];members=Array.isArray(m)?m:[];
if(activeThread){const fresh=threadById(activeThread.thread_id);if(fresh)activeThread={...activeThread,...fresh};}
renderSidebar();renderHead();updateUnreadBadges();
}catch(e){if(!quiet)toast(e.message||String(e),'error',6500)}finally{loadingThreads=false;}
}
async function loadMessages({mark=true,quiet=false}={}){
if(!activeThread||!available()||loadingMessages)return;
loadingMessages=true;renderMessages();
try{
const data=await rpc('sun_chat_list_messages_v29',{p_thread:activeThread.thread_id,p_limit:120,p_before:null});messages=Array.isArray(data)?data:[];
if(mark){const t=threadById(activeThread.thread_id);if(Number(t?.unread_count||0)>0){await rpc('sun_chat_mark_read_v29',{p_thread:activeThread.thread_id});await loadThreads({quiet:true});}}
}catch(e){if(!quiet)toast(e.message||String(e),'error',6500)}finally{loadingMessages=false;renderMessages();}
}
async function openThread(t){
if(!t)return;activeThread=t;messages=[];pendingFiles=[];$('sunChatShellV29')?.classList.add('thread-open');$('sunChatComposerV29').hidden=false;$('sunChatInputV29').value='';renderPending();renderSidebar();renderHead();renderMessages();await subscribeThread(t.thread_id);await loadMessages({mark:true});setTimeout(()=>$('sunChatInputV29')?.focus(),80);
}
async function openDirect(userId){
if(!available())return;try{sidebarMode='direct';const id=await rpc('sun_chat_get_direct_thread_v29',{p_workspace:workspace().id,p_other_user:userId});await loadThreads({quiet:true});const t=threadById(id)||{thread_id:id,kind:'direct',other_user_id:userId,title:memberById(userId)?.display_name||'Сотрудник',unread_count:0};await openThread(t);}catch(e){toast(e.message||String(e),'error',6500)}
}
async function getOrderThread(orderId){
const id=await rpc('sun_chat_get_order_thread_v29',{p_workspace:workspace().id,p_order_id:String(orderId)});await loadThreads({quiet:true});return threadById(id)||{thread_id:id,kind:'order',order_id:String(orderId),title:`Заказ №${orderId}`,unread_count:0};
}
async function openOrderGlobal(orderId){
if(!available())return toast('Войдите в Caterium, чтобы открыть обсуждение заказа.','warn');if(!orderId)return toast('Сначала сохраните заказ.','warn');
try{dockMainGlobal();setActiveNav();sidebarMode='orders';await ensureWorkspaceChannel();const t=await getOrderThread(orderId);renderSidebar();await openThread(t);}catch(e){toast(e.message||String(e),'error',6500)}
}
function ensureOrderChatPanel(){
const sub=document.querySelector('#new .subnav');if(!sub)return null;
let p=$('sunOrderChatPanelV31');
if(!p){p=document.createElement('div');p.id='sunOrderChatPanelV31';p.className='sun-order-chat-panel';sub.after(p);}
return p;
}
function dockMainGlobal(){
const main=$('sunChatMainV29'),shell=$('sunChatShellV29');if(main&&shell&&main.parentElement!==shell)shell.appendChild(main);chatDock='global';
}
function dockMainOrder(){
const main=$('sunChatMainV29'),panel=ensureOrderChatPanel();if(main&&panel&&main.parentElement!==panel)panel.appendChild(main);chatDock='order';
}
function setOrderChatMode(on){
const sub=document.querySelector('#new .subnav'),card=sub?.closest('.card'),panel=ensureOrderChatPanel(),chatTab=$('sunOrderChatTabV31');if(!card||!panel)return;
const money=card.querySelector(':scope > .money-form'),summary=card.querySelector(':scope > .order-summary'),actions=card.querySelector(':scope > .actions');
if(on){
$('orderForm')?.style.setProperty('display','none');$('details')?.style.setProperty('display','none');
if(money)money.style.display='none';if(summary)summary.style.display='none';if(actions)actions.style.display='none';
panel.classList.add('on');panel.hidden=false;$('orderTab')?.classList.remove('on');$('detailTab')?.classList.remove('on');chatTab?.classList.add('on');dockMainOrder();
}else{
panel.classList.remove('on');panel.hidden=true;if(money)money.style.display='';if(summary)summary.style.display='';if(actions)actions.style.display='';chatTab?.classList.remove('on');dockMainGlobal();
}
}
async function openOrderInline(orderId){
if(!available())return toast('Войдите в Caterium, чтобы открыть обсуждение заказа.','warn');if(!orderId)return toast('Сначала сохраните заказ — после этого появится чат.','warn');
try{ensureView();ensureOrderDiscussionTab();setOrderChatMode(true);await ensureWorkspaceChannel();sidebarMode='orders';const t=await getOrderThread(orderId);await openThread(t);}catch(e){setOrderChatMode(false);toast(e.message||String(e),'error',6500)}
}
async function openMain({threadId=null,mode='general'}={}){
if(!available())return toast('Чат доступен после входа в рабочую компанию.','warn');ensureView();dockMainGlobal();ensureNav();setActiveNav();sidebarMode=['general','direct','orders'].includes(mode)?mode:'general';await ensureWorkspaceChannel();await loadThreads();let t=threadId?threadById(threadId):null;if(!t&&sidebarMode==='general')t=threads.find(x=>x.kind==='company')||null;if(t)await openThread(t);else{activeThread=null;messages=[];unsubscribeThread();$('sunChatShellV29')?.classList.remove('thread-open');$('sunChatComposerV29').hidden=true;renderSidebar();renderHead();renderMessages();}
}
async function addPendingFiles(fileList){
const files=[...(fileList||[])];$('sunChatFilesV29').value='';
for(const f of files){if(pendingFiles.length>=5){toast('Можно отправить не более 5 файлов за раз.','warn');break}if(f.size>15*1024*1024){toast(`Файл «${f.name}» больше 15 МБ.`,'warn');continue}pendingFiles.push(f)}renderPending();
}
async function uploadPending(){
const c=client(),ws=workspace();if(!c||!ws||!activeThread)return [];
const result=[];
for(const f of pendingFiles){const path=`${ws.id}/${activeThread.thread_id}/${uid()}-${safeName(f.name)}`;const up=await c.storage.from('sun-chat').upload(path,f,{contentType:f.type||'application/octet-stream',upsert:false});if(up.error)throw up.error;result.push({path,name:f.name,type:f.type||'application/octet-stream',size:f.size});}
return result;
}
async function sendMessage(){
if(!activeThread||!available())return;const input=$('sunChatInputV29'),btn=$('sunChatSendV29');const text=String(input?.value||'').trim();if(!text&&!pendingFiles.length)return;
btn.disabled=true;btn.textContent='…';
try{const attachments=await uploadPending();await rpc('sun_chat_send_message_v29',{p_thread:activeThread.thread_id,p_body:text,p_attachments:attachments});input.value='';input.style.height='auto';pendingFiles=[];renderPending();sendTyping(false);await Promise.all([loadThreads({quiet:true}),loadMessages({mark:false,quiet:true})]);}
catch(e){toast(e.message||String(e),'error',7000)}finally{btn.disabled=false;btn.textContent='Отправить';input?.focus();}
}
async function ensureWorkspaceChannel(){
if(!available())return unsubscribeWorkspace();const c=client(),ws=workspace(),ss=session();if(!c||!ws||!ss)return;
if(workspaceChannel&&workspaceChannelId===ws.id)return;
unsubscribeWorkspace();workspaceChannelId=ws.id;
try{if(c.realtime?.setAuth)await c.realtime.setAuth(ss.access_token);}catch(_){}
const ch=c.channel(`sun-chat-workspace:${ws.id}`,{config:{private:true,presence:{key:me()},broadcast:{ack:true}}});workspaceChannel=ch;
ch.on('broadcast',{event:'chat_changed'},async()=>{await loadThreads({quiet:true});if(activeThread){const fresh=threadById(activeThread.thread_id);if(fresh&&Number(fresh.unread_count||0)>0)await loadMessages({mark:true,quiet:true});else renderMessages();}})
.on('presence',{event:'sync'},()=>{online=new Set(Object.keys(ch.presenceState()||{}));renderSidebar();renderHead();})
.on('presence',{event:'join'},()=>{online=new Set(Object.keys(ch.presenceState()||{}));renderSidebar();renderHead();})
.on('presence',{event:'leave'},()=>{online=new Set(Object.keys(ch.presenceState()||{}));renderSidebar();renderHead();})
.subscribe(async(status)=>{if(status==='SUBSCRIBED'){try{await ch.track({user_id:me(),name:meName(),at:new Date().toISOString()})}catch(_){}await loadThreads({quiet:true});}});
}
function unsubscribeWorkspace(){if(workspaceChannel){try{client()?.removeChannel?.(workspaceChannel)}catch(_){}workspaceChannel=null;}workspaceChannelId='';online=new Set();unsubscribeThread();}
async function subscribeThread(id){
if(!available()||!id)return;const c=client(),ss=session();if(threadChannel&&threadChannelId===id)return;unsubscribeThread();threadChannelId=id;
try{if(c.realtime?.setAuth)await c.realtime.setAuth(ss.access_token);}catch(_){}
const ch=c.channel(`sun-chat-thread:${id}`,{config:{private:true,broadcast:{ack:false,self:false}}});threadChannel=ch;
ch.on('broadcast',{event:'message'},()=>loadMessages({mark:true,quiet:true}))
.on('broadcast',{event:'read'},()=>loadMessages({mark:false,quiet:true}))
.on('broadcast',{event:'typing'},payload=>{const p=payload?.payload||{};if(!p.user_id||String(p.user_id)===String(me()))return;if(p.typing)typingUsers.set(String(p.user_id),{user_id:p.user_id,name:p.name||'Сотрудник',at:Date.now()});else typingUsers.delete(String(p.user_id));renderTyping();setTimeout(()=>renderTyping(),3600)})
.subscribe();
}
function unsubscribeThread(){if(threadChannel){try{client()?.removeChannel?.(threadChannel)}catch(_){}threadChannel=null;}threadChannelId='';typingUsers.clear();renderTyping();}
async function sendTyping(flag){
if(!threadChannel||!activeThread)return;const now=Date.now();if(flag&&now-lastTypingSent<700){clearTimeout(typingStopTimer);typingStopTimer=setTimeout(()=>sendTyping(false),1800);return}lastTypingSent=now;
try{await threadChannel.send({type:'broadcast',event:'typing',payload:{user_id:me(),name:meName(),typing:Boolean(flag)}})}catch(_){}
clearTimeout(typingStopTimer);if(flag)typingStopTimer=setTimeout(()=>sendTyping(false),1800);
}
function updateOrderChatBadge(){
const b=$('sunOrderChatTabV31');if(!b)return;const id=currentOrderId(),t=id?orderThreadByOrder(id):null,n=Number(t?.unread_count||0);let badge=b.querySelector('.sun-chat-mini-badge');if(n>0){if(!badge){badge=document.createElement('span');badge.className='sun-chat-mini-badge';b.appendChild(badge);}badge.textContent=n>99?'99+':String(n);}else badge?.remove();
}
function ensureOrderDiscussionTab(){
const sub=document.querySelector('#new .subnav');if(!sub)return;
ensureOrderChatPanel();
let b=$('sunOrderChatTabV31');
$('sunOrderChatTabV29')?.remove();
if(!b){
b=document.createElement('button');b.id='sunOrderChatTabV31';b.type='button';b.className='sun-chat-order-tab';b.textContent='Чат';sub.appendChild(b);
b.addEventListener('click',e=>{e.preventDefault();const id=currentOrderId();if(!id)return toast('Сначала сохраните заказ — после этого появится чат.','warn');openOrderInline(id);});
}
b.title=currentOrderId()?'Чат этого заказа':'Сначала сохраните заказ';b.style.opacity=currentOrderId()?'1':'.55';
['orderTab','detailTab'].forEach(id=>{const x=$(id);if(x&&!x.dataset.sunChatCloseBound){x.dataset.sunChatCloseBound='1';x.addEventListener('click',()=>setOrderChatMode(false));}});
updateOrderChatBadge();
}
function openOrderFromCard(id){
if(!id)return;
try{if(typeof window.openOrder==='function')window.openOrder(Number(id));}catch(e){return toast(e.message||String(e),'error')}
setTimeout(()=>openOrderInline(String(id)),60);
}
function decorateOrderCards(){
qa('#sunOrderCards .sun-order-card').forEach(card=>{const actions=card.querySelector('.sun-order-card-actions');if(!actions||actions.querySelector('[data-card-chat]'))return;const b=document.createElement('button');b.className='mini';b.type='button';b.dataset.cardChat=card.dataset.orderId;b.textContent='Чат';b.onclick=e=>{e.stopPropagation();openOrderFromCard(b.dataset.cardChat)};actions.prepend(b);});
}
function safeStateChanged(){
try{
installStyle();ensureView();ensureOrderDiscussionTab();decorateOrderCards();
if(available()){ensureNav();ensureWorkspaceChannel();loadThreads({quiet:true});}
else{setNavBadge(0);$('sunChatNavV29')?.remove();unsubscribeWorkspace();activeThread=null;threads=[];members=[];messages=[];setOrderChatMode(false);}
}catch(e){console.error('[Caterium Chat] state refresh failed',e);}
}
function boot(){
installStyle();ensureView();ensureOrderDiscussionTab();
const priorOpen=window.openOrder;
if(typeof priorOpen==='function'&&!priorOpen.__sunChatV31){const wrapped=function(...args){setOrderChatMode(false);const r=priorOpen.apply(this,args);setTimeout(()=>{ensureOrderDiscussionTab();decorateOrderCards();},0);return r};wrapped.__sunChatV31=true;window.openOrder=wrapped;}
const priorRenderOrders=window.renderOrders;
if(typeof priorRenderOrders==='function'&&!priorRenderOrders.__sunChatV31){const wrapped=function(...args){const r=priorRenderOrders.apply(this,args);setTimeout(decorateOrderCards,0);return r};wrapped.__sunChatV31=true;window.renderOrders=wrapped;}
document.addEventListener('click',e=>{const nav=e.target.closest('header nav button');if(!nav)return;if(nav.id!=='sunChatNavV29')setOrderChatMode(false);setTimeout(()=>{ensureOrderDiscussionTab();decorateOrderCards();},20);},true);
window.addEventListener('sun:cloud-permissions-changed',()=>setTimeout(safeStateChanged,60));
window.addEventListener('sun:cloud-state-applied',()=>setTimeout(safeStateChanged,60));
window.addEventListener('online',()=>{if(available())ensureWorkspaceChannel()});
window.addEventListener('beforeunload',()=>{try{workspaceChannel?.untrack?.()}catch(_){}});
let tries=0;bootRetryTimer=setInterval(()=>{tries++;safeStateChanged();if(available()||tries>=24){clearInterval(bootRetryTimer);bootRetryTimer=null;}},750);setTimeout(safeStateChanged,250);
}
window.SunChatV29={VERSION,open:openMain,openOrder:openOrderInline,openDirect,refresh:()=>loadThreads(),getThreads:()=>threads.slice(),getMembers:()=>members.slice()};
window.SunChatV31=window.SunChatV29;
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
})();