caterium-app/public/core/signature-offer-pdf-v18.js
pavlov346346-source 44bf5e9af1 feat: add 2 new templates modeled on the referenced menu.furset.ru design
User pointed at https://menu.furset.ru/view/23855347 as a design they
like and asked for a couple more templates in that direction. Added:

- premium-dark: near-black background, warm gold accents, real
  line-drawn icon badges (calendar/people/box -- not emoji) for the
  date/guests/boxes stats, bullet-point value props under the title,
  minimal flat menu list -- closely matching that reference's visual
  language instead of the card-heavy style of the existing templates.
- premium-emerald: the same layout in an emerald-green palette, as
  the second "couple" of designs requested, sharing the cover code
  with premium-dark and only differing by PALETTES entry.

Both plug into the existing shared renderContentPages engine, so they
get proper multi-page menu + pricing pages for free, consistent with
all 6 existing signature templates. New icon-drawing helpers
(iconBadge/iconCalendar/iconPeople/iconGauge/iconBox, statBadgeRow,
bulletList) live in the shared helper section for reuse.

Verified locally: both render in <70ms combined, produce 3 pages each
for a normal order, and hold up under the same adversarial-data pass
used for the other 6 (long names, 350 guests, seven-figure price,
item with no photo/price/category) -- no overlap, no crashes, generous
ellipsis truncation throughout. Fixed one real bug found while
testing: the date value in the stat badge ("8 августа 2026 г.") was
getting ellipsis-truncated at font-size 20px; reduced to 16px so it
fits cleanly.

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

286 lines
39 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.1.0';
const RELEASE='20260916-v18-1-0-signature-offer-pdf-polish';
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','premium-dark','premium-emerald'];
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',
'premium-dark':'black-gold',
'premium-emerald':'midnight-glass'
};
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'},
'premium-dark':{bg:'#0a0d12',paper:'#12161d',ink:'#f5f6f8',muted:'#8b93a1',accent:'#e0a545',accent2:'#c9863a',deep:'#171c24',line:'#252b35',foot:'#0a0d12'},
'premium-emerald':{bg:'#0a1210',paper:'#121d19',ink:'#f2f8f5',muted:'#8fa79c',accent:'#3fcf8e',accent2:'#2a9d6c',deep:'#17241f',line:'#233830',foot:'#0a1210'}
};
// Distinctive Cyrillic-friendly type pairing per polished template (falls back to Georgia/Arial elsewhere).
const FONT_STACKS={
'cream-elegance':{display:'"Playfair Display",Georgia,serif',label:'"Montserrat",Arial,sans-serif'},
'neon-menu':{display:'"Unbounded",Arial,sans-serif',label:'"Manrope",Arial,sans-serif'},
'emerald-circles':{display:'"Playfair Display",Georgia,serif',label:'"Montserrat",Arial,sans-serif'},
'midnight-checklist':{display:'"Unbounded",Arial,sans-serif',label:'"Manrope",Arial,sans-serif'},
'gourmet-hero':{display:'"Unbounded",Arial,sans-serif',label:'"Manrope",Arial,sans-serif'},
'diamond-gold':{display:'"Playfair Display",Georgia,serif',label:'"Montserrat",Arial,sans-serif'},
'premium-dark':{display:'"Unbounded",Arial,sans-serif',label:'"Manrope",Arial,sans-serif'},
'premium-emerald':{display:'"Unbounded",Arial,sans-serif',label:'"Manrope",Arial,sans-serif'}
};
function fontsFor(id){return FONT_STACKS[id]||{display:'Georgia,serif',label:'Arial,sans-serif'}}
function ensureFontLink(){
if(document.getElementById('sunSignatureFontsV18'))return;
const link=document.createElement('link');link.id='sunSignatureFontsV18';link.rel='stylesheet';
link.href='https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600;700&family=Montserrat:wght@500;600;700;800&family=Unbounded:wght@600;700;800&family=Manrope:wght@500;600;700;800&display=swap';
document.head.appendChild(link);
}
ensureFontLink();
async function ensureFontsReady(id){
const f=FONT_STACKS[id];if(!f||!document.fonts)return;
try{
await Promise.all([
document.fonts.load(`700 40px ${f.display}`),
document.fonts.load(`600 16px ${f.label}`),
document.fonts.load(`800 16px ${f.label}`)
]);
}catch(_){}
}
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||'#111';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,fonts}={},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}const df=fonts?.display||'Arial',lf=fonts?.label||'Arial';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 23px ${df}`,color:dark?'#fff':p.ink,maxLines:1});text(ctx,line2,x+61,y+42,180,18,{font:`600 11px ${lf}`,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='Солнце Кейтеринг',fonts){const lf=fonts?.label||'Arial';line(ctx,M,H-48,W-M,H-48,p.line,1);text(ctx,String(brandName||'Солнце Кейтеринг')+' · предложение клиенту',M,H-34,500,16,{font:`500 10px ${lf}`,color:p.muted,maxLines:1});text(ctx,String(page)+' / '+String(total),W-M,H-34,100,16,{font:`500 10px ${lf}`,color:p.muted,align:'right',maxLines:1})}
function iconCircle(ctx,cx,cy,r,glyph,{fill,stroke,glyphColor='#fff',size=20}={}){roundRect(ctx,cx-r,cy-r,r*2,r*2,r,fill,stroke,1.4);text(ctx,glyph,cx,cy-size/2-1,r*2,size+4,{font:`${size}px Arial`,color:glyphColor,align:'center',maxLines:1})}
// Small line-drawn icon badges (calendar/people/gauge/box) for the "premium" templates, so those stat cards use real iconography instead of emoji.
function iconBadge(ctx,x,y,size,p,draw){roundRect(ctx,x,y,size,size,size*0.28,p.deep,p.accent,1.4);ctx.save();ctx.translate(x+size/2,y+size/2);ctx.strokeStyle=p.accent;ctx.fillStyle=p.accent;ctx.lineWidth=Math.max(1.6,size*0.05);ctx.lineCap='round';ctx.lineJoin='round';draw(ctx,size*0.32);ctx.restore()}
function iconCalendar(ctx,r){roundRect(ctx,-r,-r*0.75,r*2,r*1.65,r*0.22,null,ctx.strokeStyle,ctx.lineWidth);ctx.beginPath();ctx.moveTo(-r,-r*0.3);ctx.lineTo(r,-r*0.3);ctx.stroke();ctx.beginPath();ctx.moveTo(-r*0.5,-r*1.05);ctx.lineTo(-r*0.5,-r*0.55);ctx.stroke();ctx.beginPath();ctx.moveTo(r*0.5,-r*1.05);ctx.lineTo(r*0.5,-r*0.55);ctx.stroke()}
function iconPeople(ctx,r){ctx.beginPath();ctx.arc(0,-r*0.4,r*0.4,0,Math.PI*2);ctx.stroke();ctx.beginPath();ctx.arc(0,r*0.75,r*0.82,Math.PI,0,true);ctx.stroke()}
function iconGauge(ctx,r){ctx.beginPath();ctx.arc(0,r*0.15,r*0.85,Math.PI,0);ctx.stroke();ctx.beginPath();ctx.moveTo(0,r*0.15);ctx.lineTo(r*0.48,-r*0.32);ctx.stroke();ctx.beginPath();ctx.arc(0,r*0.15,r*0.1,0,Math.PI*2);ctx.fill()}
function iconBox(ctx,r){roundRect(ctx,-r,-r*0.75,r*2,r*1.55,r*0.16,null,ctx.strokeStyle,ctx.lineWidth);ctx.beginPath();ctx.moveTo(-r,-r*0.05);ctx.lineTo(r,-r*0.05);ctx.stroke();ctx.beginPath();ctx.moveTo(0,-r*0.75);ctx.lineTo(0,r*0.8);ctx.stroke()}
function statBadgeRow(ctx,p,f,x,y,w,h,cards){const gap=14,cw=(w-gap*(cards.length-1))/cards.length;const bs=Math.min(52,h*0.42);cards.forEach(([draw,value,label],i)=>{const xx=x+i*(cw+gap);roundRect(ctx,xx,y,cw,h,20,p.paper,p.line,1);iconBadge(ctx,xx+18,y+18,bs,p,draw);text(ctx,value,xx+18+bs+10,y+18+bs/2-12,cw-18-bs-10-12,24,{font:`700 16px ${f.display}`,color:'#fff',maxLines:1});text(ctx,label,xx+18,y+h-30,cw-36,16,{font:`500 12px ${f.label}`,color:p.muted,maxLines:1})})}
function bulletList(ctx,p,f,x,y,w,lineH,items,color){let yy=y;items.forEach(t=>{ctx.save();ctx.fillStyle=p.accent;ctx.beginPath();ctx.arc(x+4,yy+9,4,0,Math.PI*2);ctx.fill();ctx.restore();const used=text(ctx,t,x+20,yy,w-20,18,{font:`500 14px ${f.label}`,color:color||p.ink,maxLines:2});yy+=Math.max(lineH,used+6)});return yy-y}
function factChips(ctx,s,p,x,y,w,{dark=true,fonts}={}){const lf=fonts?.label||'Arial';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:`600 12px ${lf}`,color:dark?'#f4f1e6':p.ink,align:'center',maxLines:1})})}
function menuList(ctx,s,p,x,y,w,rowH,count,{dark=true,numbered=true,fonts}={}){const df=fonts?.display||'Georgia',lf=fonts?.label||'Arial';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+13,yy+rowH/2-9,26,18,{font:`700 12px ${lf}`,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:`600 14px ${df}`,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:`500 11px ${lf}`,color:p.muted,maxLines:1});text(ctx,'× '+Math.round(Number(item.qty||1)),x+w-16,yy+rowH/2-9,50,18,{font:`700 12px ${lf}`,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,fonts}={}){const lf=fonts?.label||'Arial';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+17,yy+10,22,16,{font:`700 10px ${lf}`,color:'#fff',align:'center',maxLines:1})}})})()}
function photoGridCircles(ctx,s,p,x,y,w,cols,r,{numbered=true,fonts}={}){const lf=fonts?.label||'Arial';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,cy+r-4,26,16,{font:`700 10px ${lf}`,color:p.deep,align:'center',maxLines:1})}})})()}
function statCards(ctx,s,p,x,y,w,h,cards,{dark=true,fonts}={}){const lf=fonts?.label||'Arial';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 ${lf}`,color:p.muted,maxLines:1});text(ctx,String(c[0]),xx+16,y+38,cw-32,32,{font:`800 24px ${lf}`,color:dark?'#fff':p.deep,maxLines:1})})}
function priceBand(ctx,s,p,x,y,w,h,{dark=true,fonts}={}){
const df=fonts?.display||'Arial',lf=fonts?.label||'Arial';
const perGuest=s.guests?money((Number(s.pricing?.itemsTotal||s.pricing?.total||0))/Math.max(1,Number(s.guests))):'';
if(dark){
roundRect(ctx,x,y,w,h,16,p.accent,null);
text(ctx,'ИТОГОВАЯ СТОИМОСТЬ',x+22,y+16,w-44,16,{font:`700 11px ${lf}`,color:p.deep,maxLines:1});
text(ctx,money(s.pricing?.total),x+22,y+38,w-44,44,{font:`700 32px ${df}`,color:p.deep,maxLines:1});
if(perGuest)text(ctx,'на гостя: '+perGuest,x+22,y+h-26,w-44,18,{font:`500 12px ${lf}`,color:p.deep,maxLines:1});
}else{
roundRect(ctx,x,y,w,h,16,p.paper,p.accent,2);
text(ctx,'ИТОГОВАЯ СТОИМОСТЬ',x+22,y+18,w-44,16,{font:`700 11px ${lf}`,color:p.accent2,maxLines:1});
text(ctx,money(s.pricing?.total),x+22,y+38,w-44,46,{font:`700 34px ${df}`,color:p.deep,maxLines:1});
if(perGuest)text(ctx,'на гостя: '+perGuest,x+22,y+h-25,w-44,18,{font:`500 12px ${lf}`,color:p.muted,maxLines:1});
}
}
function checklistCard(ctx,p,x,y,w,h,title,lines,{dark=true,fonts}={}){const lf=fonts?.label||'Arial';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 ${lf}`,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+7,yy+3,10,12,{font:`700 9px ${lf}`,color:p.deep,align:'center',maxLines:1});text(ctx,t,xx+20,yy,colW-24,14,{font:`500 10px ${lf}`,color:dark?'#eef':p.ink,maxLines:1})})}
function addonRow(ctx,p,x,y,w,h,items,{dark=true,fonts}={}){const lf=fonts?.label||'Arial';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,y+14,22,18,{font:`700 14px ${lf}`,color:p.deep,align:'center',maxLines:1});text(ctx,t,xx+cw/2,y+44,cw-16,14,{font:`600 10px ${lf}`,color:dark?'#eef':p.ink,align:'center',maxLines:2})})}
const CHECK_LINES=['Доставка в фирменных боксах','Свежие продукты каждый день','Аккуратная подача и упаковка','Готовность к сервировке'];
const ADDON_LINES=['Напитки','Чайная станция','Официант','Текстиль'];
async function renderCover(s,id){
await ensureFontsReady(id);
const p=PALETTES[id]||PALETTES['cream-elegance'],f=fontsFor(id),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();
if(id==='cream-elegance'){
logoOrBrand(ctx,logo,p,{fonts:f},s.brandName);
text(ctx,'МЕРОПРИЯТИЕ',M,168,390,18,{font:`700 11px ${f.label}`,color:p.accent2,maxLines:1});
text(ctx,title,M,198,405,52,{font:`600 40px ${f.display}`,color:p.ink,maxLines:2});
factChips(ctx,s,p,M,372,405,{dark:false,fonts:f});
line(ctx,M,440,445,440,p.accent,1);
text(ctx,'Меню составлено индивидуально для вашего мероприятия с учётом количества гостей и пожеланий.',M,464,395,24,{font:`500 14px ${f.label}`,color:p.muted,maxLines:4});
if(hero)coverImage(ctx,hero,500,150,446,600,28);
text(ctx,'МЕНЮ',W/2,795,W,26,{font:`600 24px ${f.display}`,color:p.deep,align:'center',maxLines:1});
line(ctx,M,814,410,814,p.line,1);line(ctx,590,814,W-M,814,p.line,1);
await photoGridSquare(ctx,s,p,M,850,W-2*M,300,3,2,{numbered:false,fonts:f});
statCards(ctx,s,p,M,1170,565,100,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:false,fonts:f});
priceBand(ctx,s,p,640,1170,306,100,{dark:false,fonts:f});
}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,fonts:f},s.brandName);
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 ${f.label}`,color:p.accent,maxLines:1});
text(ctx,title,M,192,430,60,{font:`700 34px ${f.display}`,color:'#fff',maxLines:3});
text(ctx,event,M,340,400,26,{font:`500 14px ${f.label}`,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),888,402,116,26,{font:`700 20px ${f.display}`,color:p.accent,align:'center',maxLines:1});text(ctx,'ГОСТЕЙ',888,432,116,14,{font:`700 9px ${f.label}`,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+106,494,188,16,{font:`600 11px ${f.label}`,color:p.accent,align:'center',maxLines:2})});
menuList(ctx,s,p,M,560,470,64,5,{dark:true,fonts:f});
await photoGridSquare(ctx,s,p,540,560,406,470,3,4,{numbered:true,fonts:f});
text(ctx,'НАШИ ЗАКУСКИ',743,536,406,16,{font:`700 13px ${f.display}`,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,fonts:f});
priceBand(ctx,s,p,376,1060,570,80,{dark:true,fonts:f});
checklistCard(ctx,p,M,1160,470,150,'ВСЁ ПОД КОНТРОЛЕМ',CHECK_LINES,{dark:true,fonts:f});
addonRow(ctx,p,540,1160,406,150,ADDON_LINES,{dark:true,fonts:f});
}else if(id==='emerald-circles'){
logoOrBrand(ctx,logo,p,{x:M,y:52,dark:true,fonts:f},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 ${f.label}`,color:p.accent,maxLines:1});text(ctx,title,M,192,430,58,{font:`600 38px ${f.display}`,color:'#fff',maxLines:3});text(ctx,'Составим идеальное гастрономическое решение для вашего мероприятия.',M,350,410,22,{font:`500 13px ${f.label}`,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+66,446,112,14,{font:`600 10px ${f.label}`,color:p.accent2,align:'center',maxLines:2})});
menuList(ctx,s,p,M,510,470,66,5,{dark:true,fonts:f});await photoGridCircles(ctx,s,p,565,520,380,3,58,{numbered:true,fonts:f});
statCards(ctx,s,p,M,940,W-2*M,78,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ ',],[s.guests||'—','ГОСТЕЙ']],{dark:true,fonts:f});priceBand(ctx,s,p,M,1032,W-2*M,90,{dark:true,fonts:f});
checklistCard(ctx,p,M,1140,470,170,'ВСЁ ПОД КОНТРОЛЕМ КОМАНДЫ СОЛНЦА',CHECK_LINES,{dark:true,fonts:f});addonRow(ctx,p,540,1140,406,170,ADDON_LINES,{dark:true,fonts:f});
}else if(id==='midnight-checklist'){
logoOrBrand(ctx,logo,p,{x:M,y:48,dark:true,fonts:f},s.brandName);if(s.client)text(ctx,String(s.client),W-M,58,300,20,{font:`700 15px ${f.display}`,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:`500 11px ${f.label}`,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:`500 12px ${f.label}`,color:p.muted,maxLines:2});
text(ctx,'МЕНЮ',M,215,300,24,{font:`700 20px ${f.display}`,color:'#fff',maxLines:1});menuList(ctx,s,p,M,255,W-2*M,58,8,{dark:true,fonts:f});
await photoGridSquare(ctx,s,p,M,860,W-2*M,190,5,2,{numbered:false,fonts:f});
statCards(ctx,s,p,M,1075,470,74,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true,fonts:f});priceBand(ctx,s,p,540,1075,406,74,{dark:true,fonts:f});
checklistCard(ctx,p,M,1170,470,150,'ВСЁ ПОД КОНТРОЛЕМ КОМАНДЫ СОЛНЦА',CHECK_LINES,{dark:true,fonts:f});addonRow(ctx,p,540,1170,406,150,ADDON_LINES,{dark:true,fonts:f});
}else if(id==='gourmet-hero'){
logoOrBrand(ctx,logo,p,{x:W-M-190,y:44,dark:true,fonts:f},s.brandName);text(ctx,'КЕЙТЕРИНГ ДЛЯ',M,96,460,44,{font:`700 32px ${f.display}`,color:'#fff',maxLines:1});text(ctx,'ВАШЕГО СОБЫТИЯ',M,140,460,44,{font:`700 32px ${f.display}`,color:'#fff',maxLines:1});text(ctx,'Индивидуальное меню, безупречная подача и сервис, о котором будут говорить ваши гости.',M,235,400,24,{font:`500 14px ${f.label}`,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 ${f.label}`,color:p.accent,maxLines:1});
const realCats=[...new Set((s.items||[]).map(it=>it.categoryName).filter(Boolean))].slice(0,5);
if(realCats.length){const gap=10,cw=(W-2*M-gap*(realCats.length-1))/realCats.length;realCats.forEach((t,i)=>{const xx=M+i*(cw+gap);roundRect(ctx,xx,494,cw,32,10,'rgba(255,255,255,.06)',p.line);text(ctx,t,xx+cw/2,503,cw-10,14,{font:`600 10px ${f.label}`,color:p.accent,align:'center',maxLines:1})})}
const featured=(s.items||[]).slice(0,3);const cardW=(W-2*M-2*14)/3;
{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 ${f.label}`,color:p.accent,maxLines:1});text(ctx,it.name||'Позиция меню',xx+14,735,cardW-28,20,{font:`600 13px ${f.display}`,color:'#fff',maxLines:2});if(it.weight)text(ctx,String(it.weight),xx+14,765,cardW-28,14,{font:`500 10px ${f.label}`,color:p.muted,maxLines:1})})}
roundRect(ctx,M,822,W-2*M,52,14,null,p.accent,1.4);text(ctx,'СМОТРЕТЬ ПОЛНОЕ МЕНЮ →',W/2,838,W,18,{font:`700 12px ${f.label}`,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,fonts:f});priceBand(ctx,s,p,376,910,306,100,{dark:true,fonts:f});checklistCard(ctx,p,698,910,248,100,'ДОСТАВКА',CHECK_LINES.slice(0,2),{dark:true,fonts:f});
addonRow(ctx,p,M,1045,W-2*M,150,ADDON_LINES,{dark:true,fonts:f});
}else if(id==='diamond-gold'){
logoOrBrand(ctx,logo,p,{x:M,y:50,dark:true,fonts:f},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-70,58,140,16,{font:`700 11px ${f.label}`,color:'#fff',align:'center',maxLines:1})}
text(ctx,'ИНДИВИДУАЛЬНОЕ МЕНЮ',M,150,440,34,{font:`700 28px ${f.display}`,color:'#fff',maxLines:1});text(ctx,'для вашего мероприятия',M,186,440,24,{font:`500 18px ${f.display}`,color:p.accent,maxLines:1});text(ctx,'Мы создаём гастрономические впечатления, которые запоминаются.',M,222,400,20,{font:`500 13px ${f.label}`,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:`500 10px ${f.label}`,color:p.accent2,maxLines:1})});
menuList(ctx,s,p,M,440,470,56,5,{dark:true,fonts:f});
text(ctx,'ПРИМЕРЫ ЗАКУСОК',540,428,406,16,{font:`700 12px ${f.label}`,color:p.accent,maxLines:1});await photoGridSquare(ctx,s,p,540,455,406,300,3,2,{numbered:true,fonts:f});
statCards(ctx,s,p,M,900,306,90,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true,fonts:f});priceBand(ctx,s,p,376,900,306,90,{dark:true,fonts:f});
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+124,dgy+70,248,14,{font:`700 9px ${f.label}`,color:p.muted,align:'center',maxLines:1});
checklistCard(ctx,p,M,1010,470,170,'ДОСТАВКА И КОНТРОЛЬ КАЧЕСТВА',CHECK_LINES,{dark:true,fonts:f});addonRow(ctx,p,540,1010,406,170,ADDON_LINES.concat(['Посуда','Мебель']),{dark:true,fonts:f});
}else if(id==='premium-dark'||id==='premium-emerald'){
logoOrBrand(ctx,logo,p,{x:M,y:44,dark:true,fonts:f},s.brandName);
text(ctx,'ПЕРСОНАЛЬНОЕ ПРЕДЛОЖЕНИЕ',M,140,500,16,{font:`700 11px ${f.label}`,color:p.accent,maxLines:1});
text(ctx,title,M,164,W-2*M,40,{font:`700 32px ${f.display}`,color:'#fff',maxLines:2});
bulletList(ctx,p,f,M,256,W-2*M,30,CHECK_LINES,'#dfe3e8');
if(hero)coverImage(ctx,hero,M,396,W-2*M,230,26);
statBadgeRow(ctx,p,f,M,650,W-2*M,104,[[iconCalendar,dateText(s.date)||'Уточняется','Дата мероприятия'],[iconPeople,s.guests?String(s.guests):'—','Количество гостей'],[iconBox,String(Math.round(classicBoxCount(s))||0),'Коробок в заказе']]);
text(ctx,'МЕНЮ',W/2,786,W,26,{font:`700 22px ${f.display}`,color:'#fff',align:'center',maxLines:1});
line(ctx,M,812,410,812,p.line,1);line(ctx,590,812,W-M,812,p.line,1);
await photoGridSquare(ctx,s,p,M,846,W-2*M,272,3,2,{numbered:true,fonts:f});
statCards(ctx,s,p,M,1138,565,95,[[Math.round(classicBoxCount(s))||0,'КОРОБОК'],[(s.items||[]).length,'ПОЗИЦИЙ']],{dark:true,fonts:f});
priceBand(ctx,s,p,640,1138,306,95,{dark:true,fonts:f});
checklistCard(ctx,p,M,1253,470,113,'ВСЁ ПОД КОНТРОЛЕМ',CHECK_LINES.slice(0,2),{dark:true,fonts:f});
addonRow(ctx,p,540,1253,406,113,ADDON_LINES,{dark:true,fonts:f});
}
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(_){}})}
// ---- Fully-styled inner pages for the 2 polished templates, so the whole document reads as one design instead of a nice cover glued to the generic base pages. ----
function newPage(p,dark){const 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);if(dark){ctx.save();roundRect(ctx,20,20,W-40,H-40,0,null,p.accent,1.4);ctx.restore()}return{canvas,ctx}}
function contentHeader(ctx,p,f,s,label,dark){text(ctx,String(s.brandName||'Солнце Кейтеринг').toUpperCase(),M,50,320,16,{font:`700 11px ${f.label}`,color:p.accent,maxLines:1});text(ctx,eventTitle(s),M,70,520,26,{font:`600 20px ${f.display}`,color:dark?'#fff':p.ink,maxLines:1});text(ctx,label,W-M,58,260,16,{font:`600 11px ${f.label}`,color:p.muted,align:'right',maxLines:1});line(ctx,M,108,W-M,108,p.line,1)}
async function itemRowsPage(ctx,p,f,items,y0,dark){
const rowH=dark?96:104,gap=dark?12:14;
const imgs=await Promise.all(items.map(it=>loadImage(it.photoData||'')));
items.forEach((item,i)=>{
const yy=y0+i*(rowH+gap),photoSize=rowH-20,px=M+10,py=yy+10,textX=px+photoSize+18,sum=Number(item.sum??(Number(item.unitPrice||0)*Number(item.qty||0)));
roundRect(ctx,M,yy,W-2*M,rowH,16,dark?'rgba(255,255,255,.04)':p.paper,dark?'rgba(255,255,255,.12)':p.line);
roundRect(ctx,px,py,photoSize,photoSize,12,p.deep,null);
if(imgs[i])coverImage(ctx,imgs[i],px,py,photoSize,photoSize,12);
text(ctx,item.name||'Позиция меню',textX,yy+14,W-2*M-(textX-M)-135,22,{font:`600 16px ${f.display}`,color:dark?'#fff':p.ink,maxLines:1});
const meta=[item.weight?String(item.weight):'',item.categoryName?String(item.categoryName):''].filter(Boolean).join(' · ');
if(meta)text(ctx,meta,textX,yy+42,W-2*M-(textX-M)-135,16,{font:`500 12px ${f.label}`,color:p.muted,maxLines:1});
text(ctx,'× '+Math.round(Number(item.qty||1)),W-M-16,yy+16,110,18,{font:`700 13px ${f.label}`,color:p.accent,align:'right',maxLines:1});
text(ctx,money(sum),W-M-16,yy+rowH-36,110,18,{font:`700 16px ${f.label}`,color:dark?'#fff':p.deep,align:'right',maxLines:1});
});
}
function pricingPage(ctx,p,f,s,dark){
text(ctx,'ИТОГОВАЯ СМЕТА',M,132,W-2*M,34,{font:`600 28px ${f.display}`,color:dark?'#fff':p.deep,maxLines:1});
const pr=s.pricing||{};let y=200;
const rows=[['Стоимость позиций',Number(pr.base||0),false]];
if(Number(pr.manual||0)>0)rows.push(['Скидка',Number(pr.manual),true]);
if(Number(pr.promo||0)>0)rows.push(['Промокод'+(s.promoCode?` «${s.promoCode}»`:''),Number(pr.promo),true]);
rows.push(['Меню после скидок',Number(pr.itemsTotal||0),false]);
rows.push(['Доставка',Number(pr.delivery||0),false]);
rows.forEach(([label,val,negative])=>{
roundRect(ctx,M,y,W-2*M,52,14,dark?'rgba(255,255,255,.04)':p.paper,dark?'rgba(255,255,255,.12)':p.line);
text(ctx,label,M+20,y+17,520,18,{font:`500 14px ${f.label}`,color:dark?'#eef2ee':p.ink,maxLines:1});
text(ctx,(negative?' ':'')+money(val),W-M-20,y+17,260,18,{font:`700 16px ${f.label}`,color:negative?'#c0554a':(dark?'#fff':p.deep),align:'right',maxLines:1});
y+=52+10;
});
y+=8;
roundRect(ctx,M,y,W-2*M,160,20,p.accent,null);
text(ctx,'ИТОГО К ОПЛАТЕ',M+28,y+24,400,16,{font:`700 12px ${f.label}`,color:p.deep,maxLines:1});
text(ctx,money(pr.total),M+28,y+48,W-2*M-56,56,{font:`700 44px ${f.display}`,color:p.deep,maxLines:1});
if(s.guests)text(ctx,'на гостя: '+money(Number(pr.itemsTotal||pr.total||0)/Math.max(1,Number(s.guests))),M+28,y+118,400,20,{font:`600 14px ${f.label}`,color:p.deep,maxLines:1});
y+=160+36;
text(ctx,'Спасибо, что выбираете нас — будем рады сделать ваше событие особенным.',M,y,W-2*M,22,{font:`500 15px ${f.label}`,color:p.muted,maxLines:2});
}
async function renderContentPages(s,id){
await ensureFontsReady(id);
const p=PALETTES[id],f=fontsFor(id),dark=id!=='cream-elegance';
const items=s.items||[],top=120,bottom=H-90,rowH=dark?96:104,gap=dark?12:14;
const perPage=Math.max(1,Math.floor((bottom-top+gap)/(rowH+gap)));
const pageCount=Math.max(1,Math.ceil(items.length/perPage));
const pages=[];
for(let pi=0;pi<pageCount;pi++){
const {canvas,ctx}=newPage(p,dark);
contentHeader(ctx,p,f,s,`Меню · стр. ${pi+1} из ${pageCount}`,dark);
await itemRowsPage(ctx,p,f,items.slice(pi*perPage,(pi+1)*perPage),top,dark);
pages.push(canvas);
}
const priced=newPage(p,dark);
contentHeader(priced.ctx,p,f,s,'Стоимость',dark);
pricingPage(priced.ctx,p,f,s,dark);
pages.push(priced.canvas);
return pages;
}
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 [cover,innerPages]=await Promise.all([renderCover(snapshot,id),renderContentPages(snapshot,id)]);
const pages=[cover,...(innerPages||[])],total=pages.length,p=PALETTES[id]||PALETTES['cream-elegance'];
try{const ctx=cover.getContext('2d');ctx.save();ctx.setTransform(SCALE,0,0,SCALE,0,0);footer(ctx,p,1,total,snapshot?.brandName,fontsFor(id));ctx.restore()}catch(_){}
const f=fontsFor(id);
(innerPages||[]).forEach((canvas,i)=>{try{const scale=canvas.width/W,ctx=canvas.getContext('2d');ctx.save();ctx.setTransform(scale,0,0,scale,0,0);footer(ctx,p,2+i,total,snapshot?.brandName,f);ctx.restore()}catch(_){}});
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,.sun-offer-template-mini[data-template-mini="premium-dark"] i,.sun-offer-template-mini[data-template-mini="premium-emerald"] 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}
.sun-offer-template-mini[data-template-mini="premium-dark"]{background:radial-gradient(circle at 18% 15%,rgba(224,165,69,.35),transparent 40%),linear-gradient(160deg,#12161d,#0a0d12)!important;border-color:#252b35!important}
.sun-offer-template-mini[data-template-mini="premium-emerald"]{background:radial-gradient(circle at 18% 15%,rgba(63,207,142,.35),transparent 40%),linear-gradient(160deg,#121d19,#0a1210)!important;border-color:#233830!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})};
})();