From d71cec6e44d13e837163884c19a2516a8b948cee Mon Sep 17 00:00:00 2001 From: alva-p Date: Sun, 25 Oct 2026 11:30:08 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Truco:=20cartas=20m=C3=A1s=20grandes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mano propia: de 72–120 px a 100–140 px de ancho - Cartas jugadas: más grandes en 1v1 y 2v2 (en 3v3 quedan como estaban, no entran seis pilas grandes) - Cartas del compañero más grandes en la compu - El de enfrente lleva sus cartas y su globo al costado, para no tapar su pila - Pie de altura fija: la mesa no cambia de tamaño al terminar la mano Co-Authored-By: Claude --- src/components/TrucoCard.tsx | 7 +- src/components/TrucoTable.tsx | 121 +++++++++++++++++++--------------- src/index.css | 12 ++++ 3 files changed, 85 insertions(+), 55 deletions(-) diff --git a/src/components/TrucoCard.tsx b/src/components/TrucoCard.tsx index addfa99..d5d0d4b 100644 --- a/src/components/TrucoCard.tsx +++ b/src/components/TrucoCard.tsx @@ -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) => ( Date: Sun, 25 Oct 2026 11:37:46 -0300 Subject: [PATCH 2/2] Botones de volver en mesas y sala de espera; landing con UNO y truco --- src/components/TrucoCard.tsx | 4 +- src/i18n.ts | 32 ++++++++++++---- src/pages/Game.tsx | 9 +++++ src/pages/Landing.tsx | 74 +++++++++++++++++++++++++++++------- src/pages/Lobbies.tsx | 12 +++++- 5 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/components/TrucoCard.tsx b/src/components/TrucoCard.tsx index d5d0d4b..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 ? ( <> diff --git a/src/i18n.ts b/src/i18n.ts index ff3c441..98407cc 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -7,9 +7,9 @@ const resources = { common: { title: 'CHAIN TABLE', play_now: 'Jugar Ahora', - hero_title: 'UNO con amigos.', + hero_title: 'UNO y truco con amigos.', hero_title_accent: 'Con premio, si te animás.', - hero_sub: 'Entrá con un alias y jugá gratis en segundos. Conectá tu wallet para mesas con pozo en cripto: un contrato guarda la plata y le paga al ganador.', + hero_sub: 'Entrá con un alias y jugá gratis en segundos: UNO de 2 a 8, o truco mano a mano y en equipos. Conectá tu wallet para mesas con pozo en cripto: un contrato guarda la plata y le paga al ganador.', live_tables_one: '{{count}} mesa abierta', live_tables_other: '{{count}} mesas abiertas', live_playing_one: '{{count}} jugando', @@ -18,7 +18,7 @@ const resources = { step1_title: 'Elegí un alias', step1_text: 'Sin registro ni mail. La wallet es opcional y la conectás cuando quieras.', step2_title: 'Sentate en una mesa', - step2_text: 'Pública, privada con contraseña o paga. De 2 a 8 jugadores.', + step2_text: 'Elegí UNO o truco: pública, privada con contraseña o paga.', step3_title: 'Ganá la partida', step3_text: 'En las mesas pagas el premio va a tu wallet apenas termina la partida.', paid_kicker: 'Con premio', @@ -26,11 +26,19 @@ const resources = { paid_text: 'Todos pagan la misma entrada y el pozo queda en un contrato inteligente, no en nuestras manos. El contrato solo puede pagarles a los jugadores de esa mesa, y si la partida no arranca recuperás tu entrada.', networks_label: 'Redes', coming_soon: 'Las mesas pagas se habilitan muy pronto.', + paid_uno_only: 'Por ahora las mesas pagas son solo de UNO; el truco con premio viene después.', + games_title: 'Dos juegos, una mesa', + game_uno_text: 'El clásico de colores: +2, +4, saltos y cambios de sentido. Gana el primero que se queda sin cartas.', + game_uno_tags: 'De 2 a 8 jugadores|Mesas pagas|Rápido', + game_truco_text: 'Truco argentino sin flor: envido, truco, retruco y vale cuatro, con los tantos en fósforos.', + game_truco_tags: '1v1 · 2v2 · 3v3|A 15 o 30|Pica-pica en 3v3', + back_home: 'Volver al inicio', + tables_back: 'Mesas', final_cta: '¿Armamos una partida?', made_by: 'Hecho por', change_language: 'Cambiar idioma', // Auth - auth_brandline: 'UNO on-chain, con amigos', + auth_brandline: 'UNO y truco on-chain, con amigos', auth_connect_wallet: 'Conectar Wallet', auth_wallet_prompt: 'Las mesas gratis se juegan solo con alias. Para las mesas pagas vas a necesitar una wallet: la podés conectar ahora o después.', table_public: 'Pública', @@ -212,9 +220,9 @@ const resources = { common: { title: 'CHAIN TABLE', play_now: 'Play Now', - hero_title: 'UNO with friends.', + hero_title: 'UNO and truco with friends.', hero_title_accent: 'With a prize, if you dare.', - hero_sub: 'Pick an alias and play for free in seconds. Connect your wallet for tables with a crypto pot: a smart contract holds the money and pays the winner.', + hero_sub: 'Pick an alias and play for free in seconds: UNO for 2 to 8, or Argentine truco one-on-one and in teams. Connect your wallet for tables with a crypto pot: a smart contract holds the money and pays the winner.', live_tables_one: '{{count}} open table', live_tables_other: '{{count}} open tables', live_playing_one: '{{count}} playing', @@ -223,7 +231,7 @@ const resources = { step1_title: 'Pick an alias', step1_text: 'No sign-up, no email. Your wallet is optional; connect it whenever you want.', step2_title: 'Take a seat', - step2_text: 'Public, private with a password, or paid. 2 to 8 players.', + step2_text: 'Pick UNO or truco: public, private with a password, or paid.', step3_title: 'Win the game', step3_text: 'On paid tables the prize goes to your wallet as soon as the game ends.', paid_kicker: 'With prizes', @@ -231,11 +239,19 @@ const resources = { paid_text: 'Everyone pays the same entry and the pot sits in a smart contract, not with us. The contract can only pay the players at that table, and if the game never starts you get your entry back.', networks_label: 'Networks', coming_soon: 'Paid tables are coming very soon.', + paid_uno_only: 'For now paid tables are UNO only; truco with prizes comes later.', + games_title: 'Two games, one table', + game_uno_text: 'The color classic: +2, +4, skips and reverses. First one out of cards wins.', + game_uno_tags: '2 to 8 players|Paid tables|Quick', + game_truco_text: 'Argentine truco without flor: envido, truco, retruco and vale cuatro, with matchsticks for the score.', + game_truco_tags: '1v1 · 2v2 · 3v3|To 15 or 30|Pica-pica in 3v3', + back_home: 'Back to home', + tables_back: 'Tables', final_cta: 'Up for a game?', made_by: 'Made by', change_language: 'Change language', // Auth - auth_brandline: 'On-chain UNO with friends', + auth_brandline: 'On-chain UNO and truco with friends', auth_connect_wallet: 'Connect Wallet', auth_wallet_prompt: 'Free tables only need an alias. Paid tables need a wallet: connect it now or later.', table_public: 'Public', diff --git a/src/pages/Game.tsx b/src/pages/Game.tsx index f1fcf5d..3b10102 100644 --- a/src/pages/Game.tsx +++ b/src/pages/Game.tsx @@ -267,6 +267,15 @@ const Game: React.FC = () => { 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")}