fix: replace gourmet-hero's fake category tabs with real ones

Same issue as the cards just removed from cream-elegance: a hardcoded
['РЫБНЫЕ','МЯСНЫЕ','ВЕГЕТАРИАНСКИЕ','САЛАТЫ','ДЕСЕРТЫ'] tab row that
had nothing to do with what was actually in the order (an order with
zero vegetarian items still showed a "ВЕГЕТАРИАНСКИЕ" tab as if
active/relevant). Now derives the chip row from the distinct
categoryName values actually present on the order's items, and
renders nothing if that data isn't available.

Verified this pass end-to-end rather than just by inspection:
- Real "Скачать PDF" pipeline (sunClientOfferDebugPdf) produces a
  valid application/pdf blob with a correct %PDF-1.4 header, not just
  canvases.
- Confirmed Canvas text genuinely falls back through the font stack
  (measured identical metrics for an unregistered font vs. Georgia
  directly, and document.fonts.load() resolves instantly for a
  never-registered family) -- so a blocked/unreachable Google Fonts
  degrades silently to Georgia/Arial instead of hanging or erroring,
  which matters given this app already had to work around Russia
  connectivity issues elsewhere.
- Re-rendered all 6 templates against deliberately adversarial data
  (150+ word event name, 44-char client name, 350 guests, a
  million-ruble price, an item with no photo/price/category, a long
  promo code) -- no crashes, no overlap, graceful ellipsis truncation
  throughout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
pavlov346346-source 2026-09-16 10:57:29 +03:00
parent b702b4b69e
commit 28fd97bcf3

View File

@ -146,7 +146,8 @@
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 tabs=['РЫБНЫЕ','МЯСНЫЕ','ВЕГЕТАРИАНСКИЕ','САЛАТЫ','ДЕСЕРТЫ'];let tx=M;tabs.forEach((t,i)=>{text(ctx,t,tx,505,200,16,{font:`700 11px ${f.label}`,color:i===0?'#fff':p.muted,maxLines:1});if(i===0)line(ctx,tx,528,tx+70,528,p.accent,2);tx+=90});
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});