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>
132 lines
26 KiB
JavaScript
132 lines
26 KiB
JavaScript
(()=>{
|
||
'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})};
|
||
})();
|