const CARD_SIZES = { xs:{width:78,height:112}, sm:{width:98,height:140}, md:{width:118,height:168}, lg:{width:138,height:196} }; const SUITS_LOCAL=["oros","copas","espadas","bastos"]; const VALID_RANKS=[1,2,3,4,5,6,7,10,11,12]; const SUIT_INFO={ oros:{label:"Oros",ink:"#6B4308"}, copas:{label:"Copas",ink:"#651720"}, espadas:{label:"Espadas",ink:"#173F68"}, bastos:{label:"Bastos",ink:"#24552A"} }; function normalizeCard(input){ if(!input) return null; const aliases={oro:"oros",coin:"oros",coins:"oros",cup:"copas",cups:"copas",sword:"espadas",swords:"espadas",spade:"espadas",club:"bastos",clubs:"bastos"}; const rawSuit=input.suit??input.palo??input.suitId??input.suitName; const rawRank=input.rank??input.value??input.number??input.valor; const st=String(rawSuit??"").trim().toLowerCase(); const suit=SUITS_LOCAL.includes(st)?st:(aliases[st]??null); const rank=Number(rawRank); if(!suit||!Number.isInteger(rank)||!VALID_RANKS.includes(rank)) return null; return {...input,suit,rank,id:input.id??`${suit}-${rank}`}; } function rankWord(rank){ if(rank===1)return t("cardAs"); if(rank===10)return t("cardSota"); if(rank===11)return t("cardCaballo"); if(rank===12)return t("cardRey"); return String(rank); } function suitLabel(suit){return trSuit(suit)} function safeCardTitle(card){return card?`${rankWord(card.rank)} de ${suitLabel(card.suit)}`:t("cardUnknown")} function powerClass(p){return p>=11?"top":p>=8?"high":p>=5?"mid":"low"} function powerColor(p){ const map={14:"#A82626",13:"#A13A26",12:"#B45B20",11:"#8A650C",10:"#2464A6",9:"#225A93",8:"#2C7774",7:"#2D7A4C",6:"#4A7C2C",5:"#7C7F2A",4:"#5B55A0",3:"#6047A0",2:"#5A6472",1:"#565652"}; return map[p]??"#565652"; } const SPANISH_DECK_SHEET = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/Cartas_Tat%C3%BA_con_1_de_bastos_dado_vuelta.png/960px-Cartas_Tat%C3%BA_con_1_de_bastos_dado_vuelta.png"; const DECK_SHEET_ROW = { espadas:0, copas:1, oros:2, bastos:3 }; const DECK_SHEET_COL = {1:0,2:1,3:2,4:3,5:4,6:5,7:6,10:7,11:8,12:9}; function sheetPosition(card){ const row=DECK_SHEET_ROW[card.suit]??0, col=DECK_SHEET_COL[card.rank]??0; return { backgroundImage:`url("${SPANISH_DECK_SHEET}"), url("art/cards/${card.suit}-${card.rank}.svg")`, backgroundSize:"1007% 407%, 100% 100%", backgroundPosition:`${(col/9)*100}% ${(row/3)*100}%, 50% 50%` }; } function cardArtPath(card){return `art/cards/${card.suit}-${card.rank}.svg`} function PixelCard({card:inputCard,face=true,size="md",selected=false,disabled=false,onClick,dimmed=false,lift=false,suggested=false}){ const card=normalizeCard(inputCard),dims=CARD_SIZES[size]??CARD_SIZES.md, clickable=Boolean(onClick)&&!disabled, p=card?trucoPower(card):1; const title=safeCardTitle(card); return ; } function ScoreBar({label,value}){const v=Math.max(0,Math.min(30,Number(value)||0));const pct=v/30*100;return
{label}{v}/30
} function RoundStatus({results=[]}){return
{t("rounds")}
{[0,1,2].map(i=>{const r=results[i]??null;const m=r==="player"?"✓":r==="ai"?"×":r==="parda"?"=":"·";const c=r==="player"?"round-win":r==="ai"?"round-loss":r==="parda"?"round-draw":"round-pending";return
{i+1}{m}
})}
} function recommendedCardIdFor(match,playable){const hand=match.hands?.player||[];const current=match.currentTrick||[];const ai=current.find(p=>p.player==="ai")?.card;if(ai){const wins=hand.filter(c=>playable.has(c.id)&&trucoPower(c)>trucoPower(ai));if(wins.length)return wins.sort((a,b)=>trucoPower(a)-trucoPower(b))[0]?.id||null}const ph=hand.filter(c=>playable.has(c.id));return ph.sort((a,b)=>trucoPower(b)-trucoPower(a))[0]?.id||null}