caterium-app/public/core/signature-offer-pdf-v18.js
pavlov346346-source b92b52e03e feat: add 6 new client-offer PDF templates, archive the old 13
New "signature" template pack (cream-elegance, neon-menu,
emerald-circles, midnight-checklist, gourmet-hero, diamond-gold)
replaces the visible template gallery in Settings. The previous 13
classic/archive templates keep rendering correctly for any existing
order that already used one, but are no longer selectable for new
orders — nothing was deleted.

Also adds a Settings option to upload a custom logo and company name
for PDF documents, replacing the hardcoded "Солнце Кейтеринг" branding
wherever a logo fails to load.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 18:27:23 +03:00

132 lines
26 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.

(()=>{
'use strict';
if(window.SunSignatureOfferPDFV18)return;
const VERSION='18.0.0';
const RELEASE='20260915-v18-0-0-signature-offer-pdf';
const W=1000,H=1414,SCALE=1.6,M=54;
const SIGNATURE_IDS=['cream-elegance','neon-menu','emerald-circles','midnight-checklist','gourmet-hero','diamond-gold'];
const SIGNATURE_SET=new Set(SIGNATURE_IDS);
const INNER_THEME={
'cream-elegance':'light',
'neon-menu':'midnight-glass',
'emerald-circles':'midnight-glass',
'midnight-checklist':'midnight-glass',
'gourmet-hero':'black-gold',
'diamond-gold':'black-gold'
};
const PALETTES={
'cream-elegance':{bg:'#faf3e6',paper:'#ffffff',ink:'#22281f',muted:'#726c5f',accent:'#8f8536',accent2:'#4f6a3f',deep:'#274a34',line:'#ecdfc4',foot:'#faf3e6'},
'neon-menu':{bg:'#070908',paper:'#0d1210',ink:'#eafff1',muted:'#8fa89a',accent:'#39e27a',accent2:'#c9a24a',deep:'#0f2318',line:'#1f4430',foot:'#070908'},
'emerald-circles':{bg:'#08181c',paper:'#0d2226',ink:'#eef6f2',muted:'#94aca6',accent:'#c9a24a',accent2:'#2f9e78',deep:'#0a2a26',line:'#1f4642',foot:'#08181c'},
'midnight-checklist':{bg:'#06170f',paper:'#0b2417',ink:'#eef6ee',muted:'#8fac97',accent:'#dfb957',accent2:'#3f8f5c',deep:'#123822',line:'#1f4a30',foot:'#06170f'},
'gourmet-hero':{bg:'#0a0a09',paper:'#141311',ink:'#f5f1e8',muted:'#a89f8e',accent:'#d7ac55',accent2:'#8a6a2c',deep:'#1c1a15',line:'#3a352a',foot:'#0a0a09'},
'diamond-gold':{bg:'#071510',paper:'#0d251c',ink:'#f0f5ee',muted:'#93ab9c',accent:'#e0b95c',accent2:'#2f9e6f',deep:'#123d29',line:'#204b34',foot:'#071510'}
};
const money=v=>Math.round(Number(v||0)).toLocaleString('ru-RU')+' ₽';
const classicBoxCount=s=>(s?.items||[]).filter(i=>[0,5].includes(Number(i?.categoryId))).reduce((sum,i)=>sum+Math.max(0,Number(i?.qty||0)),0);
const dateText=value=>{if(!value)return'';const d=new Date(String(value)+'T12:00:00');return Number.isNaN(d.getTime())?String(value):d.toLocaleDateString('ru-RU',{day:'numeric',month:'long',year:'numeric'})};
const plural=(n,a,b,c)=>{const x=Math.abs(Number(n)||0)%100,y=x%10;return x>10&&x<20?c:y===1?a:y>=2&&y<=4?b:c};
const safeImage=src=>/^data:image\/(?:png|jpe?g|webp);base64,/i.test(String(src||''));
function loadImage(src){return new Promise(resolve=>{if(!safeImage(src))return resolve(null);const im=new Image();let done=false;const finish=v=>{if(done)return;done=true;clearTimeout(timer);resolve(v)};const timer=setTimeout(()=>finish(null),3500);im.onload=()=>finish(im);im.onerror=()=>finish(null);im.src=String(src)})}
function roundRect(ctx,x,y,w,h,r,fill,stroke,width=1){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=width;ctx.stroke()}}
function wrap(ctx,text,maxWidth){const words=String(text||'').trim().split(/\s+/).filter(Boolean),lines=[];let line='';for(const word of words){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 text(ctx,value,x,y,width,lineHeight,{font='16px Arial',color='#111',align='left',maxLines=0}={}){ctx.save();ctx.font=font;ctx.fillStyle=color;ctx.textAlign=align;ctx.textBaseline='top';let lines=wrap(ctx,value,width);if(maxLines&&lines.length>maxLines){lines=lines.slice(0,maxLines);let last=lines[lines.length-1]||'';while(last&&ctx.measureText(last+'…').width>width)last=last.slice(0,-1);lines[lines.length-1]=last+'…'}lines.forEach((line,i)=>ctx.fillText(line,x,y+i*lineHeight));ctx.restore();return lines.length*lineHeight}
function line(ctx,x1,y1,x2,y2,color,width=1,dash=[]){ctx.save();ctx.strokeStyle=color;ctx.lineWidth=width;ctx.setLineDash(dash);ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();ctx.restore()}
function coverImage(ctx,img,x,y,w,h,r=24){if(!img)return;const s=Math.max(w/img.naturalWidth,h/img.naturalHeight),sw=w/s,sh=h/s,sx=(img.naturalWidth-sw)/2,sy=(img.naturalHeight-sh)/2;ctx.save();roundRect(ctx,x,y,w,h,r);ctx.clip();ctx.drawImage(img,sx,sy,sw,sh,x,y,w,h);ctx.restore()}
function circleImage(ctx,img,cx,cy,r){if(!img)return;const d=r*2,s=Math.max(d/img.naturalWidth,d/img.naturalHeight),sw=d/s,sh=d/s,sx=(img.naturalWidth-sw)/2,sy=(img.naturalHeight-sh)/2;ctx.save();ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.clip();ctx.drawImage(img,sx,sy,sw,sh,cx-r,cy-r,d,d);ctx.restore()}
function sun(ctx,cx,cy,r,color,rays=18){ctx.save();ctx.strokeStyle=color;ctx.fillStyle=color;ctx.lineWidth=2;for(let i=0;i<rays;i++){const a=i*Math.PI*2/rays,ri=r+8,ro=r+24+(i%2)*8;ctx.beginPath();ctx.moveTo(cx+Math.cos(a)*ri,cy+Math.sin(a)*ri);ctx.lineTo(cx+Math.cos(a)*ro,cy+Math.sin(a)*ro);ctx.stroke()}ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.fill();ctx.restore()}
function logoOrBrand(ctx,logo,p,{x=M,y=46,dark=false}={},brandName='Солнце Кейтеринг'){if(logo){const s=Math.min(190/logo.naturalWidth,64/logo.naturalHeight),dw=logo.naturalWidth*s,dh=logo.naturalHeight*s;ctx.drawImage(logo,x,y,dw,dh);return}sun(ctx,x+28,y+29,15,p.accent,16);const parts=String(brandName||'Солнце Кейтеринг').trim().split(/\s+/),line1=(parts[0]||'Солнце').toUpperCase(),line2=(parts.slice(1).join(' ')||'Кейтеринг').toUpperCase();text(ctx,line1,x+60,y+11,220,28,{font:'700 24px Arial',color:dark?'#fff':p.ink,maxLines:1});text(ctx,line2,x+61,y+41,180,18,{font:'11px Arial',color:p.accent,maxLines:1})}
function eventTitle(s){const event=String(s.event||'Персональное предложение').trim();return String(s.client||'').trim()?'Предложение для '+String(s.client).trim():event}
function footer(ctx,p,page,total,brandName='Солнце Кейтеринг'){line(ctx,M,H-48,W-M,H-48,p.line,1);text(ctx,String(brandName||'Солнце Кейтеринг')+' · предложение клиенту',M,H-34,500,16,{font:'10px Arial',color:p.muted,maxLines:1});text(ctx,String(page)+' / '+String(total),W-M,H-34,100,16,{font:'10px Arial',color:p.muted,align:'right',maxLines:1})}
function iconCircle(ctx,cx,cy,r,glyph,{fill,stroke,glyphColor}={}){roundRect(ctx,cx-r,cy-r,r*2,r*2,r,fill,stroke,1.4);text(ctx,glyph,cx-r,cy-9,r*2,20,{font:'700 15px Arial',color:glyphColor,align:'center',maxLines:1})}
function factChips(ctx,s,p,x,y,w,{dark=true}={}){const facts=[];if(s.date)facts.push(dateText(s.date));if(s.guests)facts.push(String(s.guests)+' '+plural(s.guests,'гость','гостя','гостей'));if(s.time)facts.push('Доставка '+String(s.time));const gap=8,cw=(w-gap*Math.max(0,facts.length-1))/Math.max(1,facts.length);facts.forEach((f,i)=>{const xx=x+i*(cw+gap);roundRect(ctx,xx,y,cw,44,13,dark?'rgba(255,255,255,.06)':p.paper,dark?'rgba(255,255,255,.2)':p.line);text(ctx,f,xx+cw/2,y+14,cw-14,17,{font:'12px Arial',color:dark?'#f4f1e6':p.ink,align:'center',maxLines:1})})}
function menuList(ctx,s,p,x,y,w,rowH,count,{dark=true,numbered=true}={}){const items=(s.items||[]).slice(0,count),gap=8;items.forEach((item,i)=>{const yy=y+i*(rowH+gap);roundRect(ctx,x,yy,w,rowH,13,dark?'rgba(255,255,255,.05)':p.paper,dark?'rgba(255,255,255,.14)':p.line);const badgeX=x+14;if(numbered){roundRect(ctx,badgeX,yy+rowH/2-13,26,26,13,p.accent,null);text(ctx,String(i+1),badgeX,yy+rowH/2-9,26,18,{font:'700 12px Arial',color:p.deep,align:'center',maxLines:1})}const textX=numbered?badgeX+38:x+16;text(ctx,item.name||'Позиция меню',textX,yy+10,w-(textX-x)-70,20,{font:dark?'600 13px Arial':'500 14px Georgia',color:dark?'#fff':p.ink,maxLines:1});const meta=[item.weight?String(item.weight):'',].filter(Boolean).join(' · ');if(meta)text(ctx,meta,textX,yy+rowH-24,w-(textX-x)-70,16,{font:'11px Arial',color:p.muted,maxLines:1});text(ctx,'× '+Math.round(Number(item.qty||1)),x+w-58,yy+rowH/2-9,50,18,{font:'700 12px Arial',color:p.accent,align:'right',maxLines:1})});return items.length*(rowH+gap)}
function photoGridSquare(ctx,s,p,x,y,w,h,cols,rows,{numbered=true}={}){const items=(s.items||[]).slice(0,cols*rows);const gap=7,cw=(w-gap*(cols-1))/cols,ch=(h-gap*(rows-1))/rows;return (async()=>{const imgs=await Promise.all(items.map(it=>loadImage(it.photoData||'')));imgs.forEach((img,i)=>{const col=i%cols,row=Math.floor(i/cols),xx=x+col*(cw+gap),yy=y+row*(ch+gap);roundRect(ctx,xx,yy,cw,ch,10,p.deep,null);if(img)coverImage(ctx,img,xx,yy,cw,ch,10);if(numbered){roundRect(ctx,xx+6,yy+6,22,22,11,'rgba(6,10,8,.72)',null);text(ctx,String(i+1),xx+6,yy+10,22,16,{font:'700 10px Arial',color:'#fff',align:'center',maxLines:1})}})})()}
function photoGridCircles(ctx,s,p,x,y,w,cols,r,{numbered=true}={}){const items=(s.items||[]).slice(0,cols*2);const gapX=(w-cols*r*2)/(cols-1||1);return (async()=>{const imgs=await Promise.all(items.map(it=>loadImage(it.photoData||'')));imgs.forEach((img,i)=>{const col=i%cols,row=Math.floor(i/cols),cx=x+r+col*(r*2+gapX),cy=y+r+row*(r*2+22);ctx.save();ctx.beginPath();ctx.arc(cx,cy,r+3,0,Math.PI*2);ctx.strokeStyle=p.accent;ctx.lineWidth=2;ctx.stroke();ctx.restore();if(img)circleImage(ctx,img,cx,cy,r);else{ctx.save();ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.fillStyle=p.deep;ctx.fill();ctx.restore()}if(numbered){roundRect(ctx,cx-13,cy+r-8,26,22,11,p.accent,null);text(ctx,String(i+1),cx-13,cy+r-4,26,16,{font:'700 10px Arial',color:p.deep,align:'center',maxLines:1})}})})()}
function statCards(ctx,s,p,x,y,w,h,cards,{dark=true}={}){const gap=10,cw=(w-gap*(cards.length-1))/cards.length;cards.forEach((c,i)=>{const xx=x+i*(cw+gap);roundRect(ctx,xx,y,cw,h,14,dark?'rgba(255,255,255,.05)':p.paper,dark?'rgba(255,255,255,.16)':p.line);text(ctx,c[1],xx+16,y+14,cw-32,17,{font:'700 10px Arial',color:p.muted,maxLines:1});text(ctx,String(c[0]),xx+16,y+38,cw-32,32,{font:'700 24px Arial',color:dark?'#fff':p.deep,maxLines:1})})}
function priceBand(ctx,s,p,x,y,w,h,{dark=true}={}){roundRect(ctx,x,y,w,h,16,p.accent,null);text(ctx,'ИТОГОВАЯ СТОИМОСТЬ',x+22,y+16,w-44,16,{font:'700 11px Arial',color:p.deep,maxLines:1});text(ctx,money(s.pricing?.total),x+22,y+40,w-44,42,{font:'700 32px Arial',color:p.deep,maxLines:1});if(s.guests)text(ctx,'на гостя: '+money((Number(s.pricing?.itemsTotal||s.pricing?.total||0))/Math.max(1,Number(s.guests))),x+22,y+h-26,w-44,18,{font:'12px Arial',color:p.deep,maxLines:1})}
function checklistCard(ctx,p,x,y,w,h,title,lines,{dark=true}={}){roundRect(ctx,x,y,w,h,16,dark?'rgba(255,255,255,.04)':p.paper,dark?'rgba(255,255,255,.14)':p.line);text(ctx,title,x+18,y+16,w-36,18,{font:'700 13px Arial',color:p.accent,maxLines:1});const cols=2,rowH=22,colW=(w-36)/cols;lines.forEach((t,i)=>{const col=i%cols,row=Math.floor(i/cols),xx=x+18+col*colW,yy=y+46+row*rowH;roundRect(ctx,xx,yy+2,14,14,7,p.accent,null);text(ctx,'✓',xx+2,yy+3,10,12,{font:'700 9px Arial',color:p.deep,align:'center',maxLines:1});text(ctx,t,xx+20,yy,colW-24,14,{font:'10px Arial',color:dark?'#eef':p.ink,maxLines:1})})}
function addonRow(ctx,p,x,y,w,h,items,{dark=true}={}){const gap=9,cw=(w-gap*(items.length-1))/items.length;items.forEach((t,i)=>{const xx=x+i*(cw+gap);roundRect(ctx,xx,y,cw,h,13,dark?'rgba(255,255,255,.04)':p.paper,dark?'rgba(255,255,255,.14)':p.line);roundRect(ctx,xx+cw/2-11,y+12,22,22,11,p.accent,null);text(ctx,'+',xx+cw/2-11,y+14,22,18,{font:'700 14px Arial',color:p.deep,align:'center',maxLines:1});text(ctx,t,xx+8,y+44,cw-16,14,{font:'10px Arial',color:dark?'#eef':p.ink,align:'center',maxLines:2})})}
const CHECK_LINES=['Доставка в фирменных боксах','Свежие продукты каждый день','Аккуратная подача и упаковка','Готовность к сервировке'];
const ADDON_LINES=['Напитки','Чайная станция','Официант','Текстиль'];
async function renderCover(s,id){
const p=PALETTES[id]||PALETTES['cream-elegance'],canvas=document.createElement('canvas');canvas.width=Math.round(W*SCALE);canvas.height=Math.round(H*SCALE);const ctx=canvas.getContext('2d',{alpha:false});ctx.setTransform(SCALE,0,0,SCALE,0,0);ctx.fillStyle=p.bg;ctx.fillRect(0,0,W,H);
const logo=await loadImage(s.logo||''),heroSrc=(s.items||[]).find(x=>safeImage(x.photoData||x.photo))?.photoData||(s.finalGallery||[]).find(safeImage)||'',hero=await loadImage(heroSrc),title=eventTitle(s),event=String(s.event||'Персональное предложение').trim(),dark=id!=='cream-elegance';
if(id==='cream-elegance'){
logoOrBrand(ctx,logo,p,{},s.brandName);text(ctx,'МЕРОПРИЯТИЕ',M,168,390,18,{font:'700 11px Arial',color:p.accent2,maxLines:1});text(ctx,title,M,198,405,52,{font:'500 40px Georgia',color:p.ink,maxLines:3});factChips(ctx,s,p,M,360,405,{dark:false});line(ctx,M,428,445,428,p.accent,1);text(ctx,'Меню составлено индивидуально для вашего мероприятия с учётом количества гостей и пожеланий.',M,452,395,24,{font:'14px Arial',color:p.muted,maxLines:4});if(hero)coverImage(ctx,hero,500,150,446,600,28);
text(ctx,'МЕНЮ',0,795,W,26,{font:'700 22px Georgia',color:p.deep,align:'center',maxLines:1});line(ctx,M,812,410,812,p.line,1);line(ctx,590,812,W-M,812,p.line,1);
const cats=[['Рыбные закуски','🐟'],['Мясные закуски','🥩'],['Для вегетарианцев','🌿'],['Мини-салаты','🥗']];const colW=(W-2*M-14)/2;cats.forEach((c,i)=>{const col=i%2,row=Math.floor(i/2),xx=M+col*(colW+14),yy=845+row*88;roundRect(ctx,xx,yy,colW,76,16,p.paper,p.line);iconCircle(ctx,xx+40,yy+38,20,'',{fill:p.deep,stroke:null});text(ctx,c[0],xx+72,yy+22,colW-90,18,{font:'600 14px Arial',color:p.ink,maxLines:1});text(ctx,'Собрано под ваше событие',xx+72,yy+44,colW-90,14,{font:'11px Arial',color:p.muted,maxLines:1})});
await photoGridSquare(ctx,s,p,M,1035,W-2*M,100,6,1,{numbered:false});
priceBand(ctx,s,p,640,1150,306,155,{dark:false});statCards(ctx,s,p,M,1150,565,70,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:false});
}else if(id==='neon-menu'){
ctx.save();roundRect(ctx,20,20,W-40,H-40,0,null,p.accent,1.4);ctx.restore();logoOrBrand(ctx,logo,p,{x:M,y:52,dark:true},s.brandName);text(ctx,'8 АВГУСТА',M,150,240,16,{font:'700 11px Arial',color:p.accent,maxLines:1});roundRect(ctx,M,140,150,34,17,'rgba(57,226,122,.12)',p.accent,1);text(ctx,dateText(s.date)||'Дата уточняется',M+14,150,200,16,{font:'700 10px Arial',color:p.accent,maxLines:1});text(ctx,title,M,192,430,60,{font:'700 38px Arial',color:'#fff',maxLines:3});text(ctx,event,M,340,400,26,{font:'14px Arial',color:p.muted,maxLines:2});if(hero)coverImage(ctx,hero,520,140,426,300,20);if(s.guests){roundRect(ctx,830,390,116,58,16,p.deep,p.accent,1.4);text(ctx,String(s.guests),830,402,116,26,{font:'700 20px Arial',color:p.accent,align:'center',maxLines:1});text(ctx,'ГОСТЕЙ',830,432,116,14,{font:'700 9px Arial',color:p.muted,align:'center',maxLines:1})}
const qual=['Свежие ингредиенты','Авторские рецепты','Эстетичная подача','Контроль качества'];qual.forEach((t,i)=>{const xx=M+i*228;roundRect(ctx,xx,470,212,60,14,'rgba(57,226,122,.05)',p.line);text(ctx,t,xx+12,494,188,16,{font:'11px Arial',color:p.accent,align:'center',maxLines:2})});
const listH=menuList(ctx,s,p,M,560,470,64,5,{dark:true});await photoGridSquare(ctx,s,p,540,560,406,470,3,4,{numbered:true});
text(ctx,'НАШИ ЗАКУСКИ',540,536,406,16,{font:'700 12px Arial',color:p.accent,align:'center',maxLines:1});
statCards(ctx,s,p,M,1060,306,80,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true});priceBand(ctx,s,p,376,1060,570,80,{dark:true});
checklistCard(ctx,p,M,1160,470,150,'ВСЁ ПОД КОНТРОЛЕМ',CHECK_LINES,{dark:true});addonRow(ctx,p,540,1160,406,150,ADDON_LINES,{dark:true});
}else if(id==='emerald-circles'){
logoOrBrand(ctx,logo,p,{x:M,y:52,dark:true},s.brandName);roundRect(ctx,M,140,260,34,17,'rgba(201,162,74,.12)',p.accent,1);text(ctx,'ИНДИВИДУАЛЬНОЕ ПРЕДЛОЖЕНИЕ',M+14,150,240,14,{font:'700 9px Arial',color:p.accent,maxLines:1});text(ctx,title,M,192,430,58,{font:'500 38px Georgia',color:'#fff',maxLines:3});text(ctx,'Составим идеальное гастрономическое решение для вашего мероприятия.',M,350,410,22,{font:'13px Arial',color:p.muted,maxLines:3});
const feats=['Разнообразие блюд','Свежие ингредиенты','Премиальное качество'];feats.forEach((t,i)=>{const xx=M+i*145;roundRect(ctx,xx,430,132,48,13,'rgba(255,255,255,.05)',p.line);text(ctx,t,xx+10,446,112,14,{font:'10px Arial',color:p.accent2,align:'center',maxLines:2})});
menuList(ctx,s,p,M,510,470,66,5,{dark:true});await photoGridCircles(ctx,s,p,565,520,380,3,58,{numbered:true});
statCards(ctx,s,p,M,940,W-2*M,78,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ ',],[s.guests||'—','ГОСТЕЙ']],{dark:true});priceBand(ctx,s,p,M,1032,W-2*M,90,{dark:true});
checklistCard(ctx,p,M,1140,470,170,'ВСЁ ПОД КОНТРОЛЕМ КОМАНДЫ СОЛНЦА',CHECK_LINES,{dark:true});addonRow(ctx,p,540,1140,406,170,ADDON_LINES,{dark:true});
}else if(id==='midnight-checklist'){
logoOrBrand(ctx,logo,p,{x:M,y:48,dark:true},s.brandName);if(s.client)text(ctx,String(s.client),W-M,58,300,20,{font:'700 15px Arial',color:'#fff',align:'right',maxLines:1});const metaLine=[dateText(s.date),s.guests?String(s.guests)+' гостей':''].filter(Boolean).join(' · ');if(metaLine)text(ctx,metaLine,W-M,84,300,16,{font:'11px Arial',color:p.muted,align:'right',maxLines:1});
roundRect(ctx,M,120,W-2*M,60,14,'rgba(255,255,255,.04)',p.line);text(ctx,'Меню составлено из расчёта количества гостей. Все позиции приедут готовыми к подаче на стол.',M+20,138,W-2*M-40,20,{font:'12px Arial',color:p.muted,maxLines:2});
text(ctx,'МЕНЮ',M,215,300,24,{font:'700 20px Arial',color:'#fff',maxLines:1});menuList(ctx,s,p,M,255,W-2*M,58,8,{dark:true});
await photoGridSquare(ctx,s,p,M,860,W-2*M,190,5,2,{numbered:false});
statCards(ctx,s,p,M,1075,470,74,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true});priceBand(ctx,s,p,540,1075,406,74,{dark:true});
checklistCard(ctx,p,M,1170,470,150,'ВСЁ ПОД КОНТРОЛЕМ КОМАНДЫ СОЛНЦА',CHECK_LINES,{dark:true});addonRow(ctx,p,540,1170,406,150,ADDON_LINES,{dark:true});
}else if(id==='gourmet-hero'){
logoOrBrand(ctx,logo,p,{x:W-M-190,y:44,dark:true},s.brandName);text(ctx,'КЕЙТЕРИНГ ДЛЯ',M,90,420,60,{font:'700 46px Arial',color:'#fff',maxLines:1});text(ctx,'ВАШЕГО СОБЫТИЯ',M,150,420,60,{font:'700 46px Arial',color:'#fff',maxLines:1});text(ctx,'Индивидуальное меню, безупречная подача и сервис, о котором будут говорить ваши гости.',M,235,400,24,{font:'14px Arial',color:p.muted,maxLines:3});if(hero)coverImage(ctx,hero,520,60,426,340,18);roundRect(ctx,520,420,240,42,12,p.paper,p.line);text(ctx,String((s.items||[]).length)+' блюд в меню',540,432,220,18,{font:'700 12px Arial',color:p.accent,maxLines:1});
const tabs=['РЫБНЫЕ','МЯСНЫЕ','ВЕГЕТАРИАНСКИЕ','САЛАТЫ','ДЕСЕРТЫ'];let tx=M;tabs.forEach((t,i)=>{const w2=text(ctx,t,tx,505,200,16,{font:'700 11px Arial',color:i===0?'#fff':p.muted,maxLines:1});if(i===0)line(ctx,tx,528,tx+70,528,p.accent,2);tx+=90});
const featured=(s.items||[]).slice(0,3);const cardW=(W-2*M-2*14)/3;
(async()=>{const imgs=await Promise.all(featured.map(it=>loadImage(it.photoData||'')));featured.forEach((it,i)=>{const xx=M+i*(cardW+14);roundRect(ctx,xx,555,cardW,230,16,p.paper,p.line);if(imgs[i])coverImage(ctx,imgs[i],xx,555,cardW,150,16);text(ctx,String(i+1).padStart(2,'0'),xx+14,715,40,16,{font:'700 12px Arial',color:p.accent,maxLines:1});text(ctx,it.name||'Позиция меню',xx+14,735,cardW-28,20,{font:'600 13px Arial',color:'#fff',maxLines:2});if(it.weight)text(ctx,String(it.weight),xx+14,cardW>0?765:765,cardW-28,14,{font:'10px Arial',color:p.muted,maxLines:1})})})();
roundRect(ctx,M,822,W-2*M,52,14,null,p.accent,1.4);text(ctx,'СМОТРЕТЬ ПОЛНОЕ МЕНЮ →',0,838,W,18,{font:'700 12px Arial',color:p.accent,align:'center',maxLines:1});
statCards(ctx,s,p,M,910,306,100,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true});priceBand(ctx,s,p,376,910,306,100,{dark:true});checklistCard(ctx,p,698,910,248,100,'ДОСТАВКА',CHECK_LINES.slice(0,2),{dark:true});
addonRow(ctx,p,M,1045,W-2*M,150,ADDON_LINES,{dark:true});
}else if(id==='diamond-gold'){
logoOrBrand(ctx,logo,p,{x:M,y:50,dark:true},s.brandName);if(s.guests){roundRect(ctx,W-M-140,50,140,34,17,'rgba(255,255,255,.06)',p.line);text(ctx,String(s.guests)+' гостей',W-M-140,58,140,16,{font:'700 11px Arial',color:'#fff',align:'center',maxLines:1})}
text(ctx,'ИНДИВИДУАЛЬНОЕ МЕНЮ',M,150,420,60,{font:'700 34px Arial',color:'#fff',maxLines:2});text(ctx,'для вашего мероприятия',M,196,420,26,{font:'400 20px Georgia',color:p.accent,maxLines:1});text(ctx,'Мы создаём гастрономические впечатления, которые запоминаются.',M,240,400,24,{font:'13px Arial',color:p.muted,maxLines:3});
const feats=['Индивидуальный подход','Только свежие ингредиенты','Разнообразие вкусов','Сделано с любовью'];feats.forEach((t,i)=>{const xx=M+(i%2)*220,yy=330+Math.floor(i/2)*46;roundRect(ctx,xx,yy,205,38,11,'rgba(255,255,255,.04)',p.line);text(ctx,t,xx+10,yy+12,185,14,{font:'10px Arial',color:p.accent2,maxLines:1})});
menuList(ctx,s,p,M,440,470,56,5,{dark:true});
text(ctx,'ПРИМЕРЫ ЗАКУСОК',540,428,406,16,{font:'700 12px Arial',color:p.accent,maxLines:1});await photoGridSquare(ctx,s,p,540,455,406,300,3,2,{numbered:true});
statCards(ctx,s,p,M,900,306,90,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true});priceBand(ctx,s,p,376,900,306,90,{dark:true});
const dgx=698,dgy=900;roundRect(ctx,dgx,dgy,248,90,16,'rgba(255,255,255,.04)',p.line);ctx.save();ctx.translate(dgx+124,dgy+45);ctx.rotate(Math.PI/4);ctx.fillStyle=p.accent;ctx.fillRect(-16,-16,32,32);ctx.restore();text(ctx,'СКИДКА ВКЛЮЧЕНА',dgx,dgy+70,248,14,{font:'700 9px Arial',color:p.muted,align:'center',maxLines:1});
checklistCard(ctx,p,M,1010,470,170,'ДОСТАВКА И КОНТРОЛЬ КАЧЕСТВА',CHECK_LINES,{dark:true});addonRow(ctx,p,540,1010,406,170,ADDON_LINES.concat(['Посуда','Мебель']),{dark:true});
}
canvas.dataset.sunSignatureTemplate=id;canvas.dataset.sunSignatureCover='1';return canvas;
}
function correctPageNumbers(pages,start,total,innerId){const foot=(PALETTES[innerId]||{}).foot||'#fff';pages.forEach((canvas,i)=>{try{const scale=canvas.width/W,ctx=canvas.getContext('2d');ctx.save();ctx.setTransform(scale,0,0,scale,0,0);ctx.fillStyle=foot;ctx.fillRect(W-190,H-50,150,40);text(ctx,String(start+i)+' / '+String(total),W-M,H-34,100,16,{font:'10px Arial',color:'#888',align:'right',maxLines:1});ctx.restore()}catch(_){}})}
async function renderPages(snapshot,baseRenderer){
if(typeof baseRenderer!=='function')throw new Error('Base PDF renderer is unavailable');
const id=String(snapshot?.offerTemplateId||'');
if(!SIGNATURE_SET.has(id))return baseRenderer(snapshot);
const innerId=INNER_THEME[id]||'light',inner={...snapshot,offerTemplateId:innerId};
const [cover,basePages]=await Promise.all([renderCover(snapshot,id),baseRenderer(inner)]),pages=[cover,...(basePages||[])],total=pages.length;
try{const ctx=cover.getContext('2d'),p=PALETTES[id]||PALETTES['cream-elegance'];ctx.save();ctx.setTransform(SCALE,0,0,SCALE,0,0);footer(ctx,p,1,total,snapshot?.brandName);ctx.restore()}catch(_){}
correctPageNumbers(basePages||[],2,total,innerId);
return pages;
}
function installPreviewStyles(){if(document.getElementById('sunSignatureOfferPreviewV18'))return;const style=document.createElement('style');style.id='sunSignatureOfferPreviewV18';style.textContent=`
.sun-offer-template-mini[data-template-mini="cream-elegance"] i,.sun-offer-template-mini[data-template-mini="neon-menu"] i,.sun-offer-template-mini[data-template-mini="emerald-circles"] i,.sun-offer-template-mini[data-template-mini="midnight-checklist"] i,.sun-offer-template-mini[data-template-mini="gourmet-hero"] i,.sun-offer-template-mini[data-template-mini="diamond-gold"] i{display:none!important}
.sun-offer-template-mini[data-template-mini="cream-elegance"]{background:radial-gradient(circle at 78% 30%,#fff 0 18%,#8f8536 19% 21%,transparent 22%),linear-gradient(#fdf7ea,#f2e3bf)!important}
.sun-offer-template-mini[data-template-mini="neon-menu"]{background:linear-gradient(90deg,rgba(57,226,122,.35),transparent 45%),linear-gradient(135deg,#0d1210,#070908)!important;border-color:#1f4430!important}
.sun-offer-template-mini[data-template-mini="emerald-circles"]{background:radial-gradient(circle at 70% 55%,#c9a24a 0 6%,transparent 7%),radial-gradient(circle at 85% 55%,#c9a24a 0 6%,transparent 7%),linear-gradient(135deg,#0d2226,#08181c)!important;border-color:#1f4642!important}
.sun-offer-template-mini[data-template-mini="midnight-checklist"]{background:repeating-linear-gradient(0deg,#0b2417 0 11%,#123822 11% 12%),#06170f!important;border-color:#1f4a30!important}
.sun-offer-template-mini[data-template-mini="gourmet-hero"]{background:linear-gradient(90deg,#141311 0 55%,#3a352a 55% 58%,#0a0a09 58%)!important;border-color:#3a352a!important}
.sun-offer-template-mini[data-template-mini="diamond-gold"]{background:radial-gradient(circle at 75% 35%,#e0b95c 0 5%,transparent 6%),linear-gradient(135deg,#123d29,#071510)!important;border-color:#204b34!important}
`;document.head.appendChild(style)}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',installPreviewStyles,{once:true});else installPreviewStyles();
window.SunSignatureOfferPDFV18={VERSION,RELEASE,SIGNATURE_IDS:[...SIGNATURE_IDS],renderPages,renderCover,checks:()=>({version:VERSION,signature:SIGNATURE_IDS.length})};
})();