diff --git a/src/components/TrucoCard.tsx b/src/components/TrucoCard.tsx index addfa99..a5bbaee 100644 --- a/src/components/TrucoCard.tsx +++ b/src/components/TrucoCard.tsx @@ -203,10 +203,10 @@ function parseCarta(id: string): { num: number; palo: Palo } { } /** La carta como SVG suelto (para el componente y para exportar el mazo). */ -export const TrucoCardSvg: React.FC<{ id?: string; className?: string }> = ({ id, className }) => { +export const TrucoCardSvg: React.FC<{ id?: string; className?: string; style?: React.CSSProperties }> = ({ id, className, style }) => { const c = id && parseCarta(id); return ( - + {c ? ( <> @@ -226,9 +226,10 @@ const SIZES = { xs: "w-8", sm: "w-12", md: "w-16", - played: "w-11 sm:w-16", - mini: "w-8 sm:w-12", - hand: "w-[clamp(72px,11vw,120px)]", + played: "w-14 sm:w-20", + played6: "w-11 sm:w-16", + mini: "w-9 sm:w-12", + hand: "w-[clamp(100px,27vw,140px)]", pile: "w-[clamp(84px,11vw,132px)]", } as const; diff --git a/src/components/TrucoTable.tsx b/src/components/TrucoTable.tsx index 327e0be..3bcc023 100644 --- a/src/components/TrucoTable.tsx +++ b/src/components/TrucoTable.tsx @@ -54,6 +54,14 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice, top: `${50 + ry * Math.sin(angle(s))}%`, }); + // Pilas: en 1v1 y 2v2 las de arriba y abajo cerca del centro, para no chocar con el asiento; + // en 3v3 van repartidas en el óvalo (con cartas más chicas) + const pilePos = (s: number) => { + const sin = Math.sin(angle(s)); + if (n === 6) return pos(s, 21, 24); + return { left: `${50 + 20 * Math.cos(angle(s))}%`, top: `${50 + 20 * sin}%` }; + }; + const pendingBy = st.phase === "ENVIDO" ? st.envido.caller : st.phase === "TRUCO" ? st.truco.pendingBy : null; const pendingCall = st.phase === "ENVIDO" ? st.envido.chain.map(call).join(" · ") : st.phase === "TRUCO" ? call(TRUCO[st.truco.level]) : ""; @@ -93,13 +101,15 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice, const reason = (p: Puntos) => p.reason === "mano" ? (st.truco.level ? call(TRUCO[st.truco.level - 1]) : t("truco_reason_mano")) : t(`truco_reason_${p.reason}`); - // Arriba del jugador, salvo los de la mitad de arriba de la mesa: abajo, para no taparse con el encabezado - const bubble = (s: number, below = false) => + // Arriba del jugador; los de arriba de la mesa lo llevan a la izquierda (arriba está el encabezado y abajo su pila) + const bubble = (s: number, side = false) => bubbles[s] && ( {bubbleText(bubbles[s].ev)} @@ -141,7 +151,7 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice, -
+
{/* Tanteador: en el celu arriba de la mesa; en pantallas grandes, en la esquina libre */}
= ({ st, version, bubbles, handPoints, notice, if (s === me) return null; const out = !st.order.includes(s); const partner = st.partners[s]; + // Los de arriba llevan las cartas al costado: abajo chocarían con su pila + const top = Math.sin(angle(s)) < -0.9; // solo el de enfrente return (
-
- - {s === st.mano && ( - - M - - )} - {Math.sin(angle(s)) > -0.5 && bubble(s)} +
+
+ + {s === st.mano && ( + + M + + )} + {!top && bubble(s)} +
+ + {name(s)} +
- - {name(s)} -
{partner ? partner.map((c) => ) : Array.from({ length: st.counts[s] }).map((_, i) => )}
- {Math.sin(angle(s)) <= -0.5 && bubble(s, true)} + {top && bubble(s, true)}
); })} @@ -189,7 +203,7 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice, {st.order.map((s) => { const cards = st.played.flatMap((b) => b.filter((p) => p.seat === s).map((p) => p.card)); return ( -
+
{cards.map((c, j) => { const top = st.bazaTop?.[j]; const won = top && st.results[j] !== "P" && top[0] === s; @@ -198,7 +212,7 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice, @@ -251,40 +265,43 @@ const TrucoTable: React.FC = ({ st, version, bubbles, handPoints, notice,
- {st.phase === "OVER" && st.envido.result?.cards && ( -
- {t("truco_envido_of", { player: name(st.envido.result.seat), n: st.envido.result.tanto })} - {st.envido.result.cards.map((c) => ( - - ))} -
- )} + {/* Alto fijo (botonera o envido) y lugar fijo para la mano: la mesa no cambia de tamaño entre estados */} +
+ {st.phase === "OVER" && st.envido.result?.cards && ( +
+ {t("truco_envido_of", { player: name(st.envido.result.seat), n: st.envido.result.tanto })} + {st.envido.result.cards.map((c) => ( + + ))} +
+ )} - {(calls.length > 0 || canMazo) && ( -
- {calls.map((a) => ( - - ))} - {canMazo && ( - - )} -
- )} + {(calls.length > 0 || canMazo) && ( +
+ {calls.map((a) => ( + + ))} + {canMazo && ( + + )} +
+ )} +
-
+
{st.hand.map((c) => ( { return (
+

{t("waiting_players")}

{gameState.name}

diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index 1a00a12..00ea2e0 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -6,16 +6,36 @@ import { useSocket } from "../hooks/useSocket"; import { ENABLED_NETWORKS, NETWORKS, type NetworkKey } from "../config/chains"; import { Avatar } from "../components/Table"; import { CardBack } from "../components/Card"; +import { TrucoCardSvg } from "../components/TrucoCard"; import type { Card } from "../types/game"; const HAND = [ { type: "Number", color: "Red", value: "7" }, - { type: "Action", color: "Yellow", value: "Reverse" }, { type: "Wild", color: "Wild", value: "WildDrawFour" }, { type: "Action", color: "Blue", value: "Skip" }, - { type: "Number", color: "Green", value: "3" }, ] as Card[]; const TOP = { type: "Action", color: "Green", value: "DrawTwo" } as Card; +const TRUCO_HAND = ["1-basto", "1-espada", "7-oro"]; + +const GAMES = [ + { name: "UNO", icon: "🃏", text: "game_uno_text", tags: "game_uno_tags", cls: "from-red-500/20 ring-red-400/30" }, + { name: "Truco", icon: "🗡️", text: "game_truco_text", tags: "game_truco_tags", cls: "from-sky-500/20 ring-sky-400/30" }, +]; + +// Abanico de 3 cartas del juego: UNO en imagen, truco en SVG +const Fan = ({ game, cls }: { game: string; cls: string }) => ( +
+ {[0, 1, 2].map((i) => { + const style = { rotate: `${(i - 1) * 10}deg`, translate: `0 ${i === 1 ? -8 : 0}px` }; + const c = `${cls} drop-shadow-2xl ${i ? "-ml-6 sm:-ml-7" : ""}`; + return game === "UNO" ? ( + + ) : ( + + ); + })} +
+); const STEPS = [ { n: 1, title: "step1_title", text: "step1_text" }, @@ -94,32 +114,59 @@ export default function Landing() {
))}
-
+
+
{t("prize_pool")} 0.4 ETH
-
- {HAND.map((card, i) => ( - - ))} +
+ +
+ {/* Los juegos */} +
+

{t("games_title")}

+
+ {GAMES.map((g) => ( + +
+ +
+
+

+ {g.icon} {g.name} +

+

{t(g.text)}

+
+ {t(g.tags) + .split("|") + .map((tag) => ( + + {tag} + + ))} +
+
+ + ))} +
+
+ {/* Cómo funciona */}

{t("how_title")}

@@ -143,6 +190,7 @@ export default function Landing() { 💰 {t("paid_kicker")}

{t("paid_title")}

{t("paid_text")}

+

🗡️ {t("paid_uno_only")}

{t("networks_label")}
diff --git a/src/pages/Lobbies.tsx b/src/pages/Lobbies.tsx index 31e2404..985bed7 100644 --- a/src/pages/Lobbies.tsx +++ b/src/pages/Lobbies.tsx @@ -160,7 +160,17 @@ const Lobbies: React.FC = () => {
- {t("title")} + + {t("title")}