Rework de los bonus diarios + códigos canjeables — ganá fragmentos cada día y canjeá códigos de la comunidad
Dos cambios para la comunidad llegan juntos. (1) El bonus diario ya no reparte sobres gratis en un esquema plano — ahora otorga fragmentos cada día en un ciclo [3,3,3,5,5,5,8] y completar una semana consecutiva concede un bonus escalonado de sobres (1 → 2 → 3 → 4, tope en 4). Si fallás un día la racha vuelve a cero — completá la semana para llevarte el bonus de anclaje. (2) Nueva sección en el tab de Perfil para canjear códigos (p. ej. WELCOME-2026, códigos de partners, sorteos de creadores). Los códigos pueden otorgar cualquier combinación de sobres, fragmentos y piezas por rareza; el admin los crea desde la nueva pestaña Códigos del panel de administración, con tope de usos y fecha de caducidad opcionales.
Bonus diarios
- Ciclo diario de fragmentos: [3, 3, 3, 5, 5, 5, 8] según tu posición en la racha. Los fragmentos se acumulan para forja y conversión a sobres.
- Bonus de sobres al completar la semana en el día 7 / 14 / 21 / 28 / …: +1 sobre la primera semana, +2 la segunda, +3 la tercera, +4 desde la semana 4 (tope).
- Si fallás un día → la racha se reinicia a 0; el siguiente bonus de 'semana completada' vuelve a +1. Los bonus anteriores de +3 / +5 / +10 sobres en el día 7 / 14 / 28 quedan retirados.
- UI del calendario actualizada: tira de 7 días con la semana actual y una etiqueta 'Sem N' que muestra las semanas completadas acumuladas.
- Las rachas existentes se reiniciaron a cero al desplegar para que todos arranquen la nueva programación en igualdad — no se traspasa crédito de media semana.
Códigos canjeables
- Nueva sección 'Canjear' en el tab de Perfil — ingresá un código, ve la recompensa y revisá tus últimos 20 canjes.
- Cada usuario puede canjear cada código como máximo una vez (un índice único por (código, usuario) garantiza que no haya canjes duplicados).
- Los códigos pueden incluir sobres, fragmentos y piezas por rareza en cualquier combinación; el admin define la recompensa al crearlos.
- Tope total de canjes opcional (p. ej. 'solo los primeros 100 canjes') y fecha de caducidad por código (p. ej. 'válido hasta el viernes').
- Limitado a 5 intentos por minuto por usuario para descartar fuerza bruta por tipeo. Toda creación / edición / borrado de códigos por el admin queda en el audit log.
Sweep de traducciones en el juego
- Las etiquetas de carta (ATK / DEF / HP / PM / Pasiva), los badges (¡NUEVA!, ✦ Nueva, En rotación pronto, Legado) y las fases (Bebé / Fase 1-3 / MAX) ahora aparecen en tu idioma en colección, revelación de gacha e instancias en batalla.
- Campana de notificaciones + toasts de guerra de gremio: cada línea, sufijo de 'hace X', badge de victoria/derrota/empate y resumen de recompensas pasa ya por el idioma, con plurales correctos en polaco y ruso.
- Cromo de la sala de batalla (copy del lobby, overlay de rotar dispositivo, etiqueta de la mano, prefijo del rival, pista de objetivo, enlace Cancelar) y la overlay del anuncio recompensado (carga, motivos de error, botón de canjeo, aviso de mantener visible) ya están totalmente localizados.
- Flujo de confirmación de la galería de copias (progreso de nivel, confirmaciones de disolución, avisos de mazo bloqueado y trade pendiente, contador del rate limit, previsualización de fragmentos / piezas) — cada cadena que estaba solo en inglés ahora está por idioma.
- Fallback del ErrorBoundary (botón Recargar + copy seguro) y los tooltips de la WalletBar (sobres / fragmentos / pity / bonus diario disponible) ahora se muestran en el idioma activo en los 12 locales soportados.
- Pantallas previas al login traducidas de punta a punta: login / registro / 2FA / contraseña olvidada / restablecer contraseña (títulos, subtítulos, placeholders, botones, mensajes de error y éxito, copy del gate de edad y ToS, aviso de email sin verificar con temporizador).
- Overlay de onboarding del primer arranque (3 pasos activos + indicadores, botones saltar y siguiente, todas las descripciones de tipos de carta / rareza / holo / fase / habilidad) ahora en tu idioma para que el flujo del primer sobre llegue nativo.
- Frases con guiño durante la apertura de sobres: 58 frases únicas × 12 idiomas (~700 escrituras creativas) para que el texto que rota mientras se abre un sobre se lea en tu idioma en sobres estándar, dorados y diamante.
Pulido global de idiomas
- El banner de cookies ahora cambia de idioma correctamente cada vez que cambiás de idioma — antes se quedaba en el primer idioma visitado a partir del segundo cambio (p. ej. /es → /en cambiaba, pero /en → /it dejaba el banner en inglés sobre la página en italiano).
- Cartas Destacadas (la página de showcase) traducida de punta a punta a los 12 idiomas soportados — cada nombre de carta y cada blurb de marketing de 100-150 palabras ahora es nativo en lugar de caer al español para visitantes que no son de habla hispana ni inglesa.
- Entradas de la página de Novedades: esta entrada y las dos anteriores (2026-04-28 — traducción del texto de las cartas + clarificación del tope de Disolver en masa) ahora también están totalmente en idioma nativo en los 12 locales. Las entradas más antiguas caen al inglés con elegancia mientras las vamos completando en próximas releases.
- Modal de detalles de carta: la etiqueta 'X copias' y el tope de Disolver en masa ahora reflejan la carta que estás viendo realmente — antes, al abrir una carta y navegar a otra en la misma sesión del modal se mostraba el conteo de la PRIMERA carta para cada carta siguiente, dando números equivocados como '4 copias' en una pila de la que en realidad tenías 19.
El texto de las cartas ahora en tu idioma — nombres, flavor y descripciones de habilidades traducidos para las 113 cartas
Hasta hoy la interfaz estaba localizada pero el contenido de las cartas seguía en inglés: nombres, líneas de flavor, nombres y descripciones de habilidades aparecían en inglés sin importar el idioma que tuvieras elegido. Tradujimos las 113 cartas al español (España + fallback LATAM), francés, italiano, alemán, portugués (BR + fallback PT), polaco, neerlandés, ruso y turco — el nombre canónico más el flavor, más los nombres y descripciones de habilidades activas y pasivas cuando existen. La carga del servidor en inglés sigue siendo la autoritativa para la base de datos; lo que ves en pantalla se superpone en cliente según tu idioma, así la identidad de la carta se mantiene al cambiar idioma o intercambiar. Si falta alguna traducción de algún campo de alguna carta, cae al texto en inglés; nunca a un hueco.
Qué cambió
- Los 113 nombres de carta traducidos a los 11 idiomas objetivo (es, es-419, fr, it, de, pt-BR, pt-PT, pl, nl, ru, tr) — pt-PT y es-419 están ahora completamente poblados como variantes nativas, no como fallbacks.
- PT-PT (portugués europeo): grafías regionales (Fênix→Fénix, Tectônico→Tectónico, Gêiser→Géiser, Fumaça→Fumo), vocabulario distinto (mordida→dentada, filhote→cria, pulinho→saltinho, quica→salta, terremoto→terramoto, demais→restantes, libera→liberta, bravo→zangado), colocación enclítica (se alimenta→alimenta-se, a se tornar→a tornar-se), e imperativos en forma tu (Você não passará→Tu não passarás).
- es-419 (español de Latinoamérica): expresiones naturales LATAM (Picado en Bomba→Bombardeo en Picada), evitar 'Concha' (vulgar en el Cono Sur) reemplazado por 'Caparazón' por seguridad, y una nota clara de que el voseo (AR/UY) no se aplica intencionalmente para que el catálogo se mantenga universal en LATAM.
- Líneas de flavor traducidas para cada carta — son 113 cadenas únicas × 9 idiomas.
- Nombres y descripciones de habilidades activas traducidas para las 60 criaturas que las tienen.
- Nombres y descripciones de habilidades pasivas traducidas para las 50 criaturas que las tienen.
- Conectado en cada sitio donde se renderiza una carta: rejilla de colección, modal de carta, comparador de cartas, deck builder, modal de forja, pestaña de forja, revelación de gacha, sala de draft, mano e instancias en batalla, tooltips al pasar el cursor, previsualizaciones al arrastrar, desplegables de propuesta de intercambio y la previsualización del intercambio.
Notas
- Las líneas de stats de objetos como '+5 ATQ.' o 'Fuego +3 ATQ.' se mantienen tal cual — son texto mecánico donde traducir no aportaría nada.
- La identidad de la carta (card_id, instance_id, semilla holo, ATQ/DEF/PV/PM) se preserva al superponer el idioma — solo cambian las cadenas mostradas. Los intercambios, mazos y repeticiones de batalla son 100 % compatibles entre idiomas.
El tope de Disolver en masa ahora se explica solo — ves por qué tu máximo es el que es
Feedback de jugadores tras el lanzamiento de Disolver en masa: el changelog decía "hasta 50 por lote" pero un jugador con solo 21 copias de una carta veía un tope de 20 y asumía que algo estaba roto. Nada lo estaba — el servidor garantiza que conservás al menos 1 copia de cada carta, así que una pila de 21 copias tope en 20 de una vez. El tope siempre fue mín(máx servidor 50, copias elegibles, copias totales − 1); solo era invisible. Ahora la etiqueta del botón de entrada dice exactamente cuál es tu tope de lote en formato "máx N de M elegibles", y un tooltip al pasar el cursor explica la restricción que limita (tope del servidor, elegibilidad, o conservar-1). La cabecera del panel de selección repite la restricción para que no tengas que adivinar.
Qué cambió
- El botón de entrada de Disolver en masa ahora dice "💠💠 Disolver en masa... (máx N de M elegibles)" en vez de solo "(M elegibles)" — N es el tope real de selección honrando tope del servidor + conservar-1.
- El tooltip al pasar el cursor sobre el botón muestra la razón vinculante: "máx servidor 50", "M elegibles", o "conservando 1 de T totales".
- La cabecera del panel de selección ahora añade la restricción vinculante tras el número del tope, así la razón se ve sin pasar el cursor.
- Comportamiento sin cambios: la regla conservar-1 siempre se aplicó en el servidor. Este cambio es puramente claridad UX — las mismas protecciones, etiquetas más transparentes.
Disolver en masa — destruí hasta 50 copias de una carta en una sola acción
Acumuladores, esto es para ustedes. Disolver copias comunes de una en una les quemaba la ventana de rate-limit por usuario — un jugador con 80 copias comunes de una sola carta no podía realmente limpiarlas de una sentada. La vista de detalle de carta muestra ahora un botón "Disolver en masa..." debajo del Disolver por copia que ya había, que aparece siempre que tengas al menos 2 copias elegibles de la carta que estás viendo. Hacé clic, elegí qué números de impresión destruir desde una rejilla de mosaicos (las copias bloqueadas, en mazo, o en intercambio activo aparecen atenuadas y desactivadas), y todo el lote se procesa en una sola confirmación y una sola ranura de rate-limit. El tope del servidor es 50 por llamada, así que la respuesta sigue siendo rápida. La vista previa de recompensa te muestra los fragmentos exactos y las piezas por rareza que vas a recibir antes de presionar Destruir todo.
Qué cambió
- Vista de detalle de carta → nuevo botón "💠💠 Disolver en masa... (N elegibles)" debajo del Disolver por copia. Solo aparece cuando tenés ≥2 copias elegibles (elegibles = desbloqueadas + no-en-mazo + no-en-intercambio-activo).
- La rejilla de mosaicos muestra cada copia con su número de impresión y marcadores holo / bloqueada / en-mazo / en-intercambio. Las copias no elegibles aparecen atenuadas y desactivadas. La selección se limita a mín(50, elegibles, copias − 1) para que nunca puedas vaciar la pila completa.
- Controles "Seleccionar todo" y "Limpiar" más una vista previa en vivo "+X fragmentos / +Y piezas R" que refleja la UI de recompensa por copia. El modal de confirmación pregunta una vez antes de cualquier llamada de red: "¿Disolver N copias de \"Nombre de la Carta\"? +X frags +Y piezas R. [Cancelar] [Destruir todo]".
- Servidor: se consume una sola ranura de rate-limit sea cual sea el tamaño del lote — esa es la razón de ser de la función. La transacción completa es atómica (las 50 copias se disuelven juntas o ninguna).
- Seguridad ante intercambios pendientes: si alguna copia seleccionada está en un intercambio activo, el servidor avisa y ofrece un aviso "Cancelar y disolver" que reenvía la llamada con confirmación explícita. Sin sobrescritura silenciosa de un intercambio en curso.
- Seguridad de última copia: por carta, el servidor garantiza que conservás al menos una copia. Un lote que dejaría una pila a cero se rechaza con un error claro y no se borra ninguna fila.
Colección: filtro "Próximas a rotar" + tablas públicas de torneos de gremios
Dos mejoras pequeñas pero visibles en calidad de vida. En la pestaña Colección, el menú de disponibilidad ganó una opción "Próximas a rotar" que filtra la grilla a cartas que vencen en los próximos 30 días — el mismo umbral de la insignia que ya venía mostrándose en esas cartas, así que el filtro y la insignia siempre coinciden. En la pestaña Torneo, los torneos de gremios muestran ahora su panel de clasificación a cualquier visitante — no solo a los participantes inscritos. Antes abrías un torneo de gremios y veías participantes pero no cuadro salvo que tu gremio estuviera registrado. Ahora el panel público se renderiza para todos, leyendo los mismos datos que el servidor ya devolvía, así que puedes ver quién va al frente sin tener que registrarte primero.
Qué cambió
- Colección → menú de disponibilidad → nueva opción "Próximas a rotar" filtra cartas cuyo `available_until` cae dentro de los próximos 30 días. La fila de chips activos muestra un chip "Próximas a rotar" removible mientras el filtro está activo.
- La ventana de 30 días es ahora una constante compartida única, así que el filtro y la insignia "Próximas a rotar" existente no pueden divergir.
- Pestaña Torneo → torneos de gremios → el panel de clasificación se renderiza también para visitantes no inscritos. Mismos datos que la vista del participante; el panel te muestra quién va al frente incluso antes de que tu gremio se inscriba.
- Los torneos individuales quedan sin cambios — el panel público solo se renderiza cuando `tournament_type === "guild"`.
Endurecimiento de build y CI — gates de calidad movidos al CI, waivers para el audit de dependencias, refresco diario de la IAB GVL — barrido de revisión hostil de 14 áreas completo
La última área del barrido de revisión hostil de 14 áreas — pipelines de build y gates de CI. La mayoría de los cambios son invisibles para los jugadores (viven en `.github/workflows/` y `scripts/`) pero mueven la calidad de "solo local con bypass de --no-verify" a "bloquean el merge en cada PR". La suite completa de vitest (~2700 tests), el type-check de TypeScript, el drift gate de Drizzle, la paridad de claves i18n, el pin de BullMQ, la validación de battle-config y el baseline de bare-db-execute corren ahora en CI en cada PR. El escaneo de CVE de producción ganó una puerta consciente de waivers que expone vulnerabilidades moderadas (el umbral anterior silenciaba seis). La IAB Global Vendor List que usa el banner de consentimiento se refresca ahora a diario con un workflow programado que abre un PR cuando cambia la lista. Dos tests skipped de la suite de batalla llevan referencias rastreadas (T-201, T-202) y una nueva invariante hace fallar el build ante cualquier skip futuro sin tag para que no se acumule código muerto.
Qué cambió para los jugadores
- Nada visual. Todo el lote es tooling interno — workflows de CI, gates de audit de dependencias, higiene de build. La nota de cierre importa porque la revisión de 14 áreas sacó a la luz unos ~600 hallazgos en todo el código (auth, motor de batalla, esquema, gacha, API de admin, API pública, Game.tsx y tabs, UI de cartas/mazos/batalla, internals de perfil/gremios/torneos, anticheat, i18n, legal/consentimiento, observabilidad, infra de build/test) — cada arreglo visible para los jugadores de las trece áreas anteriores ya salió en entradas anteriores del changelog. Esta entrada solo cierra el barrido.
- Lado operador: la IAB Global Vendor List que el banner de consentimiento usa para la disclosure por proveedor se refresca ahora a diario con una GitHub Action programada que abre un PR cuando cambia la lista. Una GVL obsoleta era un riesgo de deriva de alcance de proveedor (peor caso: Limited Ads de AdSense en la UE). Una persona revisa cada PR de refresco antes del merge.
- Dos dependencias de producción con vulnerabilidades conocidas (next-intl + postcss-vía-next) están ahora formalmente documentadas en `context/refs/cve-waivers.md` con fechas de revisión explícitas (2026-07-25). El gate de CVE anterior las escondía subiendo el umbral de severidad; el nuevo gate expone todas las moderadas-y-mayores y falla el CI a menos que una fila de waiver las cubra. Visibilidad honesta en lugar de riesgo silencioso.
Estabilidad del servidor y privacidad en los logs — handlers de errores del proceso, retención de audit aplicada, nombres de jugador redactados de los logs
Una pasada por el pipeline de observabilidad y auditoría cerró el hueco entre lo que dice la política de privacidad y lo que el servidor hace de verdad. Nada cambia visualmente para los jugadores, pero varias cosas mejoraron por debajo: el proceso de Node.js ahora registra y cuenta los errores no capturados en lugar de caerse en silencio y dejar que la plataforma lo reinicie sin explicación; la tabla audit_log se purga a diario con un cron que aplica la ventana de retención de 90 días que promete la política de privacidad; los nombres de jugador se redactan ahora de las líneas de log del servidor (el Considerando 30 del RGPD trata los nombres como datos personales); y el pipeline de auditoría de eventos de seguridad ganó chequeos de durabilidad y huella de IP por acción para que una inspección regulatoria pueda reconstruir cualquier acción de cuenta con timestamp, actor y origen.
Qué cambió
- El proceso de Node.js ahora registra un error no capturado antes de que la plataforma lo reinicie. Antes el servidor moría en silencio, el contenedor se reiniciaba solo, y debuggear después requería adivinar a partir de los timestamps de muerte del contenedor. Ahora cada error fatal aterriza en el log estructurado con el stack trace completo y un contador para graficar las tasas de caída con el tiempo.
- La política de privacidad dice que los logs de auditoría se conservan 90 días. Antes nada lo aplicaba de verdad — las filas viejas se acumulaban para siempre. Un nuevo cron diario (`/api/cron/audit-retention`) elimina cualquier cosa con más de 90 días en lotes, así la tabla queda acotada y la promesa de la política es honesta.
- Los nombres de jugador se redactan ahora de las líneas de log del servidor. El Considerando 30 del RGPD trata los nombres de usuario como datos personales; la forma anterior de los logs incluía los campos winnerName / challengerName / opponentName tal cual en cada línea de fin de batalla. Pino redact los enmascara como [Redacted] antes de que lleguen al agregador.
- Lado operador: los endpoints /api/metrics y /api/health admiten ahora un gate de token Bearer en producción para que los datos de observabilidad no queden expuestos en la internet abierta (configura METRICS_BEARER_TOKEN / HEALTH_TOKEN; en dev se tolera la ausencia). El endpoint de reportes de violación CSP (/api/csp-report) limita ahora por IP para que un spammer no pueda ahogar las alertas CSP reales.
Endurecimiento legal y de consentimiento — string TCF v2.3 oficial, auditoría duradera del consentimiento, edad + condiciones aplicadas en servidor, lista real de proveedores, borrado y exportación de datos ampliados
Una pasada por la superficie legal y de consentimiento cerró una lista larga de huecos de cumplimiento. Titulares: el string de consentimiento IAB TCF v2.3 es ahora el formato oficial bit-packed que producen las librerías @iabtechlabtcf/* en lugar de un shim base64(JSON) — el parser de Google AdSense rechazaba nuestro shim desde la fecha límite del 2026-03-01, lo que silenciosamente dejaba a los visitantes de la UE en Limited Ads. Cada elección del banner de consentimiento (Aceptar todas / Rechazar todas / Personalizar / retirar / aviso de version bump / reapertura) escribe ahora una fila duradera en audit_log para que podamos demostrar el consentimiento bajo una inspección del Art.7(1) RGPD. La confirmación de edad 16+ y la aceptación de las Condiciones al registrarte se aplican ahora del lado del servidor mediante los additional fields de Better Auth — el checkbox solo cliente anterior se podía saltar por curl. La política de privacidad nombra ahora a los terceros reales (Arsys para hosting + Google AdSense para anuncios) en lugar de texto genérico — Postgres y Redis corren sobre infraestructura administrada por el operador en Arsys y no son encargados independientes. La eliminación de cuenta limpia ahora un conjunto mucho mayor de tablas con datos personales (amistades, pertenencia a gremios, entradas de torneo, tickets, historial de anuncios, semillas provably-fair, tokens de verificación y varias más), usa un sufijo de seudónimo aleatorio criptográfico que rompe la trazabilidad anterior con audit_log, se niega a recrear una solicitud de borrado para una cuenta ya borrada por completo, y limita los reintentos del cron para que una falla transitoria no entre en loop infinito. El artefacto de exportación de datos ganó seis nuevas secciones (pertenencia a gremios, torneos, amistades, tickets de soporte, historial de anuncios, historial de semillas provably-fair) y topes por sección para que un usuario power no pueda hacer OOM al servidor. El banner de consentimiento es ahora un diálogo modal correcto con focus trap y aria-modal, el selector de idioma es visible en cada breakpoint (estaba oculto en mobile), y los baneos de admin entregan ahora una Declaración de Motivos del Art.17 DSA mediante notificación dentro de la app con la regla violada y una ruta de apelación. Además: una IAB Global Vendor List nueva incluida y un script de refresco diario para que los cambios de alcance de proveedores no queden obsoletos, las cookies de primera parte de AdSense de verdad se borran al retirar el consentimiento (antes solo se desmontaba el tag de script), y un nuevo gate CSRF con token en los endpoints de borrado de cuenta y exportación de datos para que una página atacante con un formulario falsificado no pueda lanzar la cuenta regresiva del borrado para un visitante con sesión iniciada. Versión de consentimiento subida de 3.1 a 3.2 — todos los visitantes verán el banner una vez en su próxima visita para confirmar bajo la lista actualizada de procesadores.
String IAB TCF v2.3 real + auditoría duradera del consentimiento
- El string de consentimiento TCF v2.3 lo produce ahora el encoder oficial @iabtechlabtcf/core y la API de gestión de consentimiento es la librería oficial @iabtechlabtcf/cmpapi — sustituyendo un shim base64(JSON) que el parser de Google AdSense rechazaba desde el 2026-03-01, dejando a los visitantes de la UE en Limited Ads. El CMP se instala como `__tcfapi` en la página exactamente según la especificación IAB CMP v2.
- Cada resolución del banner de consentimiento (Aceptar todas / Rechazar todas / Personalizar / retirar / aviso de version bump / reapertura) escribe una fila duradera en audit_log etiquetada scope='legal' con el payload completo de consentimiento por finalidad, los opt-ins de funciones especiales, el string addtl_consent, la versión previa y una bandera de si la curación de Google ATP faltaba en el momento del consentimiento. Ahora podemos responder 'prueba que esta persona consintió en este momento bajo esta versión' solo desde audit_log — antes ese rastro no existía.
- La IAB Global Vendor List incluida se refrescó y un script `npm run refresh:gvl` permite que un cron diario suelte un snapshot fresco. Una GVL desactualizada era un riesgo de deriva del alcance del proveedor — cada refresco ejecuta una comprobación de esquema antes de renombrar atómicamente al disco para que un fetch interrumpido nunca deje un archivo a medio escribir.
Edad + Condiciones + CSRF + lista de procesadores aplicados en servidor
- El registro persiste ahora `age_confirmed_at` y `tos_version_accepted` en la fila de usuario mediante los additional fields de Better Auth, y un hook del servidor rechaza con dureza cualquier registro al que le falte cualquiera de los dos valores. Antes ambos eran checkboxes solo cliente que cualquier petición curl podía saltarse.
- La lista de encargados de la política de privacidad nombra ahora a los terceros reales: Arsys (España) para hosting + Google AdSense / AdMob para publicidad. Postgres y Redis corren sobre infraestructura administrada por el operador en Arsys y no son encargados independientes. La línea hipotética de 'procesador de pagos' desaparece hasta que la tienda cosmética se lance bajo un futuro flag.
- Los endpoints de borrado de cuenta y exportación de datos requieren ahora un token CSRF (patrón double-submit cookie) para que una página atacante con un formulario falsificado no pueda lanzar la cuenta regresiva del borrado de 30 días ni robar el artefacto de exportación de un usuario con sesión iniciada. La cookie de sesión de Better Auth es sameSite=Lax y de otra forma habría permitido POSTs cross-origin desde una navegación de nivel superior.
Eliminación de cuenta y exportación de datos ampliadas
- La eliminación de cuenta borra o anonimiza ahora un conjunto mucho mayor de tablas con datos personales: amistades y solicitudes, pertenencia y chat de gremios, solicitudes de unión y wishlist de gremios, participantes y rondas de torneo, tickets de soporte y respuestas, tokens e historial de anuncios, historial de semillas provably-fair, tokens de verificación de email y reseteo de contraseña — y aplica la anonimización de centinela a match-telemetry, donaciones a fondos de gremios y varias tablas más con retención de auditoría. Antes más de diez tablas conservaban silenciosamente los datos incluso después de que la eliminación quedara marcada como 'completada'.
- El seudónimo que se usa para sobrescribir el nombre y el correo de una cuenta eliminada usa ahora un sufijo aleatorio criptográfico en lugar de un corte determinista de 8 caracteres del id original. El corte anterior llevaba 32 bits de entropía del id original, así que un regulador podía correlacionar el nombre centinela de vuelta al usuario eliminado vía audit_log.target_id. El nuevo sufijo rompe el vínculo por completo.
- Dos solicitudes de eliminación simultáneas del mismo usuario se colapsan ahora en una sola fila en la base de datos (índice único parcial sobre `(user_id) WHERE status IN ('pending','processing')`); el POST de solicitud devuelve `already_pending` en vez de romperse con un 500. Un usuario cuya eliminación ya se completó no puede crear una nueva solicitud — la nueva comprobación `wasUserEverDeleted` la rechaza directamente, defendiendo contra un futuro un-ban que de otra forma resucitaría una cuenta a medio anonimizar.
- Si una transacción de eliminación falla a mitad (violación de FK, lock timeout, blip de red) el worker del cron sube ahora un contador de reintentos y guarda el último mensaje de error en lugar de quedarse en loop en `pending`. Tras cinco intentos fallidos la fila pasa a `failed` para atención de admin.
- La exportación de datos cubre ahora seis nuevas secciones: pertenencia a gremios, entradas de torneo, amistades, tickets de soporte y respuestas, historial de anuncios e historial de semillas provably-fair. La auto-documentación `gaps[]` previa las listaba como 'lo tenemos pero no lo damos' — eso violaba la portabilidad del Art.20 RGPD. Cada sección también lleva un tope de 10.000 filas para que un usuario power no haga OUT OF MEMORY al servidor, y un campo `truncations[]` indica las exportaciones parciales con honestidad.
Accesibilidad del banner + higiene de cookies AdSense + entrega del Art.17 DSA
- El banner de cookies es ahora un diálogo modal correcto: role='dialog', aria-modal='true', aria-labelledby apuntando a un encabezado solo para lectores de pantalla, y un focus trap que captura el foco al abrir y lo restaura al cerrar. El Tab no puede escapar a la página de debajo. El Esc intencionalmente no cierra porque sería un rechazo implícito — un dark pattern.
- El selector de idioma (ES | EN) es ahora visible en cada breakpoint, incluido mobile — antes estaba oculto bajo el breakpoint sm:, lo que combinaba mal con el bug del middleware F-A11-009 que ya silenciosamente ponía a los visitantes en el idioma equivocado.
- Retirar el consentimiento publicitario borra ahora de verdad las cookies de primera parte de AdSense (`__gads`, `__gpi`, `__eoi`, `FCNEC`, `FCCDCF`) del navegador, no solo el tag de script inyectado. Antes una retirada dejaba esas cookies en su lugar, lo que violaba ePrivacy + Art.7(3) RGPD (la retirada debe equivaler al borrado de cookies).
- Cuando un admin banea a un usuario, el usuario recibe ahora una notificación dentro de la app con la Declaración de Motivos del Art.17 DSA con la regla violada, la razón y una ruta de apelación — y la fila de audit_log lleva el mismo payload bajo scope='legal'. Antes los baneos eran silenciosos: el usuario no podía iniciar sesión, no sabía por qué, y no tenía ruta de apelación documentada. El endpoint rechaza ahora cualquier baneo que no suministre la regla y la razón.
i18n hardening — Spanish chrome everywhere, locale-aware dates, mobile language switcher, unified contact email
A pass through the bilingual surface closed every visible English-only chrome remnant on Spanish pages and tightened a few middleware + SEO gaps. Headline items: the marketing nav (About / How to Play / Cards / FAQ / Contact / Updates / Log in / Play for Free), every legal-page header link, the global cookie-consent banner, the offline banner, and the language-switcher screen-reader label all read from the active-locale message bundle now — Spanish visitors no longer see English wrappers around Spanish copy. The language switcher is also visible on phones (was hidden under the sm: breakpoint), so a visitor landing on the wrong locale on mobile can finally jump in-page. Changelog dates are stored as ISO YYYY-MM-DD and rendered through Intl.DateTimeFormat per locale — Spanish visitors see "26 de abril de 2026" / English visitors see "April 26, 2026". Email addresses across the legal + contact surface have been unified to a single canonical contact@drawntcg.com so visitors don't have to guess between info / privacy / support inboxes. A new /robots.txt now points crawlers at the per-locale sitemap, and link-preview crawlers (Slack, Discord, iMessage, Twitter) get an Open Graph card with the right locale tag. A subtle middleware bug that made the <html lang="…"> attribute always say "es" on /en/* pages — silently breaking screen-reader locale routing and Google's hreflang verification — has been fixed.
Spanish chrome on every public surface
- MarketingHeader (every public page) reads its nav labels and CTAs from the marketing_header.* namespace — Spanish visitors see Sobre nosotros / Cómo jugar / Cartas / FAQ / Contacto / Novedades / Iniciar sesión / Juega Gratis instead of the English equivalents.
- Legal page chrome (the back button, the four short links in the header — Terms / Privacy / Cookies / Legal Notice — and the long versions in the footer plus the all-rights-reserved line) all translate. Pre-fix /es/legal/* pages had a Spanish body with English chrome wrapping it.
- Global cookie-consent banner (the EU-mandated TCF v2.3 surface) translates intro copy, the Accept All / Reject All / Customize / Save Preferences buttons, every section heading, and every screen-reader aria-label so Spanish visitors don't see an English banner when they first land.
- The "You appear to be offline" banner (mounted on every page) and the language-switcher's accessibility label both read from the active locale.
Locale-aware dates + visible language switcher on mobile
- Changelog entry dates are now stored as ISO YYYY-MM-DD and rendered with Intl.DateTimeFormat — "26 de abril de 2026" in Spanish, "April 26, 2026" in English, with a proper <time dateTime="…"> wrapper for SEO.
- The Spanish changelog page heading is now "Registro de cambios" instead of the English word "Changelog".
- The language switcher (ES | EN) is now visible at every breakpoint, including phones. Pre-fix it was hidden under the sm: breakpoint, so a phone visitor landing on the wrong locale had to manually edit the URL to switch.
Single canonical contact email + softer Discord copy
- Every contact channel across the marketing, legal, and privacy surface points at a single canonical address: contact@drawntcg.com. Pre-fix the same visitor could see info@ on the contact page, privacy@ in the privacy policy, and support@ in the terms — confusing for anyone trying to figure out where to write.
- The Discord callout was softened — instead of "the invite link is being set up — when it lands it will show up here" it now says the invite is coming soon and points readers at the changelog page for updates in the meantime. No false promise of imminence.
Crawler + middleware fixes (mostly invisible but real)
- A new /robots.txt enumerates the public surface, blocks the authenticated game shell + admin + API + auth-only paths from crawl, and points search engines at /sitemap.xml so the per-locale sitemap is no longer discovered organically.
- Link-preview crawlers (Slack, Discord, iMessage, Twitter / X) now receive an Open Graph card with the active locale tag (es_ES or en_US) plus the alternate-locale list and a Twitter summary_large_image hint — bilingual link shares no longer surface the wrong-locale variant by accident.
- A subtle middleware bug that made <html lang="…"> always say "es" on /en/* pages is fixed. Pre-fix the request-header forwarding ran before next-intl's locale-injection, so the root layout never saw the active locale and fell back to the default — silently breaking screen-reader locale routing and Google's hreflang verification.
- Set-Cookie headers are now appended (not set) when merging next-intl's response — multiple cookies on a single response are no longer collapsed into one comma-joined value that browsers misparse.
Anticheat + provably-fair hardening — atomic seed rotation, replay scrubbing, spectator strategy strip
A pass through the anticheat surface (provably-fair seeds, replay sanitization, spectator state, collusion-detection telemetry) closed a small list of subtle but real defects. Headline items: pack-opening seed rotation is now atomic against in-flight pack opens, so a verifier replay can never reproduce N−1 of N openings while the Nth orphan dangles unverifiable; replay history scrubs raw player ids out of turn-log strings before persistence so the publishable replay file truly cannot be cross-correlated by id; spectator state strips two alpha-strategy hints (adrenaline counters and per-turn summon counts) that pre-fix flowed through unmasked; provably-fair seed rotations and client-seed changes are now durably audited so a player dispute has a recoverable trail; the history endpoint no longer 500s on garbage page params and no longer leaks raw Postgres errors to clients.
Provably-fair integrity
- Pack-opening seed rotation is now atomic against any in-flight pack open. Pre-fix a rotate landing between a pack-open's seed read and nonce update could publish a stale total_nonces value, leaving the in-flight opening unverifiable by the verifier. The race is closed via a transactional row lock plus a database-level unique-active-seed constraint.
- Both seed rotations and player-supplied client_seed changes now write a durable audit row, so a player who later disputes "I rotated at 12:34 and my opening is broken" has a recoverable trail in audit_log alone instead of relying on log-line forensics.
- PATCH client_seed now validates the format (8-64 hex chars) so unicode, control characters, or `:` separators that would ambiguate the verifier's HMAC message string can no longer be saved.
- Provably-fair history page now degrades gracefully on garbage page parameters (?page=NaN, ?page=abc) and no longer leaks raw Postgres error messages to authenticated clients.
Spectator + replay privacy
- Spectator state no longer surfaces adrenaline counters or per-turn summon counts — these are alpha-strategy hints that competitive metagame analysis tooling could be built off of. Combatants still see them in their own action-planning UI.
- Persisted replays now scrub raw player ids out of turn-log strings before storage. Pre-fix the JSON-keyed identifier fields were already pseudonymized to player_1 / player_2 but the free-text log lines could still embed raw ids — a non-admin reader of the replay blob could correlate replays by matching id substrings.
Profile, guild, tournament, and battle UI hardening — public profile clarity, achievement toasts, action timeouts
A pass through tournaments, guilds, the profile sections, and the in-battle internals fixed a long list of small bugs and added the visible affordances that were missing. Headline items: viewing a stranger's profile now tells you why bio / display case / achievements are hidden ("add as friend to see more") instead of rendering blank sections; achievement toasts auto-dismiss after 6 seconds (and cap at 5 visible at once) instead of stacking forever; every in-battle action — summon, attack, ability, mulligan, end turn, forfeit — now has a 5-second "no response from server" safety net so a flaky socket can't leave the UI stuck pending; the forfeit button uses an in-app modal instead of the browser's native confirm() popup that iOS PWAs sometimes silently suppress; the tournament bracket marks forfeit / disconnect / bye matches with a small badge so admins and spectators can tell them apart from real wins; deck-builder save validates the 3-copies-per-card limit client-side before letting you press Save; and a list of guild-management quality-of-life polish items (kick / promote confirmation, donation amount cap, war-end refresh filtering, identical-message dedupe).
Public profile + achievement toasts
- Viewing another player's profile now shows an inline notice ("Add as friend to see this player's bio, display case, achievements, and recent activity") when those sections are gated by the friendship check. Pre-fix the sections just rendered blank and players thought their account was broken.
- Public profile now shows the player's division badge (Bronze / Silver / Gold / etc.) alongside ELO — was missing on other-player profiles even though it was on your own.
- Sharing a profile link now shows "Link copied!" / "Could not share" feedback instead of being silent.
- Achievement toasts now auto-dismiss after 6 seconds and cap at 5 visible at once. Pre-fix they stacked forever and players who unlocked 5 achievements in a battle had to manually click each ×.
In-battle robustness
- Every player action — summon, evolve, attack, ability, equip, set trap, place field, use object, mulligan, end turn, go to battle, forfeit — now races against a 5-second server-ack timeout. If the server doesn't respond (network drop, server hiccup) you get a "No response from server" toast instead of the action sitting visually pending forever.
- Forfeit button uses an in-app confirmation modal showing the consequences (loss for record + rating). Replaces the browser's native confirm() popup that iOS PWA installs sometimes silently suppress, leading to "I clicked forfeit and nothing happened" reports.
- Mulligan decision can no longer multi-fire: a rapid double-tap on Keep/Mulligan now sends only one decision to the server.
- Spectator orientation hardening: the in-battle action handlers now no-op for spectators as a defense-in-depth layer over the existing UI gating.
- Battle tooltips no longer flicker shut on Android because of the touch+click race; status / zone tooltips now stay open on tap until you tap somewhere else.
- Mulligan timer screen and the in-app confirm modals now have proper aria-modal labelling for screen-reader users; previously they were unannounced.
Tournaments + guilds
- Tournament bracket now shows a small FF / DC / BYE badge on matches that ended by forfeit, disconnect, or auto-advance — pre-fix those rendered identical to real wins.
- Guild kick / promote / demote actions now require a second click to confirm (button changes to "Click again" with a 4-second auto-cancel). Pre-fix a single accidental click on Kick removed the member instantly.
- Guild chat: Enter spam can no longer post identical messages within 5 seconds; emoji and RTL names render their first character correctly in avatar bubbles.
- Guild pool donation amount is now bounded (1 to 99,999 fragments) and rejects garbage values like NaN that the form could previously generate.
- Guild war end-of-war notification now only refetches our own guild's wars instead of every guild_war_ended event in the world.
Profile sections
- Display Case selection: ordering is now user-controllable (←→ buttons on selected cards); cards traded away or dissolved no longer linger as zombie selections; the per-page collection cap was raised to 1000 cards with a clear notice if you have more than that.
- Profile name / avatar / bio now save independently — pre-fix clicking Save next to your avatar (after also typing a new name) submitted both, accidentally burning the 30-day name change cooldown.
- Avatar URL field now shows an inline preview as you type, with a fallback when the URL is invalid.
- Password change client validation now mirrors the server policy (min 8 chars + at least one digit + at least one letter + must differ from current). Pre-fix the only check was 8-char minimum.
- 2FA setup secrets (TOTP URI, backup codes, password fields) now wipe from React state on unmount so browser extensions can't scrape them from a stale snapshot.
- Provably-fair Rotate Seed: failures now surface a clear error (was silent), and the revealed seed gets a "Copy seed" button + a warning that the next rotation will reveal a new one and this one cannot be re-displayed.
- Account deletion request: surfaces "Deletion service is temporarily unavailable" upfront if the backend isn't configured, and double-clicks are rejected synchronously so two deletion rows can't be scheduled.
Battle UI fixes — spectator view, mulligan timer, deck builder hardening
A pass through the in-battle screen, the deck builder, and the card / trade modals fixed a long list of small but visible bugs. The headline items: spectators were seeing the battle mirrored to the wrong side (their view labelled the opponent as "you"), the mulligan timer reset to 30 every time anything happened on the board, the queue size showed blank when joining casual matchmaking, and trade history's wishlist 🎯 markers showed the wrong player's wishlist. The card modal now cancels in-flight fetches when you flip through a gallery, the deck builder caps how many pages it loads (so a server hiccup can't lock the screen), and copying a deck export now actually tells you whether it worked.
In-battle correctness
- Spectators now see the battle with a fixed orientation — challenger always at the bottom, opponent always at the top — instead of having the view randomly mirror to the wrong side and label the wrong player as you.
- Mulligan timer no longer resets to 30 every time the opponent acts. It now starts at 30 once when the mulligan phase begins and counts down honestly from there.
- Card tooltips no longer get wiped mid-read every time the opponent's HP ticks. They only close on real turn / phase changes now.
- If you tab out of a battle and come back, the in-battle screen requests a fresh state from the server instead of rendering whatever stale snapshot it had.
- Leaving a battle from the lobby (before the opponent joins) now actually frees your slot on the server — pre-fix you could get stuck in "already in a battle" trying to requeue.
- Casual queue size now displays correctly on join (was showing blank / NaN due to a payload mismatch).
- Battle history (the resolved list under your decks) now updates live when a battle ends — no more refresh required to see your latest match.
- Rematch banner now only shows after a battle that resolved naturally (win / loss). It used to show after forfeits and opponent disconnects too, which was confusing.
- Tournament report-result now uses an in-app modal that shows the opponent's name. Native browser confirm() popups are gone (one less spot the design guidelines didn't apply).
Deck builder + card modal
- Deck builder now validates the 3-copies-per-card limit before letting you press Save (was checked at add time only — corrupt or imported decks could slip through with 4+ copies and silently fail server-side).
- Loading a giant collection in the deck builder is now bounded — there's a hard cap on how many pages it'll fetch (50, well above any realistic collection) so a server pagination bug can't lock the screen.
- Card modal cancels in-flight network requests when you flip to another card. Pre-fix the previous card's wishlist / instances / evolutions could resolve last and overwrite the new card's data.
- Locking / unlocking a card copy now surfaces an error if the server rejects it. Used to silently swallow the failure.
- Trade history shows a Retry button if the load fails, instead of a fake "No completed trades yet" forever.
- Copying a deck export to clipboard now shows a Copied! / Copy failed toast. Used to be silent.
- Live battles list (the spectate panel) no longer shows a Watch button on your own battles, and pauses its 15-second poll when the tab is in the background.
Battle action feedback — no more frozen UI when the server hiccups
When you summon a creature, attack, or end your turn, the client now waits up to 5 seconds for the server to confirm. If the confirmation never arrives — server hiccup, network drop, dropped packet — you get a clear "No response from server — check your connection" message instead of a button that just stops responding. If the server rejects the action (out of phase, illegal target, rate-limited, etc.) the rejection reason now reaches you immediately even on flaky connections, instead of riding the next state push and arriving seconds late. The other (less critical) actions are unchanged. Spanish and English locales also got `not-found` and `loading` pages so a stale link or a slow load lands on a localised page instead of the framework default.
Battle action feedback
- Summon, attack, and end-turn now show an error toast if the server doesn't acknowledge within 5 seconds, instead of leaving the action visually pending.
- Server-side rejections (out of phase, illegal target, rate-limited) now reach you immediately even when state push is delayed.
- Localised `not-found` and `loading` pages — stale links and slow loads now land on a Spanish or English page instead of the framework default.
Privacy + safety tightening
- Profile bio, display case, recent activity, and achievements are now visible only to friends and to yourself. Strangers see your name, rating, and division but not your collection wall — closes a long-standing complaint that anyone with your user URL could browse your private profile content.
- Blocking a player is now a hard hide — your profile returns 404 to anyone you've blocked (and theirs to you), instead of just hiding tabs while still leaking the basics.
- Friend activity feed no longer surfaces fields that the original event didn't intend to share. Each event type has a strict allowlist (rare pull, achievement, battle result, trade, guild join, tournament win) so nothing extra leaks even if a future event payload changes shape.
- Leaderboard now requires sign-in. Anonymous bots can no longer scrape the rating table.
Notifications + import
- You can now mark individual notifications as read instead of "all or nothing" — the inbox now passes the specific ids you tap.
- Deck import now caps the pasted text at 4 KB so a giant accidental paste returns a clear error instead of stalling. The cap is large enough for any legitimate deck list.
Real-time push fixes
- Friend requests, accepts, and rejects now actually update the badge counters and friend list without a manual refresh. The wire vocabulary between client and server had drifted apart and every notification was being silently dropped at the server allowlist; the canonical verbs are now shared and the relay forwards them end-to-end.
- Trade offers, accepts, declines, and cancellations also push to the other player live now — the badge ticks up the moment the offer arrives, instead of waiting for the next page load.
- Trade UI: the 🎯 markers now correctly show cards the OTHER player wants (used to silently show your own wishlist on both columns).
Tab UX + battery savings
- Hidden tabs no longer drain bandwidth in the background — the leaderboard and challenges tabs now pause their live subscriptions when you're on another tab and resume when you come back.
- Leaderboard now shows a clear error + retry button if the load fails, instead of the perpetual "Loading leaderboard…" spinner.
- Challenge / Accept buttons in Battles no longer lock forever when the socket hiccups. After 15 seconds without a server ack you get a "Network timeout — try again" toast and the button releases.
- Tournament "I Won" confirmation is now a styled in-app dialog showing the opponent's name, instead of the browser's native confirm() popup. Same for visual polish + accessibility (aria-modal).
- Support ticket replies are now a multi-line textbox (Shift+Enter for newline, Enter to send) instead of a single-line input that silently truncated paragraph replies.
SEO sweep — sitemap, hreflang, canonical URLs across all locales — Phase 1 i18n fully closed
The bilingual public surface is now wired up for search engines. Every Spanish and English page emits a `<link rel="canonical">` pointing at its own locale URL plus an `<link rel="alternate" hreflang>` map covering both locales and an `x-default` fallback, so Google can pair the /es and /en variants of the same page instead of treating them as duplicates. A new `/sitemap.xml` enumerates every Phase 1 public page in every locale with the same hreflang alternates per entry. With this release Phase 1 of the i18n project is fully closed: 4 legal pages bilingual, 7 marketing pages bilingual, controlling-version clauses live in both languages, locale switcher in both headers, hreflang plumbing in metadata and sitemap, and a written workflow template for any future page.
SEO + future-page template
- New `/sitemap.xml` enumerates every Phase 1 public page (landing, about, contact, FAQ, how-to-play, showcase, changelog, legal/cookies, legal/notice, legal/privacy, legal/terms) in both Spanish and English, with per-entry hreflang alternates so search engines pair the locale variants properly.
- Every translated page now emits a per-page canonical URL pointing at its own /es or /en path and an hreflang `languages` map covering both locales plus `x-default` (defaulting to Spanish), so duplicate-content penalties are off the table.
- Future-page workflow template added to `context/refs/marketing-hero-draft.md` — every new public page added after Phase 1 must ship in BOTH Spanish and English from the first commit. The template covers the dual-locale draft workflow, TCG vocabulary, register rules, and the required SEO wiring.
- Phase 1 i18n closes here: 11 public pages × 2 locales fully bilingual end-to-end, plus the SEO infrastructure, controlling-version clauses, locale switcher, and the written contract for future bilingual pages.
Marketing pages, mastery, tournaments, and a sweep of UX gaps
A big batch of player-facing work landed across the spring. The headline items: a public marketing surface for new visitors, a card mastery system that rewards loyalty to specific cards, a tournament bracket viewer that finally lets you see how a tournament is unfolding, and a long list of small UX fixes that close gaps players have been pointing out for months. Below is the rough shape of what changed, organised by area. Patch notes for individual cards and balance tweaks live in the in-game patch feed; this changelog covers the structural and surface-level stuff.
Marketing pages (public)
- New landing page that actually explains the game, instead of dropping you straight onto a login form. Returning players get a one-click "Log in" in the sticky header.
- About, How to Play, FAQ, Contact, Featured Cards, and this Changelog — all reachable from the same header on every public page.
- Cookie consent banner upgraded to the IAB TCF v2.3 standard so EU players get the proper opt-in flow with all the per-purpose toggles.
Crafting and collection
- New Forge tab dedicated to crafting cards from the fragments you earn by dissolving duplicates.
- Mastery system: as you play with the same card across battles, it levels up. Once a card hits level 5, you get a 10% crafting discount on copies of it. At level 10 it gets a "Mastered" badge.
- Crafting cost now shows the discounted price inline at the craft button, so you can see the saving without diving into card detail.
- Wallet bar now shows your fragment stash split per rarity, instead of one aggregate number. Easier to see at a glance whether you can afford a craft.
- Card sets browser: see every set, what cards belong to it, and your progress through it. Helps you target which sets to chase.
- Persistent "Verify past pulls" link in the Gacha tab — tap it any time to audit any pack you have ever opened against the provably-fair RNG record.
Battles and competitive
- Tournament bracket viewer: when you click into a single-elimination tournament, you now see the full bracket tree with every match, who is playing whom, and who has won so far.
- Public guild tournament standings: non-participants browsing a guild tournament can see the standings panel without being enrolled.
- Battle replays now have their own shareable URL. Open the replay in a new tab, send the link to a friend, point at the moment you want to talk about. Works for any battle either of you played in.
- Element field-spell weather effects now show up properly in damage calculations during battle — fire fields boost fire creatures, water fields boost water, etc.
- Daily and weekly challenges have a streak system: completing them on consecutive days builds a streak with milestone rewards at 7, 14, and 28 days.
- Battle stats panel shows per-element breakdowns + period filtering, so you can see how your fire decks have been doing this week vs last month.
- AI battle spam fix: a race condition that allowed double-starting AI battles is closed. No more orphan match windows.
Account and trust
- Two-factor authentication available from your profile security section. Standard authenticator apps (Authy, Google Authenticator, 1Password, Bitwarden) all work.
- Self-serve account deletion lives in the profile section — no support ticket, no won't-let-you-leave dance.
- Self-serve data export: download a copy of everything we hold about your account.
- Session security tightened: changing your password or enabling 2FA now invalidates all your other active sessions automatically. If someone was logged in as you on a different device, they get kicked.
- Email verification flow improvements: clearer messaging when verification is pending, retry-with-cooldown on the resend button.
Performance and reliability
- Battle reconnection improvements: if you drop mid-match for under a minute, you reconnect to the same game state with no penalty.
- Server-side deck-out handling rewrites — the slow control decks that win by exhausting your opponent's library now work cleanly across reconnects.
- Tournament forfeit timeouts moved to a durable job queue, so a server restart no longer eats a forfeit-pending tournament.
- Offline detection banner: if your connection drops, you see a clear banner explaining what happened, instead of the UI silently failing.
Notes for next time
- We are still adding cards, sets, and modes. The world has plenty of room to grow.
- Discord invite link is being set up — when it lands the contact page will surface it.
- If you spot something here that does not match what you see in-game, tell us — most likely we shipped it differently than the note suggests.
All marketing pages now available in Spanish — Phase 1 complete
Six more marketing pages are now bilingual, closing out Phase 1 of the marketing translation effort. /es/about renders the team / vision / game-world write-up in Spanish. /es/contact translates every channel. /es/faq covers all 12 Q&A items. /es/how-to-play translates the 10-minute rundown. /es/showcase renders the curated 7-card showcase with bilingual card names + flavor blurbs. /es/changelog (this page) is now bilingual too — every ChangelogEntry carries both Spanish and English copy together in the source so future updates land in both locales atomically. The four legal pages shipped bilingual earlier this month, so the public surface is now fully bilingual end-to-end.
Marketing pages translated
- About page (Sobre Nosotros) translated to Spanish — hero 'Hecho entre amigos, por gusto', team / vision / game-world sections all mirror the English source 1:1.
- Contact page translated — section headings 'Correo / Discord / Tickets de soporte / Una nota sobre los tiempos de respuesta', back / FAQ navigation buttons.
- FAQ translated — all 12 Q&A items across three sections (Sobre el juego / Cómo jugar / Cuenta), including questions about rewarded ads, account deletion, and where data is stored.
- How to Play translated — three sections (Mecánicas, Tipos de cartas y elementos, Flujo de batalla — turno a turno). TCG glossary mazo / sobre / carta / PV / zona de monstruo / mulligan kept as-is.
- Featured Cards (showcase) translated — every card name + 50-150 word marketing blurb. Element + monster-class labels also translated (Fuego/Agua/Viento/Tierra/Metal/Rayo/Neutral; Dragón/Bestia/Constructo/Leviatán/Alado; slime preserved as TCG term).
- Changelog page itself bilingual — every ChangelogEntry now carries both Spanish and English copy together in the source. MAINTAINER NOTE in the page source updated: from this release on, every new ChangelogEntry MUST land in BOTH Spanish AND English in the same commit. Single-locale entries are forbidden.
- All Spanish versions use the indie/playful voice from the landing page — informal tú throughout, TCG vocabulary preserved (sobres, mazos, cartas, tablero, dorsos, gremios).
- Locale switcher in the marketing header lets you flip between /es and /en versions of any page without losing your place. Phase 1 marketing translations now complete.
Landing page now available in Spanish — first marketing page translated
The homepage hero is now bilingual. Spanish-speaking visitors landing on /es see the full marketing pitch in Spanish — anti-pay-to-win positioning, daily-free-packs promise, multi-pace modes (casual, ranked, AI, just collecting), element chips, and the 'built among friends' footer — all in informal tú voice. English visitors keep the original wording at /en. This is the first marketing page to land in two languages; the rest are queued up next.
Marketing pages
- Landing page (homepage hero) translated to Spanish — every paragraph mirrors the English source 1:1: hero badge ('Gratis · Sin pay-to-win'), headline, subhead, three 'why play' tiles, 'Cómo funciona' section with element chips (Fuego, Agua, Viento, Tierra, Metal, Rayo, Neutral), and the 'Hecho entre amigos por gusto' footer.
- Spanish version uses the same playful indie register as the English source — informal tú throughout, TCG vocabulary players already know (sobres, mazos, cartas), no marketing-speak.
- Marketing translations rolling out next: about, how-to-play, FAQ, contact, showcase, changelog. Each will follow the same per-page review gate.
- Locale switcher in the marketing header lets you flip between /es and /en versions of the landing page without losing your place — same flow already in use on the legal pages.
Privacy Policy now available in Spanish — Phase 1 legal coverage complete
The Privacy Policy is now bilingual, closing out Phase 1 of the legal-page localization effort. Spanish-speaking visitors land on a fully translated /es/legal/privacy, English visitors keep the original wording at /en/legal/privacy. With this release, all four legal pages — Terms of Service, Cookie Policy, Legal Notice, and Privacy Policy — ship in both English and Spanish, each carrying the controlling-version notice that names the English text as the legally authoritative version. Phase 1 legal coverage is now fully complete.
Legal pages
- Privacy Policy translated to Spanish — every section, every data table, every right enumeration mirrors the English source 1:1 (data controller, categories of data collected, purposes and legal basis, data sharing with processors, international transfers, retention periods, GDPR data-subject rights including access/rectification/erasure/portability/objection/restriction/withdrawal of consent, GDPR Article 21 objection box, AEPD complaint route, GDPR Article 33/34 breach notification, CCPA/CPRA rights for California users, cookie disclosure, children-under-16 protections, automated-decision-making disclosure).
- Spanish version uses canonical RGPD terminology throughout: 'responsable del tratamiento', 'interesado', 'encargado del tratamiento', 'base legal', 'consentimiento', 'interés legítimo', 'Delegado de Protección de Datos (DPD)', plus the full Spanish names for each GDPR right ('derecho de acceso', 'derecho de supresión', 'derecho de oposición', 'derecho a la portabilidad', 'derecho a la limitación').
- Phase 1 legal coverage now fully complete: Terms, Cookies, Legal Notice, and Privacy all available in both Spanish and English with controlling-version notices in place.
- Locale switcher in the legal header lets you flip between /es/legal/privacy and /en/legal/privacy without losing your place — same flow already in use on the other three legal pages.
Terms of Service now available in Spanish — three legal pages bilingual
The Terms of Service page is now bilingual. Spanish-speaking visitors land on a fully translated /es/legal/terms, English visitors keep the original wording at /en/legal/terms. Both versions carry the same controlling-version notice at the top, making clear that the English text remains the legally authoritative version and the Spanish translation is provided for convenience. Three of four legal pages — Terms, Cookies, and Legal Notice — now ship in both languages; Privacy is the next page in line.
Legal pages
- Terms of Service translated to Spanish — every clause, every list item, every legal definition mirrors the English source 1:1 (eligibility, gacha mechanics, ad disclosures, IP ownership, DSA Article 17 appeal flow, Belgian gacha note, EU consumer right of withdrawal).
- Phase 1 legal-page coverage is now three-quarters complete: Terms, Cookies, and Legal Notice all available in Spanish and English. Privacy Policy translation is the remaining page.
- Controlling-version banner present at the top of every translated legal page in both locales: the English text remains legally binding and translations are explicitly marked as non-authoritative for legal interpretation.
- Locale switcher in the legal header lets you flip between /es and /en versions of any legal page without losing your place.
Cookie Policy and Legal Notice now available in Spanish
The Cookie Policy and Legal Notice pages are now bilingual. Spanish-speaking visitors land on fully translated /es/legal/cookies and /es/legal/notice, English visitors keep the original wording at /en/legal/cookies and /en/legal/notice. All four pages carry a controlling-version notice at the top making clear that the English text is the legally authoritative version and the Spanish translation is provided for convenience. Terms and privacy will follow the same pattern in upcoming releases.
Legal pages
- Cookie Policy translated to Spanish — every section, every cookie table, every browser-settings note is mirrored 1:1 with the English source.
- Legal Notice (Aviso Legal) translated to Spanish — operator identity, NIF/CIF cell, Spain-based address, applicable LSSI-CE/RGPD/LOPDGDD/DSA legislation list, intellectual property, liability and dispute-resolution sections all mirrored 1:1.
- Controlling-version banner added at the top of both legal pages in both locales: the English text remains the legally binding version and translations are explicitly marked as non-authoritative for legal interpretation.
- Locale switcher in the legal header lets you flip between /es/legal/* and /en/legal/* without losing your place.
¿Quieres sugerir lo próximo? La página de contacto tiene email y Discord. ¿Quieres seguir las actualizaciones según salen? Guarda esta página — la actualizamos cada vez que aparece un cambio visible para los jugadores.