import fs from 'node:fs'; const VERSION='17.6.6'; const RELEASE='20260908-v17-6-6-offer-templates'; const read=f=>fs.readFileSync(f,'utf8'); const write=(f,s)=>fs.writeFileSync(f,s); function once(s,a,b,label){const n=s.split(a).length-1;if(n!==1)throw new Error(`${label}: expected 1 match, got ${n}`);return s.replace(a,b)} // ---- Core proposal renderer: keep the per-offer template authoritative and make page compositions unmistakably different. let runtime=read('public/app-runtime.js'); runtime=once(runtime, " const prepared=await preparedSnapshot(snapshot);prepared.offerTemplateId=offerTemplateId();", " const prepared=await preparedSnapshot(snapshot);const explicitTemplate=String(snapshot?.offerTemplateId||prepared?.offerTemplateId||'').trim();prepared.offerTemplateId=OFFER_TEMPLATE_IDS.has(explicitTemplate)?explicitTemplate:offerTemplateId();", 'preserve offer template'); runtime=once(runtime, " newPage();brandHeader();hero();sellingBlock();drawItems();summary();if(id==='editorial-grid'){pricing();infoCards()}else{infoCards();pricing()}footerNoteBlock();finalGalleryFill();", " newPage();\n if(id==='editorial-grid'){\n brandHeader();hero();drawItems();summary();pricing();sellingBlock();infoCards();finalGalleryFill();footerNoteBlock();\n }else if(id==='midnight-glass'){\n brandHeader();hero();sellingBlock();summary();infoCards();drawItems();pricing();finalGalleryFill();footerNoteBlock();\n }else if(id==='emerald-gold'){\n brandHeader();hero();summary();sellingBlock();infoCards();pricing();drawItems();footerNoteBlock();finalGalleryFill();\n }else{\n brandHeader();hero();sellingBlock();drawItems();summary();infoCards();pricing();footerNoteBlock();finalGalleryFill();\n }", 'distinct PDF compositions'); runtime=once(runtime, " {id:'light',name:'Светлый фирменный',desc:'Белый фон, фирменный зелёный и золото. Основной дизайн.',thumb:'offer-templates/thumb-light.jpg'},\n {id:'editorial-grid',name:'Солнце Editorial',desc:'Макет по референсу: крупный первый экран, компактное меню, фотосетка и итоги на одной визуальной системе.',thumb:'offer-templates/thumb-editorial-grid.jpg'},\n {id:'midnight-glass',name:'Midnight Glass',desc:'Двухколоночный макет: меню списком слева, реальные фото боксов мозаикой справа.',thumb:'offer-templates/thumb-midnight-glass.jpg'},\n {id:'emerald-gold',name:'Emerald Gold',desc:'Премиальная композиция: золотой итог в первом экране, меню слева и фотоколлаж справа.',thumb:'offer-templates/thumb-emerald-gold.jpg'}", " {id:'light',name:'Светлый фирменный',desc:'Классическая A4-композиция: спокойная обложка, карточки блюд по порядку, затем итог и условия.',thumb:'offer-templates/thumb-light.jpg'},\n {id:'editorial-grid',name:'Солнце Editorial',desc:'Журнальная сетка: крупный визуальный первый экран, асимметричные блоки меню и фотосетка.',thumb:'offer-templates/thumb-editorial-grid.jpg'},\n {id:'midnight-glass',name:'Midnight Glass',desc:'Тёмная презентация: крупные стеклянные панели, итог раньше меню и контрастная двухколоночная подача.',thumb:'offer-templates/thumb-midnight-glass.jpg'},\n {id:'emerald-gold',name:'Emerald Gold',desc:'Премиальный буклет: итог на первом экране, золотые информационные блоки и меню как финальная часть презентации.',thumb:'offer-templates/thumb-emerald-gold.jpg'}", 'template descriptions'); const cardAnchor=" function cardHtml(){\n const cur=selected();"; const cardHelper=` function offerTemplateMini(id){\n if(id==='editorial-grid')return '';\n if(id==='midnight-glass')return '';\n if(id==='emerald-gold')return '';\n return '';\n }\n function cardHtml(){\n const cur=selected();`; runtime=once(runtime,cardAnchor,cardHelper,'global template mini helper'); runtime=once(runtime, "\"${esc(t.name)}\"", "
${offerTemplateMini(t.id)}
", 'global structural previews'); write('public/app-runtime.js',runtime); // ---- UX: sidebar icon uses the same CSS-mask system as every other navigation icon; no duplicate SVG/square. let ux=read('public/core/ux-fixes-v1764.js'); ux=once(ux," const VERSION='17.6.4';"," const VERSION='17.6.6';",'ux version'); const pickerOld=" function pickerHtml(){\n const current=activeTemplateId();return `
Оформление PDFШаблон закрепляется только за этим предложением.
${templateList().map(t=>``).join('')}
`;\n }"; const pickerNew=" function offerTemplateMini(id){\n if(id==='editorial-grid')return '';\n if(id==='midnight-glass')return '';\n if(id==='emerald-gold')return '';\n return '';\n }\n function pickerHtml(){\n const current=activeTemplateId();return `
Оформление PDFВыбранный макет закрепляется за этим предложением. Миниатюра повторяет структуру PDF.
${templateList().map(t=>``).join('')}
`;\n }"; ux=once(ux,pickerOld,pickerNew,'per-offer structural previews'); const cssOld=" header nav .nav-menu{display:inline-flex!important;align-items:center;gap:7px}.sun-v1764-menu-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}\n #new .catalog-head .sun-v1764-menu-action{display:inline-flex;align-items:center;gap:7px}.sun-v1764-menu-action .sun-v1764-menu-icon{width:17px;height:17px}\n .sun-v1764-offer-template{padding:12px 16px;border-bottom:1px solid #e7e9ec;background:#fbfbfc}.sun-v1764-offer-template-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.sun-v1764-offer-template-head b{color:#273746}.sun-v1764-offer-template-head small{display:block;color:#7a838c;margin-top:3px}.sun-v1764-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.sun-v1764-offer-template-grid button{border:1px solid #dfe3e7;background:#fff;border-radius:11px;padding:6px;text-align:left;color:#3f4d59}.sun-v1764-offer-template-grid button.on{border-color:var(--g);box-shadow:0 0 0 2px color-mix(in srgb,var(--g) 20%,transparent)}.sun-v1764-offer-template-grid img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:7px;background:#eef1f3}.sun-v1764-offer-template-grid span{display:block;padding:6px 3px 2px;font-size:11px;font-weight:800;line-height:1.2}"; const cssNew=` header nav .nav-menu{--sun-icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.9%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%2018h16M6%2017a6%206%200%200%201%2012%200M12%2011V8M10.5%208h3M3%2020h18%22%2F%3E%3C%2Fsvg%3E")!important;display:flex!important;align-items:center!important;gap:0!important}.sun-enterprise-sidebar header nav .nav-menu::before{background:var(--sy-accent,#dfb941)!important;opacity:1!important}.sun-enterprise-sidebar header nav .nav-menu>.sun-v1764-menu-icon{display:none!important}.sun-v1764-menu-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}\n #new .catalog-head .sun-v1764-menu-action{display:inline-flex;align-items:center;gap:7px}.sun-v1764-menu-action .sun-v1764-menu-icon{width:17px;height:17px}\n .sun-v1764-offer-template{padding:14px 16px;border-bottom:1px solid #e7e9ec;background:#fbfbfc}.sun-v1764-offer-template-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.sun-v1764-offer-template-head b{color:#273746}.sun-v1764-offer-template-head small{display:block;color:#7a838c;margin-top:3px}.sun-v1764-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px}.sun-v1764-offer-template-grid button{border:1px solid #dfe3e7;background:#fff;border-radius:12px;padding:7px;text-align:left;color:#3f4d59}.sun-v1764-offer-template-grid button.on{border-color:var(--g);box-shadow:0 0 0 2px color-mix(in srgb,var(--g) 20%,transparent)}.sun-v1764-offer-template-grid span{display:block;padding:7px 3px 2px;font-size:11px;font-weight:800;line-height:1.2}\n .sun-offer-template-mini{position:relative;display:block;width:100%;aspect-ratio:16/10;overflow:hidden;border-radius:8px;background:#fff;border:1px solid #e3e5e7;box-sizing:border-box}.sun-offer-template-mini i{position:absolute;display:block;border-radius:2px}.sun-offer-template-mini .mini-head{left:7%;right:7%;top:7%;height:6%;background:#c8b17a}.sun-offer-template-mini-light{background:#fbfaf6}.mini-light-hero{left:7%;right:7%;top:18%;height:24%;background:linear-gradient(90deg,#e9eee7 0 45%,#bdc8bb 45%)}.mini-light-row{left:7%;right:7%;height:10%;background:#eff1ed;border:1px solid #d8dcd5}.mini-light-row.a{top:47%}.mini-light-row.b{top:59%}.mini-light-row.c{top:71%}.mini-light-total{left:55%;right:7%;bottom:7%;height:12%;background:#d7e4d5}.sun-offer-template-mini-editorial-grid{background:#fbfaf6}.mini-editorial-hero{left:7%;top:18%;width:45%;height:35%;background:#dfe8df}.mini-editorial-stack{left:55%;right:7%;height:16%;background:#b8c9b7}.mini-editorial-stack.a{top:18%}.mini-editorial-stack.b{top:37%}.mini-editorial-grid{left:7%;right:7%;bottom:8%;height:32%;background:repeating-linear-gradient(90deg,#ece7dc 0 22%,transparent 22% 26%)}.sun-offer-template-mini-midnight-glass{background:#071621;border-color:#294a5b}.sun-offer-template-mini-midnight-glass .mini-head{background:#d5aa56}.mini-midnight-hero{left:7%;right:7%;top:18%;height:30%;background:linear-gradient(90deg,#12313b 0 57%,#31515b 57%)}.mini-midnight-total{left:7%;right:7%;top:53%;height:12%;background:#d5aa56}.mini-midnight-left{left:7%;width:54%;bottom:8%;height:22%;background:#0b2633;border:1px solid #315263}.mini-midnight-right{right:7%;width:27%;bottom:8%;height:22%;background:repeating-linear-gradient(0deg,#284856 0 30%,#102735 30% 34%)}.sun-offer-template-mini-emerald-gold{background:#061318;border-color:#314a4c}.sun-offer-template-mini-emerald-gold .mini-head{background:#d7ac55}.mini-emerald-hero{left:7%;right:7%;top:18%;height:38%;background:linear-gradient(90deg,#0c2d29 0 60%,#31534b 60%)}.mini-emerald-gold{left:10%;width:42%;top:43%;height:10%;background:#d7ac55}.mini-emerald-info{left:7%;right:57%;bottom:8%;height:27%;background:#0b2526;border:1px solid #67572f}.mini-emerald-menu{left:46%;right:7%;bottom:8%;height:27%;background:repeating-linear-gradient(0deg,#102c2d 0 17%,#67572f 17% 19%)}\n .sun-offer-template-option .sun-offer-template-mini{width:100%;aspect-ratio:16/9;border-radius:10px}.sun-offer-template-thumb.sun-offer-template-mini{object-fit:initial;height:auto}\n`; ux=once(ux,cssOld,cssNew,'template preview and menu icon CSS'); const decorateOld=" qa('header nav button.nav-menu,header nav button[data-nav-label=\\\"Меню\\\"]').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.innerHTML=`${menuSvg}Меню`;changed=true});"; const decorateNew=" qa('header nav button.nav-menu,header nav button[data-nav-label=\\\"Меню\\\"]').forEach(button=>{if(button.dataset.sunMenuIconV1766==='1'&&!button.querySelector('.sun-v1764-menu-icon'))return;button.dataset.sunMenuIconV1766='1';button.querySelectorAll('.sun-v1764-menu-icon').forEach(x=>x.remove());let label=button.querySelector('span');if(!label){label=document.createElement('span');button.textContent='';button.appendChild(label)}label.textContent='Меню';changed=true});"; ux=once(ux,decorateOld,decorateNew,'menu icon cleanup'); write('public/core/ux-fixes-v1764.js',ux); // ---- Loader and PWA cache/versioning. let perf=read('public/core/performance.js'); perf=once(perf," const VERSION='17.6.5';\n const RELEASE='20260908-v17-6-5-stability';",` const VERSION='${VERSION}';\n const RELEASE='${RELEASE}';`,'performance version'); write('public/core/performance.js',perf); let index=read('public/index.html'); index=index.split('20260908-v17-6-5-stability').join(RELEASE); write('public/index.html',index); let sw=read('public/service-worker.js'); sw=sw.replace(/const CACHE='sun-catering-pwa-v\d+-[^']+';\nconst VERSION='[^']+';/,`const CACHE='sun-catering-pwa-v71-${RELEASE}';\nconst VERSION='${RELEASE}';`); write('public/service-worker.js',sw); // ---- Package/release metadata. const pkg=JSON.parse(read('package.json'));pkg.version=VERSION;write('package.json',JSON.stringify(pkg,null,2)+'\n'); const lock=JSON.parse(read('package-lock.json'));lock.version=VERSION;if(lock.packages?.[''])lock.packages[''].version=VERSION;write('package-lock.json',JSON.stringify(lock,null,2)+'\n'); const manifestPath='docs/release-manifest.json';const manifest=JSON.parse(read(manifestPath));Object.assign(manifest,{version:'v17.6.6',channel:'production',pwaCache:`v71-${RELEASE}`,release:RELEASE,offerTemplates:4,offerTemplatePerOrder:true,offerTemplateStructuralPreviews:true,offerPdfDistinctLayouts:true,menuNavMaskFixed:true,notes:'Offer templates now keep the per-order selection through render/PDF and use four visibly different page compositions. Menu sidebar icon uses the native masked icon system without a duplicate square.'});write(manifestPath,JSON.stringify(manifest,null,2)+'\n'); write('docs/releases/V17.6.6-CHANGES.txt',`Caterium v17.6.6 — Proposal templates + Menu icon\nDate: 2026-09-08\n\n1. Removed the yellow square/duplicate icon next to Menu in the enterprise sidebar. Menu now uses the same 20px CSS-mask icon mechanism, spacing and accent color as the other navigation items.\n2. Per-order proposal template is preserved by the core renderer instead of being overwritten by the global template during render.\n3. Four templates now use deliberately different PDF section compositions:\n - Light: classic hero -> selling block -> menu -> summary -> info/pricing.\n - Editorial: hero -> menu first -> summary/pricing -> editorial story/info.\n - Midnight Glass: hero -> story -> summary/info -> menu -> pricing.\n - Emerald Gold: hero -> premium summary -> story/info/pricing -> menu as the final presentation section.\n4. Template selectors use structural mini-previews that mirror the corresponding PDF composition instead of relying on static screenshots/colors.\n5. PWA cache v71 / ${RELEASE}.\n`); // ---- Static release guard additions. Keep existing checks, append focused assertions. let releaseCheck=read('tests/release-check.mjs'); releaseCheck += `\ncheck(pkg.version==='17.6.6','package version is v17.6.6');\ncheck(index.includes('${RELEASE}'),'index loads v17.6.6 cache-busted assets');\ncheck(sw.includes('v71-${RELEASE}'),'service worker cache is v17.6.6');\ncheck(runtime.includes('explicitTemplate')&&runtime.includes("OFFER_TEMPLATE_IDS.has(explicitTemplate)"),'proposal renderer preserves explicit per-offer template');\ncheck(runtime.includes("if(id==='editorial-grid')")&&runtime.includes("else if(id==='midnight-glass')")&&runtime.includes("else if(id==='emerald-gold')"),'PDF renderer has distinct layout sequences for all four templates');\ncheck(ux.includes('sunMenuIconV1766')&&ux.includes('--sun-icon:url(')&&ux.includes('.sun-offer-template-mini-editorial-grid')&&ux.includes('.sun-offer-template-mini-midnight-glass')&&ux.includes('.sun-offer-template-mini-emerald-gold'),'Menu icon and structural template previews are installed');\n`; write('tests/release-check.mjs',releaseCheck); let spec=read('tests/app.spec.mjs'); spec += `\n\ntest('v17.6.6 Menu uses one native-sized sidebar icon without the yellow square', async ({ page }) => {\n await page.goto('/index.html',{waitUntil:'domcontentloaded'});\n await page.evaluate(()=>{\n let nav=document.querySelector('header nav');if(!nav){const h=document.createElement('header');nav=document.createElement('nav');h.appendChild(nav);document.body.prepend(h)}\n let b=nav.querySelector('.nav-menu');if(!b){b=document.createElement('button');b.className='sun-nav-button nav-menu';b.dataset.navLabel='Меню';b.textContent='Меню';nav.appendChild(b)}\n document.body.classList.add('sun-enterprise-sidebar');\n window.SunCloudV2=window.SunCloudV2||{getSession:()=>null};\n });\n const ux=fs.readFileSync(path.join(process.cwd(),'public','core','ux-fixes-v1764.js'),'utf8');await page.addScriptTag({content:ux});\n await page.waitForFunction(()=>Boolean(window.SunUXFixV1764));\n const r=await page.evaluate(()=>{const b=document.querySelector('header nav .nav-menu'),p=getComputedStyle(b,'::before');return {inline:b.querySelectorAll('.sun-v1764-menu-icon').length,mask:p.maskImage||p.webkitMaskImage,w:parseFloat(p.width),h:parseFloat(p.height),label:b.textContent.trim()}});\n expect(r.inline).toBe(0);expect(r.mask).not.toBe('none');expect(r.w).toBeLessThanOrEqual(22);expect(r.h).toBeLessThanOrEqual(22);expect(r.label).toContain('Меню');\n});\n\ntest('v17.6.6 proposal selector exposes four structurally different previews', async ({ page }) => {\n await page.goto('/index.html',{waitUntil:'domcontentloaded'});\n const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');\n expect(runtimeSource).toContain('data-template-mini');\n expect(runtimeSource).toContain('mini-editorial-hero');\n expect(runtimeSource).toContain('mini-midnight-total');\n expect(runtimeSource).toContain('mini-emerald-gold');\n expect(runtimeSource).toContain("OFFER_TEMPLATE_IDS.has(explicitTemplate)");\n});\n`; write('tests/app.spec.mjs',spec); console.log('Applied Caterium v17.6.6 proposal/menu patch');